前面我們實(shí)現(xiàn)了TCP服務(wù)器和客戶端的簡(jiǎn)單應(yīng)用,接下來(lái)我們實(shí)現(xiàn)一個(gè)基于TCP協(xié)議的應(yīng)用協(xié)議,那就是HTTP超文本傳輸協(xié)議
1 、 HTTP****協(xié)議簡(jiǎn)介
超文本傳輸協(xié)議(Hyper Text Transfer Protocol),簡(jiǎn)稱HTTP,是一種基于TCP的應(yīng)用層協(xié)議,也是目前為止最為流行的應(yīng)用層協(xié)議之一,可以說(shuō)HTTP協(xié)議是萬(wàn)維網(wǎng)的基石。
HTTP是一種客戶端請(qǐng)求、服務(wù)器應(yīng)答式的應(yīng)用層傳輸協(xié)議,也就是說(shuō)服務(wù)器端是不可能主動(dòng)向客戶端發(fā)送數(shù)據(jù)的。在網(wǎng)絡(luò)正常的情況下請(qǐng)求和響應(yīng)都是一一對(duì)應(yīng)的。而這個(gè)請(qǐng)求和響應(yīng)也就是后端開(kāi)發(fā)人員經(jīng)??吹降腞equest和Response。
首先,我們來(lái)看客戶器端的請(qǐng)求,HTTP請(qǐng)求報(bào)文由請(qǐng)求行、請(qǐng)求頭、空白行以及請(qǐng)求體組成。其報(bào)文格式如下:
我們來(lái)說(shuō)一說(shuō)請(qǐng)求行,它由請(qǐng)求方法字段、URL字段和HTTP協(xié)議版本字段3個(gè)字段組成,它們用空格分隔。需要理解的是請(qǐng)求方法,HTTP協(xié)議的請(qǐng)求方法有GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT幾種。先對(duì)常用的幾種說(shuō)明如下:
- GET****方法 ,意思是獲取URL指定的資源,這個(gè)請(qǐng)求方式是最簡(jiǎn)單的也是最常用的。使用GET 方法時(shí),可以將請(qǐng)求參數(shù)和對(duì)應(yīng)的值附加在 URI 后面,利用一個(gè)問(wèn)號(hào)(“?”)將資源的URI和請(qǐng)求參數(shù)隔開(kāi),參數(shù)之間使用與符號(hào)(“&”)隔開(kāi),因此傳遞參數(shù)長(zhǎng)度也受到了限制,而且與隱私相關(guān)的信息也直接暴露在URI中。比如/index.jsp?username=holmofy&password=123123
- HEAD****方法 ,與GET用法相同,但沒(méi)有響應(yīng)體,使用場(chǎng)合沒(méi)有GET多。比如下載前使用HEAD發(fā)送請(qǐng)求,通過(guò)ContentLength響應(yīng)字段,來(lái)了解網(wǎng)絡(luò)資源的大?。换蛘咄ㄟ^(guò)LastModified響應(yīng)字段來(lái)判斷本地緩存資源是否要更新。
- POST****方法 ,一般用提交信息或數(shù)據(jù),請(qǐng)求服務(wù)器進(jìn)行處理(例如提交表單或者上傳文件)。表單使用POST相對(duì)GET來(lái)說(shuō)還是比較隱秘的,而且GET的URL有長(zhǎng)度限制,而上傳大文件就必須要使用POST了。
- OPTIONS****方法 ,該方法用于請(qǐng)求服務(wù)器告知其支持哪些其他的功能和方法。通過(guò)OPTIONS 方法,可以詢問(wèn)服務(wù)器具體支持哪些方法,或者服務(wù)器會(huì)使用什么樣的方法來(lái)處理一些特殊資源??梢哉f(shuō)這是一個(gè)探測(cè)性的方法,客戶端通過(guò)該方法可以在不訪問(wèn)服務(wù)器上實(shí)際資源的情況下就知道處理該資源的最優(yōu)方式。這個(gè)選項(xiàng)在跨域HTTP請(qǐng)求的情況出現(xiàn)的比較多,這里有一片關(guān)于跨域請(qǐng)求的文章,其中有一張圖很好的解釋了什么是跨域HTTP請(qǐng)求。
客戶端發(fā)出HTTP請(qǐng)求,服務(wù)端接收后,會(huì)向客戶端發(fā)送響應(yīng)信息。所以接下來(lái),我們來(lái)看看服務(wù)器端的響應(yīng)報(bào)文。HTTP響應(yīng)報(bào)文由響應(yīng)行、響應(yīng)頭、空白行以及響應(yīng)體組成。其報(bào)文格式如下:
在響應(yīng)報(bào)文中,非常重要的就是響應(yīng)行,其中響應(yīng)行中最重要的就是HTTP的狀態(tài)碼。HTTP協(xié)議中狀態(tài)碼有三位數(shù)字組成,第一位數(shù)字定義了響應(yīng)的類別,有以下五種:
- 1XX :信息提示。表示請(qǐng)求已被服務(wù)器接受,但需要繼續(xù)處理,范圍為100~101。
- 2XX :請(qǐng)求成功。服務(wù)器成功處理了請(qǐng)求。范圍為200~206。
- 3XX :客戶端重定向。重定向狀態(tài)碼用于告訴客戶端瀏覽器,它們?cè)L問(wèn)的資源已被移動(dòng),并告訴客戶端新的資源位置??蛻舳耸盏街囟ㄏ驎?huì)重新對(duì)新資源發(fā)起請(qǐng)求。范圍為300~305。
- 4XX :客戶端信息錯(cuò)誤??蛻舳丝赡馨l(fā)送了服務(wù)器無(wú)法處理的東西,比如請(qǐng)求的格式錯(cuò)誤,或者請(qǐng)求了一個(gè)不存在的資源。范圍為400~415。
- 5XX :服務(wù)器出錯(cuò)??蛻舳税l(fā)送了有效的請(qǐng)求,但是服務(wù)器自身出現(xiàn)錯(cuò)誤,比如Web程序運(yùn)行出錯(cuò)。范圍是500~505。
我們開(kāi)發(fā)過(guò)程有一些狀態(tài)碼比較常見(jiàn),我們對(duì)其簡(jiǎn)單說(shuō)明如下:
2 、 HTTP****客戶端設(shè)計(jì)
我們已經(jīng)說(shuō)過(guò)了,HTTP協(xié)議是基于TCP運(yùn)行的,那么佷顯然我們要實(shí)現(xiàn)一個(gè)HTTP客戶端其本質(zhì)上首先是要實(shí)現(xiàn)一個(gè)TCP客戶端。
在實(shí)現(xiàn)TCP客戶端的基礎(chǔ)上,我們要讓這個(gè)客戶端能夠?qū)崿F(xiàn)HTTP協(xié)議的基本操作。所以我們需要為客戶端構(gòu)造請(qǐng)求報(bào)文。關(guān)于請(qǐng)求報(bào)文的格式前面已經(jīng)介紹過(guò)了,我們根據(jù)這個(gè)格式來(lái)構(gòu)造,因?yàn)槲覀冎皇呛?jiǎn)單的一個(gè)HTTP客戶端測(cè)試,所以我們采用GET方法。我們構(gòu)造報(bào)文如下:
"GET https://www.cnblogs.com/foxclever/ HTTP/1.1\\r\\n"
"Host:www.cnblogs.com:80\\r\\n\\r\\n";
對(duì)于HTTP協(xié)議具有專門(mén)的端口號(hào),所以我們采用這個(gè)制定的端口號(hào)來(lái)實(shí)現(xiàn)。而實(shí)現(xiàn)的流程與一般TCP客戶端是一樣的。
3 、 HTTP****客戶端實(shí)現(xiàn)
經(jīng)過(guò)上述的分析以及我們前面實(shí)現(xiàn)TCP客戶端的經(jīng)驗(yàn),實(shí)現(xiàn)HTTP客戶端已經(jīng)沒(méi)有問(wèn)題。與TCP客戶端一般,我們將HTTP客戶端分成4個(gè)函數(shù)來(lái)實(shí)現(xiàn)。首先依然是實(shí)現(xiàn)HTTP客戶端的初始化:
1 /* HTTP客戶端初始化配置*/
2 void Http_Client_Initialization(void)
3 {
4 struct tcp_pcb *tcp_client_pcb;
5 ip_addr_t ipaddr;
6
7 /* 將目標(biāo)服務(wù)器的IP寫(xiě)入一個(gè)結(jié)構(gòu)體,為pc機(jī)本地連接IP地址 */
8 IP4_ADDR(&ipaddr,httpServerIP[0],httpServerIP[1],httpServerIP[2],httpServerIP[3]);
9
10 /* 為tcp客戶端分配一個(gè)tcp_pcb結(jié)構(gòu)體 */
11 tcp_client_pcb = tcp_new();
12
13 /* 綁定本地端號(hào)和IP地址 */
14 tcp_bind(tcp_client_pcb, IP_ADDR_ANY, TCP_HTTP_CLIENT_PORT);
15
16 if (tcp_client_pcb != NULL)
17 {
18 /* 與目標(biāo)服務(wù)器進(jìn)行連接,參數(shù)包括了目標(biāo)端口和目標(biāo)IP */
19 tcp_connect(tcp_client_pcb, &ipaddr, TCP_HTTP_SERVER_PORT, HTTPClientConnected);
20
21 tcp_err(tcp_client_pcb, HTTPClientConnectError);
22 }
23 }
我們很容易發(fā)現(xiàn),上述初始化的代碼其實(shí)就是TCP客戶端的初始化代碼,除了所使用的端口不一樣外,其它都一樣。也是在初始化代碼中實(shí)現(xiàn)了兩個(gè)函數(shù)的注冊(cè):一是使用tcp_connect注冊(cè)連接完成的處理回調(diào)函數(shù);二是使用tcp_err注冊(cè)了連接錯(cuò)誤處理回調(diào)函數(shù)。很明顯接下來(lái)我們需要實(shí)現(xiàn)這兩個(gè)函數(shù)。
連接到服務(wù)器成功后的回調(diào)函數(shù)是tcp_connected_fn類型。在客戶端建立一個(gè)連接后,內(nèi)核會(huì)調(diào)用這個(gè)函數(shù)。在這個(gè)函數(shù)中,客戶端回想服務(wù)器發(fā)送最初的操作請(qǐng)求,并且會(huì)在這個(gè)函數(shù)中注冊(cè)數(shù)據(jù)接收處理回調(diào)函數(shù)。
1 /* HTTP客戶端連接到服務(wù)器回調(diào)函數(shù) */
2 static err_t HTTPClientConnected(void *arg, struct tcp_pcb *pcb, err_t err)
3 {
4 char clientString[]="GET https://www.cnblogs.com/foxclever/ HTTP/1.1\\r\\n"
5
6 "Host:www.cnblogs.com:80\\r\\n\\r\\n";
7
8 /* 配置接收回調(diào)函數(shù) */
9 tcp_recv(pcb, HTTPClientCallback);
10
11 /* 發(fā)送一個(gè)建立連接的問(wèn)候字符串*/
12 tcp_write(pcb,clientString, strlen(clientString),0);
13
14 return ERR_OK;
15 }
這個(gè)代碼也是與普通TCP客戶端一樣,只是為了應(yīng)用于HTTP協(xié)議,我們發(fā)送的請(qǐng)求字符串需要按照HTTP的格式來(lái)設(shè)定。對(duì)HTTP客戶端連接服務(wù)器錯(cuò)誤回調(diào)函數(shù),它是tcp_err_fn類型,在這個(gè)程序中主要完成連接異常結(jié)束時(shí)的一些處理,可以釋放一些必要的資源。在這個(gè)函數(shù)被內(nèi)核調(diào)用時(shí),連接實(shí)際上已經(jīng)斷開(kāi),相關(guān)控制塊也已經(jīng)被刪除。所以在這個(gè)函數(shù)中我們可以重新初始化連接及其資源。在這里我們就是使用它來(lái)重新初始化TCP客戶端。
1 /* HTTP客戶端連接服務(wù)器錯(cuò)誤回調(diào)函數(shù) */
2 static void HTTPClientConnectError(void *arg, err_t err)
3 {
4 /* 重新啟動(dòng)連接 */
5 Http_Client_Initialization();
6 }
最后我們需要實(shí)現(xiàn)的是HTTP客戶端接收到數(shù)據(jù)后的數(shù)據(jù)處理回調(diào)函數(shù)。這個(gè)函數(shù)其實(shí)就是我們前面連接成功時(shí),注冊(cè)過(guò)的HTTP客戶端數(shù)據(jù)接收處理函數(shù)。這個(gè)函數(shù)是tcp_recv_fn類型。這是使用RAW API實(shí)現(xiàn)HTTP客戶端功能最重要的一個(gè)函數(shù),因?yàn)樗鼪Q定HTTP客戶端的具體功能。
1 /* HTTP客戶端接收到數(shù)據(jù)后的數(shù)據(jù)處理回調(diào)函數(shù) */
2 static err_t HTTPClientCallback(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err)
3 {
4 struct pbuf *tcp_send_pbuf;
5 char echoString[]="GET https://www.cnblogs.com/foxclever/ HTTP/1.1\\r\\n"
6
7 "Host:www.cnblogs.com:80\\r\\n\\r\\n";
8
9 if (tcp_recv_pbuf != NULL)
10 {
11 /* 更新接收窗口 */
12 tcp_recved(pcb, tcp_recv_pbuf->tot_len);
13
14 /* 將接收到的服務(wù)器內(nèi)容回顯*/
15 tcp_write(pcb,echoString, strlen(echoString), 1);
16 tcp_send_pbuf = tcp_recv_pbuf;
17 tcp_write(pcb, tcp_send_pbuf->payload, tcp_send_pbuf->len, 1);
18
19 pbuf_free(tcp_recv_pbuf);
20 }
21 else if (err == ERR_OK)
22 {
23 tcp_close(pcb);
24 Http_Client_Initialization();
25
26 return ERR_OK;
27 }
28
29 return ERR_OK;
30 }
同樣,這段代碼也是除了要按HTTP協(xié)議構(gòu)造響應(yīng)信息外,其他部分與普通TCP客戶端類似。
4 、結(jié)論
與前一篇實(shí)現(xiàn)HTTP服務(wù)器是基于TCP服務(wù)器實(shí)現(xiàn)的一樣,這里我們實(shí)現(xiàn)HTTP客戶端是基于TCP客戶端來(lái)實(shí)現(xiàn)的。在我們前面已經(jīng)實(shí)現(xiàn)TCP客戶端的情況下,開(kāi)發(fā)HTTP客戶端應(yīng)用就顯得簡(jiǎn)單了。在這一篇我們基于LwIP實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的HTTP客戶端應(yīng)用,我們并對(duì)其進(jìn)行了簡(jiǎn)單的測(cè)試。再歷程中我們只是實(shí)現(xiàn)了GET方法,但經(jīng)測(cè)試設(shè)計(jì)是正確的。如果需要設(shè)計(jì)其他方法的HTTP應(yīng)用只需在此基礎(chǔ)上添加即可。
-
服務(wù)器
+關(guān)注
關(guān)注
12文章
9596瀏覽量
86986 -
HTTP
+關(guān)注
關(guān)注
0文章
516瀏覽量
32290 -
TCP
+關(guān)注
關(guān)注
8文章
1395瀏覽量
80140 -
客戶端
+關(guān)注
關(guān)注
1文章
296瀏覽量
16940 -
LwIP
+關(guān)注
關(guān)注
2文章
89瀏覽量
27915
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
stm32 +lwip1.3.1客戶端異常導(dǎo)致網(wǎng)絡(luò)ping不通怎么解決?
ucos2中lwip tcp做客戶端
lwip客戶端不能傳輸串口的動(dòng)態(tài)數(shù)據(jù)
如何解決lwip的netconn客戶端發(fā)送問(wèn)題
LWIP作服務(wù)器連接第一個(gè)客戶端后,以后的客戶端就連接不上
請(qǐng)問(wèn)LWIP TCPserver支持多個(gè)客戶端連接嗎?
怎樣去設(shè)計(jì)嵌入式LWIP網(wǎng)絡(luò)客戶端
stm32f107vc lwip tcp客戶端
一個(gè)HTTP協(xié)議客戶端源代碼資料免費(fèi)下載

嵌入式LWIP網(wǎng)絡(luò)客戶端設(shè)計(jì)資料下載

STM32+LWIP服務(wù)器實(shí)現(xiàn)多客戶端連接

適用于Java的Google HTTP客戶端庫(kù)使用教程
基于LwIP的TCP客戶端設(shè)計(jì)

評(píng)論