前面我們實現了TCP服務器和客戶端的簡單應用,接下來我們實現一個基於TCP協議的應用協議,那就是HTTP超文本傳輸協議
1、 HTTP協議簡介
超文本傳輸協議(Hyper Text Transfer Protocol),簡稱HTTP,是一種基於TCP的應用層協議,也是目前為止最為流行的應用層協議之一,可以說HTTP協議是萬維網的基石。
HTTP是一種客戶端請求、服務器應答式的應用層傳輸協議,也就是說服務器端是不可能主動向客戶端發送數據的。在網絡正常的情況下請求和響應都是一一對應的。而這個請求和響應也就是后端開發人員經常看到的Request和Response。
首先,我們來看客戶器端的請求,HTTP請求報文由請求行、請求頭、空白行以及請求體組成。其報文格式如下:
我們來說一說請求行,它由請求方法字段、URL字段和HTTP協議版本字段3個字段組成,它們用空格分隔。需要理解的是請求方法,HTTP協議的請求方法有GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT幾種。先對常用的幾種說明如下:
- GET方法,意思是獲取URL指定的資源,這個請求方式是最簡單的也是最常用的。使用GET 方法時,可以將請求參數和對應的值附加在 URI 后面,利用一個問號(“?”)將資源的URI和請求參數隔開,參數之間使用與符號(“&”)隔開,因此傳遞參數長度也受到了限制,而且與隱私相關的信息也直接暴露在URI中。比如/index.jsp?username=holmofy&password=123123
- HEAD方法,與GET用法相同,但沒有響應體,使用場合沒有GET多。比如下載前使用HEAD發送請求,通過ContentLength響應字段,來了解網絡資源的大小;或者通過LastModified響應字段來判斷本地緩存資源是否要更新。
- POST方法,一般用提交信息或數據,請求服務器進行處理(例如提交表單或者上傳文件)。表單使用POST相對GET來說還是比較隱秘的,而且GET的URL有長度限制,而上傳大文件就必須要使用POST了。
- OPTIONS方法,該方法用於請求服務器告知其支持哪些其他的功能和方法。通過OPTIONS 方法,可以詢問服務器具體支持哪些方法,或者服務器會使用什么樣的方法來處理一些特殊資源。可以說這是一個探測性的方法,客戶端通過該方法可以在不訪問服務器上實際資源的情況下就知道處理該資源的最優方式。這個選項在跨域HTTP請求的情況出現的比較多,這里有一篇關於跨域請求的文章,其中有一張圖很好的解釋了什么是跨域HTTP請求。
客戶端發出HTTP請求,服務端接收后,會向客戶端發送響應信息。所以接下來,我們來看看服務器端的響應報文。HTTP響應報文由響應行、響應頭、空白行以及響應體組成。其報文格式如下:
在響應報文中,非常重要的就是響應行,其中響應行中最重要的就是HTTP的狀態碼。HTTP協議中狀態碼有三位數字組成,第一位數字定義了響應的類別,有以下五種:
- 1XX:信息提示。表示請求已被服務器接受,但需要繼續處理,范圍為100~101。
- 2XX:請求成功。服務器成功處理了請求。范圍為200~206。
- 3XX:客戶端重定向。重定向狀態碼用於告訴客戶端瀏覽器,它們訪問的資源已被移動,並告訴客戶端新的資源位置。客戶端收到重定向會重新對新資源發起請求。范圍為300~305。
- 4XX:客戶端信息錯誤。客戶端可能發送了服務器無法處理的東西,比如請求的格式錯誤,或者請求了一個不存在的資源。范圍為400~415。
- 5XX:服務器出錯。客戶端發送了有效的請求,但是服務器自身出現錯誤,比如Web程序運行出錯。范圍是500~505。
我們開發過程有一些狀態碼比較常見,我們對其簡單說明如下:
2、 HTTP服務器端的設計
我們已經對基於RAW API的TCP應用有了了解。我們在實現TCP服務器的實驗時就提到過對於更復雜的應用和應用層協議只是在功能上的差別,從實現的結構及流程來說是完全一致的。所以對於實現HTTP服務器需要使用到的函數及整個操作流程我們就不再敘述了。重點說一說不同的地方。
首先HTTP服務器是基於TCP的,所以其我們先將其當作TCP服務器來實現。需要注意的是,HTTP協議有其專門的操作端口:80。所以我們設計服務器時需要使用這個端口。
在這里,我們設計一個簡單的HTTP服務器,當客戶端連接到服務器之后,如果收到的是html請求,則返回一個我們預先設定好的網頁。正常返回這個網頁,HTTP的功能就完成了,HTTP服務器會主動斷開與客戶端的連接。
3、 TTP服務器實現
既然是基於TCP的HTTP服務器,我們佷顯然依然按照TCP服務器的結構來實現。我們依然將其划分為三個部分來實現。首先要實現的是HTTP服務器的初始化。
1 /* HTTP服務器初始化配置*/ 2 void Http_Server_Initialization(void) 3 { 4 struct tcp_pcb *pcb = NULL; 5 6 /* 生成一個新的TCP控制塊 */ 7 pcb = tcp_new(); 8 9 /* 控制塊綁定到本地IP和對應端口 */ 10 tcp_bind(pcb, IP_ADDR_ANY, TCP_HTTP_SERVER_PORT); 11 12 /* 服務器進入偵聽狀態 */ 13 pcb = tcp_listen(pcb); 14 15 /* 注冊服務器accept回調函數 */ 16 tcp_accept(pcb, HttpServerAccept); 17 18 }
從上面的代碼不難看出,與TCP服務器的初始化一樣:建立控制塊,為控制塊綁定本地IP和端口,服務器監聽控制塊同時注冊接收處理回調函數。所以接下來就是實現接收處理回調函數。
1 /* HTTP接收回調函數,客戶端建立連接后,本函數被調用 */ 2 static err_t HttpServerAccept(void *arg, struct tcp_pcb *pcb, err_t err) 3 { 4 /*注冊HTTP服務器回調函數*/ 5 tcp_recv(pcb, HttpServerCallback); 6 7 return ERR_OK; 8 }
客戶端連接成功后就會調用接收處理回調函數。該函數為tcp_accept_fn類型,注冊到了監聽控制塊的accept字段。在這個函數中,我們需要注冊HTTP服務器處理函數。其功能就由這個函數決定。
1 /* HTTP服務器信息處理回調函數 */ 2 static err_t HttpServerCallback(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) 3 { 4 char *data = NULL; 5 6 if (p != NULL) 7 { 8 /* 更新接收窗口 */ 9 tcp_recved(pcb, p->tot_len); 10 data = p->payload; 11 12 /* 如果是http請求,返回html信息,否則無響應 */ 13 if(p->len >=3 && data[0] == 'G'&& data[1] == 'E'&& data[2] == 'T') 14 { 15 tcp_write(pcb, htmlMessage, sizeof(htmlMessage), 1); 16 } 17 else 18 { 19 20 } 21 pbuf_free(p); 22 tcp_close(pcb); 23 } 24 else if (err == ERR_OK) 25 { 26 return tcp_close(pcb); 27 } 28 return ERR_OK; 29 }
這個HTTP服務器非常簡單,我們只是實現了GET方法。也就是說,收到客戶端的html請求后,我們檢測其要求,如果是GET方法,我們就返回預先設定好的網頁,否則無返回。然后關閉這一連接。如果我們想要實現更復雜的功能,或者需要支持HTTP協議的其他方法,只需要擴展這個函數就可以了。
4、 結論
HTTP協議是一種使用非常廣泛的協議,其基於TCP基礎上運行,所以在我們前面已經實現TCP服務器及客戶端的情況下,開發HTTP服務器應用就顯得簡單了。在這一篇我們基於LwIP實現了一個簡單的HTTP服務器應用,我們並對其進行了簡單的測試,雖然我們只是實現了GET方法,但經測試設計是正確的。如果需要設計其他方法的HTTP應用只需在此基礎上添加即可。
歡迎關注: