Linux下Web伺服器開發
程式碼下載地址:http://download.csdn.net/detail/u010959074/9572149
學習提示:
1. 在“桌面環境”中動手練習,若環境不流暢可選擇WebIDE或字元介面。
2. 在教程下方“課程問答”中提出問題,或“共享桌面”尋求遠端幫助。
3. 在教程下方“實驗報告”中完成作業,記錄心得。公開報告可以獲得大家點評。
4. 在“我的程式碼庫”中用GIT提交你的實驗程式碼。
Web伺服器
The way to learn a programming language is to write programs.
這門專案訓練營最大的價值是實驗樓和教師共同提供的教學服務,目的只有一個:讓你把專案做出來並完全理解。所以請對自己負責,提出你遇到的任何問題,完成專案,你所花費的時間才有價值!
一、實驗說明
歡迎參加C++語言經典專案實戰訓練營,在四周的時間裡我們將一起完成四個小型及中型C++語言專案,涉及到C++語言技術的多方面。為了你能夠有所收穫,學習過程中務必注意:
進度很重要:必須跟上每週的進度,專案,問答。我們會認真對待每一位參與訓練營的同學,請你不要因為困難半途而廢。
問答很重要:遇到知識難點請多多提問,這是你的權利更是對自己負責的義務。
實踐很重要:完成每週專案,解決專案中出現的一切問題。專案都提供完整的程式碼,但僅供學習中參考,應按照實驗文件的思路自己實現,請勿直接拷貝程式碼。
實驗報告很重要:詳細記錄你完成專案任務和解決問題的思路。
反饋很重要:請務必將你對課程的建議告訴我們,不同於視訊,我們的文件可以很快更新升級以滿足你的學習需求。
程式碼練習說明
實驗樓環境使用GCC/G++ 4.8.4 編譯環境。C++程式碼採用C++11標準。編寫可以使用vim或gedit,並使用Git進行程式碼管理,實驗報告需要以Markdown格式編寫並公開。
這些技術都是很多程式設計師工作中天天用到的,如果你對其中任何知識不熟悉,可以先學習課程:
如果需要儲存程式碼到我的程式碼庫,則需要用到git,目前實驗樓的程式碼庫可以在實驗環境或者你自己的電腦上提交,請務必注意git push時輸入的使用者名稱是你登陸實驗樓的郵箱(不是你程式碼庫使用者名稱),如果你是第三方登陸的,則需要先更新郵箱及登入密碼後才可以在實驗樓外部使用。
程式編寫與編譯最基本的方法(#標誌後為註釋):
# 開啟桌面上的Xfce終端並執行後續命令# 進入到/home/shiyanlou目錄cd /home/shiyanlou# 建立並開啟原始檔hello.cpp
gedit hello.cpp# 在gedit(類似windows上的記事本)中輸入程式碼# 儲存退出# 編譯hello.cpp
g++ -std=c++11 hello.cpp -o hello# 執行生成的二進位制檔案
./hello
二、專案簡介
1. 介紹
本實驗使用 C++ 實現一個Web伺服器。
這個專案會學習C++網路開發,kqueue IO複用機制,熟悉Linux下的C++程式編譯方法,Makefile編寫。
本節實驗專案比較複雜,提供的示例程式碼接近3000行,但程式碼邏輯簡單,難點在kqueue IO複用機制的理解,儘量獨立實現,遇到難點可以參考示例程式碼或到實驗樓問答中提問。
2. 專案需求
編寫一個Web伺服器,程式具備的基本功能:
支援多個客戶端連線
支援HTTP協議中的GET,HEAD,OPTION三個方法
支援通過瀏覽器訪問多種型別的檔案資源
支援多虛擬主機,每個虛擬主機可以配置獨立的資源池
3. 知識點
本實驗實踐的知識點包括:
C++ 物件導向程式設計
基本的Makefile
C++ 網路程式設計
kqueue IO複用機制
HTTP 協議基礎
4. 專案效果圖
程式執行的截圖如下所示:
5. 專案完整程式碼
專案完整程式碼可以通過wget下載獲得:
# 下載程式程式碼wget http://labfile.oss.aliyuncs.com/courses/454/webserver.zip
# 解壓程式碼
unzip webserver.zip
# 進入程式碼資料夾檢視
cd webserver
三、程式設計與實現
3.1 需求分析
大家平時上網時,瀏覽器中顯示的HTML網頁都是來源自Web伺服器。本專案通過一個例項介紹如何實現一個簡單的Web伺服器。
本專案中實現的Web伺服器需要支援下面幾個功能:
支援多個客戶端連線
支援HTTP協議中的GET,HEAD,OPTION三個方法
支援通過瀏覽器訪問多種型別的檔案資源
支援多虛擬主機,每個虛擬主機可以配置獨立的資源池
實現具備這些基本功能的Web伺服器,我們首先需要了解HTTP協議以及IO複用機制,在上一節的專案中我們已經學習了一種IO複用方法epoll,本專案採用kqueue機制,與epoll非常類似。
3.2 HTTP協議
HTTP協議介紹的好文章非常多,這裡只列出專案中需要的知識點,推薦大家系統學習HTTP協議,請閱讀:
HTTP協議的一個主要特點就是客戶端伺服器模式,在上一個專案中我們有過介紹,基於socket的連線過程,如果大家沒有印象可以去複習下。過程如下:
模型如下:
解釋如下:
伺服器端:
socket()建立監聽Socket
bind()繫結伺服器埠
listen()監聽客戶端連線
accept()接受連線
recv/send接收及傳送資料
close()關閉socket
客戶端:
socket()建立監聽Socket
connect()連線伺服器
recv/send接收及傳送資料
close()關閉socket
其實實現的Web伺服器是一個支援HTTP協議的TCP伺服器。所以伺服器啟動過程仍然與上述過程相同,listen後等待客戶端的連線。連線後開始進行HTTP協議的通訊,通過請求與響應來建立Web訪問。
HTTP的請求
HTTP請求由客戶端發給伺服器端,分三部分組成,分別是:請求行、訊息報頭、請求正文。
請求方法(所有方法全為大寫)有多種,各個方法的解釋如下:
GET 請求獲取Request-URI所標識的資源
POST 在Request-URI所標識的資源後附加新的資料
HEAD 請求獲取由Request-URI所標識的資源的響應訊息報頭
PUT 請求伺服器儲存一個資源,並用Request-URI作為其標識
DELETE 請求伺服器刪除Request-URI所標識的資源
TRACE 請求伺服器回送收到的請求資訊,主要用於測試或診斷
CONNECT 保留將來使用
OPTIONS 請求查詢伺服器的效能,或者查詢與資源相關的選項和需求
專案裡暫時實現了GET/HEAD/OPTION的處理,有興趣的可以根據這三個處理函式的示例實現其他的。
HTTP的響應
在接收和解釋請求訊息後,伺服器返回一個HTTP響應訊息。HTTP響應也是由三個部分組成,分別是:狀態行、訊息報頭、響應正文。
狀態行格式:HTTP-Version Status-Code Reason-Phrase CRLF,其中,HTTP-Version表示伺服器HTTP協議的版本;Status-Code表示伺服器發回的響應狀態程式碼;Reason-Phrase表示狀態程式碼的文字描述。
狀態程式碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:
1xx:指示資訊--表示請求已接收,繼續處理
2xx:成功--表示請求已被成功接收、理解、接受
3xx:重定向--要完成請求必須進行更進一步的操作
4xx:客戶端錯誤--請求有語法錯誤或請求無法實現
5xx:伺服器端錯誤--伺服器未能實現合法的請求
當客戶端與Web伺服器建立連線後就會通過傳送請求接收伺服器響應來進行通訊。而伺服器端如果要處理多個客戶端的請求,可以通過epoll或kqueue等IO機制來獲得讀寫的訊息通知,本專案採用kqueue實現。
3.4 kqueue 機制
kqueue與epoll非常相似,最初是2000年Jonathan Lemon在FreeBSD系統上開發的一個高效能的事件通知介面。註冊一批socket描述符到kqueue以後,當其中的描述符狀態發生變化時,kqueue將一次性通知應用程式哪些描述符可讀、可寫或出錯了。
如果你已經理解了聊天室專案中介紹的epoll,那kqueue就很好理解,kqueue提供kqueue()、kevent()兩個系統呼叫和struct kevent結構。
kqueue機制詳細介紹,推薦大家閱讀下面的理論文章,雖然講的是FreeBSD上的開發但在Linux上完全適用:
kqueue的介面包括 kqueue()、kevent()兩個系統呼叫和struct kevent結構:
kqueue() 生成一個核心事件佇列,返回該佇列的檔案描述索。其它 API通過該描述符操作這個kqueue。
kevent() 提供向核心註冊 /反註冊事件和返回就緒事件或錯誤事件。
struct kevent 就是kevent()操作的最基本的事件結構。
struct kevent {
uintptr_t ident; /* 事件 ID */
short filter; /* 事件過濾器 */
u_short flags; /* 行為標識 */
u_int fflags; /* 過濾器標識值 */
intptr_t data; /* 過濾器資料 */
void *udata; /* 應用透傳資料 */
};
專案中我們會用到的內容包括事件過濾器,可以將 kqueue filter 看作事件。核心檢測 ident上註冊的filter的狀態,狀態發生了變化,就通知應用程式。kqueue定義了較多的filter,本文只介紹Socket讀寫相關的filter:
EVFILT_READ:TCP監聽socket,如果在完成的連線佇列(已收三次握手最後一個ACK)中有資料,此事件將被通知。收到該通知的應用一般呼叫accept(),且可通過data獲得完成佇列的節點個數。 流或資料包socket,當協議棧的socket層接收緩衝區有資料時,該事件會被通知,並且data被設定成可讀資料的位元組數。
EVFILT_WRIT:當 socket層的寫入緩衝區可寫入時,該事件將被通知;data指示目前緩衝區有多少位元組空閒空間。
行為標誌flags:
EV_ADD:指示加入事件到 kqueue
EV_DELETE:指示將傳入的事件從 kqueue中移除
過濾器標識值:
EV_ENABLE:過濾器事件可用,註冊一個事件時,預設是可用的。
EV_DISABLE:過濾器事件不可用,當內部描述可讀或可寫時,將不通知應用程式。
這些引數都會在Web伺服器實現中使用。當socket有連線或讀寫資料事件時,伺服器讀取客戶端請求並將響應寫回到客戶端。kqueue充當的是事件觸發的中間層。
為了我們可以使用kqueue,首先要在系統中安裝libkqueue:
sudo apt-get update
sudo apt-get install libkqueue-dev
編譯連結時也要在g++後新增-lkqueue才可以正常連結。
3.5 程式設計
根據上面的需求分析,設計所需的類。
首先需要一個HTTPServer類,用來提供伺服器的啟動關閉主迴圈等支援。HTTPServer物件在程式中唯一。
其次需要Client客戶端類,這個類用來儲存客戶端的必要資訊。HTTPServer物件中需要包含多個當前連線的Client物件。
再次,對於Client和HTTPServer之間的通訊內容需要設定HTTPRequest和HTTPResponse類,而這兩個類具備很多通用的資訊,設計二者的基類為HTTPMessage類。
最後HTTPServer需要能夠返回伺服器資源給客戶端,因此需要資源類Resource(檔案,圖片等檔案)以及包含資源的資源池ResourceHost。這裡可以為不同的虛擬主機設定不同的ResourceHost。
上面的幾個類是Web伺服器構成的主體,其中HTTPRequest,HTTPResponse,HTTPMessage三個類都是用來儲存訊息, 而客戶端與伺服器之間的訊息都是以佇列的方式進行讀取和寫入的,因此在HTTPMessage的上一層,我們新增一個新的基類ByteBuffer來實現 基礎位元組流的儲存和讀寫處理。
綜上專案中實現下面的類:
ByteBuffer:定義一個快取佇列用來儲存資料
HTTPMessage:繼承ByteBuffer,增加HTTP協議特有的資訊。
HTTPRequest:HTTP請求類,繼承HTTPMessage
HTTPResponse:HTTP響應類,繼承HTTPMessage
Resource:資源類,HTTP響應訊息中的檔案資源資料
ResourceHost:資源池,用來將伺服器上的檔案載入到Resource物件
Client:客戶端類,用來儲存客戶端的必要資訊
HTTPServer:伺服器類,提供伺服器的啟動關閉主迴圈等支援
3.6 程式碼結構
根據上述細化需求,我們先建立必要的程式檔案。
首先為要實現的程式命名為webserver:
# 進入到程式碼庫自動生成的目錄# 為了方便git提交程式碼請先開通程式碼庫
cd /home/shiyanlou/Code/shiyanlou_cs454
# 建立程式碼目錄mkdir webserver
cd webserver
# 為每個需要實現的類建立一個.h和一個.cpp檔案# 並將原始檔都放在src/目錄下mkdir src# touch src/XXX.h src/XXX.cpp# 建立Makefile
touch Makefile
# 建立示例目錄,該目錄可以作為Web伺服器的資源池mkdir bin/mkdir bin/htdoc
touch bin/htdoc/test.html
本專案參考程式碼基於https://github.com/RamseyK/httpserver進行實現。每個標頭檔案中的函式都進行了標註,核心模組HTTPServer.cpp也會進行詳細介紹。其他cpp檔案由於內容相對簡單,直接使用原作者的英文註釋,如果有任何不清楚的問題可以隨時在實驗樓問答提問。
下面我們將開始實現需要的類,建議大家按照以下步驟先自己實現,最後再對專案參考程式碼進行對照學習。
3.7 ByteBuffer
ByteBuffer 定義一個快取佇列用來儲存HTTP訪問中的請求及返回資料,這個快取佇列的作用相當於CPU和硬碟之間的記憶體,用來存放Web伺服器和客戶端之間通訊的資料,需要具備下面的功能:
從佇列頭部讀取資料
向佇列尾部寫入新的資料
當佇列儲存區域滿的時候可以自動擴充
支援隨機讀取和寫入多種資料型別到指定位置(使用模板實現)
支援清空,克隆,對比,擴容
支援遍歷佇列查詢指定的資料
ByteBuffer的核心介面如下:
class ByteBuffer {
// 讀寫位置索引
unsigned int rpos, wpos;
// 快取內容使用容器儲存
std::vector<byte> buf;
// 佇列中剩餘的元素數量
unsigned int bytesRemaining();
// 清空佇列並重置讀寫索引
void clear();
// 拷貝當前ByteBuffer物件
ByteBuffer* clone();
// 對比兩個ByteBuffer物件
bool equals(ByteBuffer* other);
// 設定儲存空間大小
void resize(unsigned int newSize);
// 返回儲存空間大小
unsigned int size();
// 讀取佇列頭部的資料但不移動rpos
byte peek();
// 讀取佇列頭部的資料同時移動rpos
byte get();
// 讀取指定位置的資料
byte get(unsigned int index);
// 讀取佇列頭部指定長度的資料到buf中
void getBytes(byte* buf, unsigned int len);
// 讀取指定資料型別的元素:char,double,float,int,long,short
char getChar();
char getChar(unsigned int index);
// 將src寫入佇列
void put(ByteBuffer* src);
// 將b寫入佇列
void put(byte b);
// 將b寫入佇列指定位置
void put(byte b, unsigned int index);
// 將長度為len的快取b寫入佇列頭部
void putBytes(byte* b, unsigned int len);
// 將長度為len的快取b寫入佇列指定位置
void putBytes(byte* b, unsigned int len, unsigned int index);
// 寫入指定資料型別的元素:char,double,float,int,long,short
void putChar(char value);
void putChar(char value, unsigned int index);
}
看上去非常繁瑣,但可以使用模板實現基礎函式,大部分函式都能夠通過呼叫函式模板的方式簡化實現內容。
3.8 HTTPMessage
繼承ByteBuffer實現HTTPMessage類。這個類需要包含:
HTTP頭部
HTTP版本號
訊息資料:包括ByteBuffer中的資料及額外的資料(響應訊息中返回的資源及請求訊息中額外的引數)
HTTP訊息解析函式,根據HTTP協議的規範對訊息頭部和內容進行解析
HTTP訊息建立函式,建立符合HTTP協議要求的HTTP訊息
HTTP頭部管理函式,新增HTTP頭部資訊
這個類比較簡單,只需要把上述幾個函式依次實現,核心函式包括:
class HTTPMessage : public ByteBuffer {private:
// 訊息頭部資訊
std::map<std::string, std::string> *headers;
protected:
// HTTP版本號
std::string version;
// 訊息資料
// 響應訊息中表示資源,請求訊息中表示額外的引數
byte* data;
public:
// 建立訊息
virtual byte* create() = 0;
// 解析訊息
virtual bool parse() = 0;
};
3.9 HTTPRequest
HTTP請求類繼承HTTPMessage,需要實現以下資料項及函式功能:
請求的方法:GET/HEAD/OPTION等
請求的URI
實現HTTPMessage的建立和解析虛擬函式
核心成員宣告:
// HTTPRequest:HTTP請求訊息,從客戶端發給伺服器的訊息class HTTPRequest : public HTTPMessage {private:
// 請求的方法型別
int method;
// 請求的URL
std::string requestUri;
protected:
// 初始化訊息
virtual void init();
public:
virtual byte *create();
virtual bool parse();
// 輔助函式
// 方法字串與數字相互轉換
int methodStrToInt(std::string name);
std::string methodIntToStr(unsigned int mid);
};
3.10 HTTPResponse
HTTP響應類繼承HTTPMessage,需要實現以下資料項及函式功能:
響應狀態碼和資訊
實現HTTPMessage的建立和解析虛擬函式
核心成員宣告:
// HTTPResponse:HTTP響應訊息,從伺服器發給客戶端的訊息class HTTPResponse : public HTTPMessage {private:
// 響應狀態碼和資訊
int status;
std::string reason;
protected:
virtual void init();
public:
virtual byte* create();
virtual bool parse();
};
3.11 Resource
HTTP響應訊息中的Resource除了包含檔案資料以外,最重要的特點是具備mimeType標識,MIME (Multipurpose Internet Mail Extensions) 是描述訊息內容型別的因特網標準。MIME訊息能包含文字、影象、音訊、視訊以及其他應用程式專用的資料。
Resource類中需要的成員:
資源資料
資料大小
資料MIME型別
資源在伺服器上儲存的路徑
核心成員宣告如下:
// Resource:HTTP響應訊息中的檔案資源資料class Resource {
private:
// 檔案資源
byte* data;
// 資源大小
unsigned int size;
// 資源型別
std::string mimeType;
// 資源在伺服器上儲存的路徑
std::string location;
// 是否是目錄型別
bool directory;
};
3.11 ResourceHost
ResourceHost是一個Resource的管理元件,可以通過一個目錄來構建。需要支援的成員包括:
基礎目錄,其他的訪問路徑都是該目錄下的相對路徑
根據訪問地址URI獲取Resource資源物件
將資源寫入到檔案系統,用來支援上傳操作
Resource中的MimeType的獲取可以通過判斷副檔名來實現,需要構建副檔名和MimeType的對映表。
我們需要定義一個資料結構包含所有的MIME資訊,示例程式碼中採用巨集的方式從檔案MimeTypes.inc中讀取:
// 構建副檔名與MimeType對應的字典,從檔案MimeTypes.inc中獲取
std::unordered_map<std::string, std::string> mimeMap = {
#define STR_PAIR(K,V) std::pair<std::string, std::string>(K,V)
#include "MimeTypes.inc"
};
MimeTypes.inc中的內容是若干行:
STR_PAIR("bmp", "image/bmp"),
STR_PAIR("book", "application/book"),
...
核心成員宣告如下:
// ResourceHost:資源池,用來將伺服器上的檔案載入到Resource物件class ResourceHost {private:
// 基礎目錄,其他的訪問路徑都是該目錄下的相對路徑
std::string baseDiskPath;
// 構建副檔名與MimeType對應的字典,從檔案MimeTypes.inc中獲取
std::unordered_map<std::string, std::string> mimeMap = {
#define STR_PAIR(K,V) std::pair<std::string, std::string>(K,V)
#include "MimeTypes.inc"
};
public:
// 將資源寫入到檔案系統
void putResource(Resource* res, bool writeToDisk);
// 根據URI獲取Resource資源物件
Resource* getResource(std::string uri);
};
3.12 Client
客戶端的資料會存放在HTTPServer中的一個對映表中,用客戶端連線的socket做key。每個客戶端物件都應該包含客戶端的socket,sockaddr_in地址資訊,資料傳送佇列。
客戶端的操作需要支援新增新的資料到傳送佇列,對傳送佇列進行出隊,清空等操作。
傳送佇列使用std::queue,每個儲存單元需要單獨設計要包含傳送的資料及資料偏移,並要增加是否需要在傳送完成後斷開連線的標誌。傳送儲存單元命名為SendQueueItem類,在Client類宣告中需要使用。
核心成員宣告如下:
// HTTP客戶端// class Client {
// 連線的socket
int socketDesc;
// 地址資訊
sockaddr_in clientAddr;
// 資料傳送佇列
std::queue<SendQueueItem*> sendQueue;
public:
// 傳送佇列操作
// 新增新的資料到傳送佇列
void addToSendQueue(SendQueueItem* item);
// 傳送佇列長度
unsigned int sendQueueSize();
// 獲取傳送佇列中第一個元素
SendQueueItem* nextInSendQueue();
// 出隊操作,刪除第一個元素
void dequeueFromSendQueue();
// 清空傳送佇列
void clearSendQueue();
};
3.13 HTTPServer
這是整個專案的核心,具有最複雜的類和實現過程。
首先分析HTTPServer需要具備的資料成員,剛才已經提到Client客戶端列表是必須的,此外還需要包含:
監聽的埠號
監聽的socket
伺服器地址資訊
kqueue 描述符
kevent 事件佇列
客戶端字典,對映客戶端的socket和客戶端物件
資源主機及檔案系統列表
虛擬主機,對映請求的地址到不同的ResourceHost
根據需求中HTTPServer需要支援下面幾大類功能:
處理客戶端連線
處理客戶端請求
傳送響應訊息給客戶端
伺服器管理
每個功能集合中又可以細分出不同的功能。
處理客戶端連線
接受連線:accept接受連線並將新的socket新增到kqueue中,建立客戶端物件新增到客戶端列表中
獲取客戶端物件:根據socket查詢客戶端列表返回客戶端物件
斷開連線:清理kqueue中的socket,關閉socket,清理客戶端列表中的物件
處理客戶端讀取事件:recv()接收客戶端資料並構造HTTPRequest物件,交給handleRequest()函式處理。
處理客戶端寫入事件:獲取傳送佇列中的SendQueueItem,send()傳送給客戶端
處理客戶端請求
處理請求:對請求方法進行分類分別交給不同的處理函式
處理GET/HEAD/OPTIONS請求:構造HTTPResponse,呼叫sendResponse傳送給客戶端
傳送響應訊息給客戶端
傳送響應狀態碼:構造HTTPResponse,呼叫sendResponse傳送給客戶端
傳送響應訊息:將HTTPResponse封裝後新增到客戶端的傳送佇列
伺服器管理
啟動:包含各種資料和資源的初始化,監聽socket的建立,繫結,kqueue的建立和初始化。
停止:釋放資源,清理客戶端列表,清理kqueue,清理socket
主迴圈:進入事件迴圈,等待kqueue事件觸發,有事件觸發後需要先判斷是新的客戶端連線還是客戶端讀寫事件
核心成員宣告如下:
// HTTPServer:Web伺服器類,提供伺服器的建立,啟動,停止等管理操作class HTTPServer {
// 監聽的埠號
int listenPort;
// 監聽的socket
int listenSocket;
// 伺服器地址資訊
struct sockaddr_in serverAddr;
// kqueue 描述符
int kqfd;
// kevent佇列
struct kevent evList[QUEUE_SIZE];
// 客戶端字典,對映客戶端的socket和客戶端物件
std::unordered_map<int, Client*> clientMap;
// 資源主機及檔案系統列表
std::vector<ResourceHost*> hostList;
// 虛擬主機,對映請求的地址到不同的ResourceHost
std::unordered_map<std::string, ResourceHost*> vhosts;
// 處理客戶端連線
void acceptConnection();
void disconnectClient(Client* cl, bool mapErase = true);
void readClient(Client* cl, int data_len);
bool writeClient(Client* cl, int avail_bytes);
// 處理客戶端請求
void handleRequest(Client* cl, HTTPRequest* req);
void handleGet(Client* cl, HTTPRequest* req, ResourceHost* resHost);
void handleOptions(Client* cl, HTTPRequest* req);
// 傳送響應訊息給客戶端
void sendStatusResponse(Client* cl, int status, std::string msg = "");
void sendResponse(Client* cl, HTTPResponse* resp, bool disconnect);
// 啟動及停止Web伺服器
bool start(int port);
void stop();
// Web伺服器主迴圈
void process();
};
當HTTPServer啟動時我們預設設定當前路徑下的htdoc資料夾為ResourceHost,並新增到HTTPServer物件中。而htdoc下我們新增一個test.html檔案,作為可以訪問的Resource,test.html中的內容可以很簡單,例如:
<html><head><title>shiyanlou demo site</title></head>
<body>
Hello Shiyanlou!<br /></body></html>
這個示例頁面可以在後面的測試中通過訪問localhost:8080/test.html訪問到。
3.14 主函式
主函式中實現包括兩部分內容:
註冊各種訊號處理函式,能夠讓Web服務中止時可正常釋放資源
建立HTTPServer物件,依次啟動,進入主迴圈
需要處理的包括中斷訊號SIGABRT,SIGINT,SIGTERM,這些訊號發生時要將HTTPServer的停止標誌置為True,下次迴圈時就可以退出。SIGPIPE訊號需要被忽略,避免"Broken pipe"出現。
示例程式碼片段:
// 忽視 SIGPIPE "Broken pipe" 訊號
signal(SIGPIPE, handleSigPipe);
// 註冊中斷處理
signal(SIGABRT, &handleTermSig);
signal(SIGINT, &handleTermSig);
signal(SIGTERM, &handleTermSig);
// 建立並啟動HTTPServer例項
svr = new HTTPServer();
svr->start(8080);
// 進入主迴圈
svr->process();
// 停止伺服器
svr->stop();
delete svr;
3. 編譯及執行
將你完成的檔案儲存為/home/shiyanlou/Code/shiyanlou_cs454/webserver,在同樣的目錄下我們編輯Makefile檔案:
cd /home/shiyanlou/Code/shiyanlou_cs454/webserver
vim Makefile
Makefile檔案裡的內容:
CC := g++SRCDIR := srcBINDIR := binBUILDDIR := buildTARGET := httpserverUNAME := $(shell uname)
# Debug FlagsDEBUGFLAGS := -g3 -O0 -WallRTCHECKS := -fmudflap -fstack-check -gnato
# Production FlagsPRODFLAGS := -Wall -O2
CFLAGS := -std=c++11 -Iinclude/ $(DEBUGFLAGS)LINK := -lpthread -lkqueue $(DEBUGFLAGS)
SRCEXT := cppSOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.o))
$(TARGET): $(OBJECTS)
@echo " Linking..."$(LINK); $(CC) $^ -o $(BINDIR)/$(TARGET) $(LINK)
$(BUILDDIR)/%.o: $(SRCDIR)/%.$(SRCEXT)
@mkdir -p $(BUILDDIR)
@echo " CC $<"; $(CC) $(CFLAGS) -c -o $@ $<
clean:
@echo " Cleaning..."; rm -r $(BUILDDIR) $(BINDIR)/$(TARGET)*
.PHONY: clean
Makefile內容很多,大部分都是變數定義,核心內容只有下面兩行:
@echo " CC $<"; $(CC) $(CFLAGS) -c -o $@ $< 編譯每個CPP檔案,生成.o目標檔案
$(CC) $^ -o $(BINDIR)/$(TARGET) $(LINK) 連結上一步驟生成的所有目標檔案,得到可執行的httpserver檔案
儲存Makefile後,我們只需要在目錄下執行make就可以生成可執行檔案httpserver。
編譯過程截圖:
Makefile會把可執行檔案放到了bin/目錄下,因為bin/目錄下的htdoc/已經寫在程式碼中作為預設ResourceHost了,所以測試啟動後的Web伺服器可以訪問htdoc下的檔案。
現在進入執行測試階段,首先啟動服務端:
cd bin/
./httpserver
執行截圖如下:
如果中間有任何問題,需要根據輸出的錯誤資訊查驗下程式碼是否有BUG。歡迎隨時到實驗樓問答提問。
3.8 完整程式碼參考
我們提供本專案完整的程式碼及詳細註釋供參考,由於程式碼比較多,文件中不再貼上,建議大家下載檢視。
# 下載程式程式碼wget http://labfile.oss.aliyuncs.com/courses/454/webserver.zip
# 解壓程式碼
unzip webserver.zip
# 進入程式碼資料夾檢視
cd webserver
本專案參考程式碼基於https://github.com/RamseyK/httpserver修改。程式碼License仍然遵循Apache License, Version 2.0。
四、專案擴充套件
本實驗實現了一個Web伺服器程式。基於本課節學習,大家可以在此程式碼基礎上實現擴充套件:
支援更多的HTTP請求方法,例如POST
支援配置檔案,比如配置多個虛擬主機,資源池,埠號等
支援PHP頁面解析,可以加入單獨的模組
五、小結
通過本節實驗的學習,我們開發了Web伺服器程式,學習了C++語言的基本語法及物件導向開發,網路程式開發,HTTP協議及kqueue IO複用機制。
完成專案後可以公開你的實驗報告,並點選實驗報告下方的分享按鈕分享到微博,將會獲得教師點評,同時優秀的實驗報告官微將轉發推薦!
再次提醒,任何問題歡迎到實驗樓問答中提問,老師會及時回答你的困惑。
特別說明:
實驗作業與學習心得請寫在下方“實驗報告”(支援markdown格式)裡並公開,每週選取優秀報告獎勵IT書籍!
您已經完成本課程所有實驗
相關文章
- 跟我學 “Linux” 小程式 Web 版開發(二):UI 開發LinuxWebUI
- Linux伺服器部署Web版VSCode,在window下使用瀏覽器在linux環境下編寫程式碼Linux伺服器WebVSCode瀏覽器
- linux下搭建wails開發環境。LinuxAI開發環境
- Python web伺服器3: 靜態伺服器&併發web伺服器PythonWeb伺服器
- 10分鐘學會用nodejs開發Web伺服器NodeJSWeb伺服器
- vscode 配合wsl做linux下的開發VSCodeLinux
- Web伺服器小專案(Linux / C / epoll)Web伺服器Linux
- 面向Web開發人員的Linux實用入門WebLinux
- 向web伺服器下載檔案Web伺服器
- 跟我學 “Linux” 小程式 Web 版開發(一):初始化LinuxWeb
- linux(統信)下搭建electron開發環境Linux開發環境
- Linux下搭建FFmpeg開發除錯環境Linux除錯
- Linux下搭建Go語言開發環境LinuxGo開發環境
- Linux下簡單部署伺服器Linux伺服器
- Linux下搭建FTP伺服器教程LinuxFTP伺服器
- web伺服器靜態資源下載Web伺服器
- SpringBoot Web開發Spring BootWeb
- Solon Web 開發Web
- Python Web開發PythonWeb
- 《Linux C/C++伺服器開發實踐》簡介LinuxC++伺服器
- Web常用的伺服器軟體整理(Win+Linux)Web伺服器Linux
- Linux伺服器下Java環境搭建Linux伺服器Java
- Linux下使用Nginx做CDN伺服器下的配置LinuxNginx伺服器
- web 開發(6)- VueWebVue
- Web開發學習Web
- Flutter Web 開發部署FlutterWeb
- Web前端開發概述Web前端
- 《大話WEB開發》Web
- Dart web開發(1)DartWeb
- Python的web開發PythonWeb
- 開發Web應用Web
- 用於Web開發的本地伺服器環境的MAMP Pro for MacWeb伺服器Mac
- 學python可以做Web開發嗎?python適合Web開發嗎?PythonWeb
- linux 開發離線環境下載及安裝Linux
- Windows 下如何使用 Linux 環境開發 PHP 專案!WindowsLinuxPHP
- Python 開發環境搭建(03):Linux 下 eclipse 安裝Python開發環境LinuxEclipse
- Linux系統下安裝Apache伺服器LinuxApache伺服器
- 基於 Koa + Vue3!一個開源的 Linux 伺服器 Web SSH 皮膚工具!VueLinux伺服器Web
- 跟我學 “Linux” 小程式 Web 版開發(四):引入統計及 Crash 收集LinuxWeb