LwIP应用开发笔记之五:LwIP无操作系统TCP服务器
前面我們實現(xiàn)了UDP服務(wù)器及客戶端以及基于其上的TFTP應(yīng)用服務(wù)器。接下來我們將實現(xiàn)同樣廣泛應(yīng)用的TCP協(xié)議各類應(yīng)用。
1、TCP簡述
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,由IETF的RFC 793定義。在簡化的計算機網(wǎng)絡(luò)OSI模型中,它完成第四層傳輸層所指定的功能,與用戶數(shù)據(jù)報協(xié)議(UDP)是同一層內(nèi)的,另一個重要的傳輸協(xié)議。在因特網(wǎng)協(xié)議族(Internet protocol suite)中,TCP層是位于IP層之上,應(yīng)用層之下的中間層。不同主機的應(yīng)用層之間經(jīng)常需要可靠的、像管道一樣的連接,但是IP層本身不提供這樣的流機制,而是提供不可靠的包交換,恰好TCP協(xié)議不足了這一應(yīng)用需求。
應(yīng)用層向TCP層發(fā)送用于網(wǎng)間傳輸?shù)摹⒂?位字節(jié)表示的數(shù)據(jù)流,然后TCP把數(shù)據(jù)流分區(qū)成適當長度的報文段。之后TCP把結(jié)果包傳給IP層,由它來通過網(wǎng)絡(luò)將包傳送給接收端實體的TCP層。TCP為了保證不發(fā)生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然后接收端實體對已成功收到的包發(fā)回一個相應(yīng)的確認(ACK);如果發(fā)送端實體在合理的往返時延(RTT)內(nèi)未收到確認,那么對應(yīng)的數(shù)據(jù)包就被假設(shè)為已丟失將會被進行重傳。TCP用一個校驗和函數(shù)來檢驗數(shù)據(jù)是否有錯誤;在發(fā)送和接收時都要計算校驗和,以確保數(shù)據(jù)的正確性。TCP協(xié)議的數(shù)據(jù)包結(jié)構(gòu)如下:
TCP數(shù)據(jù)包中各部分的含義如下:
(1)源端口和目標端口
源端口和目標端口各占2個字節(jié)。用來告知主機該報文段是來自哪里以及傳送給哪里。進行 TCP 通訊時,客戶端通常使用系統(tǒng)自動選擇的臨時端口號,而服務(wù)器則根據(jù)應(yīng)用不同使用知名服務(wù)端口號。
(2)序列號
序列號占4個字節(jié)。 TCP是面向字節(jié)流的,在一個 TCP 連接中傳輸?shù)淖止?jié)流中的每個字節(jié)都按照順序編號。 由于序列號由32位表示,所以最大值為2的32次方,序號增加到最大值的時候,下一個序號又回到了0。也就是說 TCP 協(xié)議可對 4GB 的數(shù)據(jù)進行編號,在一般情況下可保證當序號重復(fù)使用時,舊序號的數(shù)據(jù)早已經(jīng)通過網(wǎng)絡(luò)到達終點或者丟失了。
(3)確認號
確認號也是占4個字節(jié)。表示期望收到對方下一個報文段的序號值。 表明該序號之前的所有數(shù)據(jù)已經(jīng)正確無誤的收到。確認號只有當ACK標志為1時才有效。
(4)TCP首部長度
TCP首部長度也稱為數(shù)據(jù)偏移占半個字節(jié) (4 位)。 它指出了 TCP報文段的數(shù)據(jù)起始處距離TCP報文的起始處有多遠。當了解了LwIP中TCP存儲數(shù)據(jù)結(jié)構(gòu)后,會發(fā)現(xiàn)這個值是很有用的。
(5)TCP標志位
TCP標志位,一共有 6 個,分別占 1 位,共 6 位 。每一位的值只有0和 1,分別表達不同意思。
- URG標志,稱為緊急標志,當URG=1的時候,表示緊急指針有效。它告訴系統(tǒng)此報文段中有緊急數(shù)據(jù),應(yīng)盡快傳送,而不要按原來的排隊順序來傳送。URG標志要與首部中的“緊急指針”字段配合使用。
- ACK標志,稱為確認標志,當ACK=1的時候,確認號有效。一般稱帶有ACK標志的TCP報文段為“確認報文段”。TCP規(guī)定,在連接建立后所有傳送的報文段都必須把ACK設(shè)置為1。
- PSH標志,稱為推送標志,當PSH = 1的時候,表示該報文段高優(yōu)先級,接收方TCP應(yīng)該盡快推送給接收應(yīng)用程序,而不用等到整個TCP緩存都填滿了后再交付。
- RST標志,稱為復(fù)位標志,當RST =1的時候,表示TCP連接中出現(xiàn)嚴重錯誤,需要釋放并重新建立連接。一般稱攜帶RST標志的TCP報文段為“復(fù)位報文段”。
- SYN標志,稱為同步標志,當SYN = 1的時候,表明這是一個請求連接報文段。一般稱攜帶SYN標志的TCP報文段為“同步報文段”。在TCP 三次握手中的第一個報文就是同步報文段,在連接建立時用來同步序號。 對方若同意建立連接,則應(yīng)在響應(yīng)的報文段中使SYN = 1和ACK = 1。
- FIN標志,稱為終止標志,當FIN = 1時,表示此報文段的發(fā)送方的數(shù)據(jù)已經(jīng)發(fā)送完畢,并要求釋放TCP連接。 一般稱攜帶FIN的報文段為“結(jié)束報文段”。在TCP四次揮手釋放連接的時候,就會用到該標志。
(6)窗口大小
窗口大小占2字節(jié)。該字段明確指出了現(xiàn)在允許對方發(fā)送的數(shù)據(jù)量,它告訴對方本端的TCP接收緩沖區(qū)還能容納多少字節(jié)的數(shù)據(jù),這樣對方就可以控制發(fā)送數(shù)據(jù)的速度。窗口大小的值是指,從本報文段首部中的確認號算起,接收方目前允許對方發(fā)送的數(shù)據(jù)量。
(7)校驗和
校驗和占2個字節(jié)。由發(fā)送端填充,接收端對 TCP 報文段執(zhí)行 CRC 算法,以檢驗 TCP 報文段在傳輸過程中是否損壞,如果損壞這丟棄。檢驗范圍包括首部和數(shù)據(jù)兩部分,這也是 TCP 可靠傳輸?shù)囊粋€重要保障。
(8)緊急指針
緊急指針占2個字節(jié)。僅在URG=1時才有意義,它指出本報文段中的緊急數(shù)據(jù)的字節(jié)數(shù)。 當URG = 1時,發(fā)送方TCP就把緊急數(shù)據(jù)插入到本報文段數(shù)據(jù)的最前面,而在緊急數(shù)據(jù)后面的數(shù)據(jù)仍是普通數(shù)據(jù)。因此,緊急指針指出了緊急數(shù)據(jù)的末尾在報文段中的位置。
2、TCP服務(wù)器設(shè)計
我們已經(jīng)對TCP協(xié)議及其報文格式做了簡單說明,接下來我們將結(jié)合LwIP協(xié)議棧,使用RAW API實現(xiàn)一個TCP服務(wù)器的簡單應(yīng)用。
2.1、TCP相關(guān)的RAW API函數(shù)
在開始實現(xiàn)TCP服務(wù)器之前,我們首先來看一看LwIP中與TCP相關(guān)的RAW API函數(shù)有哪些。并簡單的了解一下其功能。
2.1.1、建立TCP連接的API函數(shù):
2.1.2、發(fā)送TCP數(shù)據(jù)的API函數(shù):
2.1.3、接收TCP數(shù)據(jù)的API函數(shù):
2.1.4、TCP輪詢API函數(shù):
2.1.5、關(guān)閉和中止TCP連接的API函數(shù):
2.2、TCP服務(wù)器的工作流程
我們已經(jīng)了解了TCP所涉及到的API函數(shù),那么使用這些函數(shù)怎么實現(xiàn)一個TCP服務(wù)器呢?我們先簡單說明一下其基本的流程。
2.2.1、新建控制塊
使用tcp_new()函數(shù)建立一個TCP控制塊。
2.2.2、綁定控制塊
對于服務(wù)器來說,新建一個控制快后,需要在控制塊上綁定本地IP和端口,以方便客戶端的連接。
2.2.3、控制塊偵聽
使用tcp_listen函數(shù),對于服務(wù)器來說,我們需要顯性調(diào)用tcp_listen函數(shù)以使控制塊進入監(jiān)聽狀態(tài),等待客戶端的連接請求。
2.2.4、建立連接
其實在我們調(diào)用tcp_listen函數(shù)進入服務(wù)器監(jiān)聽狀態(tài)后,需要馬上使用tcp_accept函數(shù)來注冊一個接收處理函數(shù),因為一旦有客戶端連接請求被成功建立后,服務(wù)器就會調(diào)用這個處理函數(shù)。
2.2.5、接受并處理數(shù)據(jù)
一旦連接成功,accept回調(diào)函數(shù)會調(diào)用tcp_recv函數(shù)注冊一個接收完成的處理函數(shù)。對于服務(wù)器來說,接收到了客戶端的數(shù)據(jù)或操作要求,就會調(diào)用這一回調(diào)函數(shù)進行處理。這其實是一個復(fù)雜的過程:接收到數(shù)據(jù)后,首先通知更新接受窗口(使用tcp_recved函數(shù)),處理并發(fā)送數(shù)據(jù)(使用tcp_write函數(shù)),數(shù)據(jù)發(fā)送成功則清除已發(fā)送的數(shù)據(jù)(使用tcp_sent函數(shù)),最后關(guān)閉連接(使用函數(shù)tcp_close)。
用流程圖表述如下:
在上述流程圖中我們列出了每一環(huán)節(jié)所用到的主要函數(shù),其他一些函數(shù)用到了但未列出,有興趣可以免查閱源碼或者看相關(guān)的手冊。
2.3、常用端口
TCP所使用的端口有很多與UDP是相同的,也有一些不一樣。為了方便操作我們已經(jīng)將常用的端口以宏定義的形式存儲在一個文件中。現(xiàn)將常用的端口列于下,我們也是使用下列端口來實現(xiàn)我們的操作。
在這里我們只是設(shè)計一個簡單的TCP服務(wù)器,并不設(shè)定任何復(fù)雜的應(yīng)用,所以我們選擇使用TCP回顯協(xié)議端口。
3、TCP服務(wù)器實現(xiàn)
我們已經(jīng)分析了TCP服務(wù)器的工作流程,我們將其劃分為三個部分來實現(xiàn):首先是TCP服務(wù)器的初始化。其實現(xiàn)代碼如下:
/* TCP服務(wù)器初始化 */ void Tcp_Server_Initialization(void) {struct tcp_pcb *tcp_server_pcb;/* 為tcp服務(wù)器分配一個tcp_pcb結(jié)構(gòu)體 */tcp_server_pcb = tcp_new();/* 綁定本地端號和IP地址 */tcp_bind(tcp_server_pcb, IP_ADDR_ANY, TCP_SERVER_PORT);/* 監(jiān)聽之前創(chuàng)建的結(jié)構(gòu)體tcp_server_pcb */tcp_server_pcb = tcp_listen(tcp_server_pcb);/* 初始化結(jié)構(gòu)體接收回調(diào)函數(shù) */tcp_accept(tcp_server_pcb, TCPServerAccept); }其次是實現(xiàn)TCP服務(wù)器接收回調(diào)函數(shù),該函數(shù)為tcp_accept_fn類型,注冊到了監(jiān)聽控制塊的accept字段。在服務(wù)器上有新連接建立時就會被內(nèi)核調(diào)用。在這個函數(shù)中,我們必須要實現(xiàn)一個非常重要的功能,就是注冊TCP服務(wù)器數(shù)據(jù)接收處理函數(shù)。
/* TCP服務(wù)器接收回調(diào)函數(shù),當客戶端建立連接后本函數(shù)被調(diào)用 */ static err_t TCPServerAccept(void *arg, struct tcp_pcb *pcb, err_t err) {/* 注冊接收回調(diào)函數(shù) */tcp_recv(pcb, TCPServerCallback);return ERR_OK; }最后,不用說就是要實現(xiàn)TCP服務(wù)器的具體實現(xiàn)功能。這個函數(shù)其實就是我們前面注冊過的TCP服務(wù)器數(shù)據(jù)接收處理函數(shù)。這個函數(shù)是tcp_recv_fn類型。這是使用RAW API實現(xiàn)TCP服務(wù)器最重要的函數(shù),因為我們實現(xiàn)的TCP服務(wù)器究竟有什么功能,完全依賴于這個函數(shù)及其所掉用的函數(shù)。
/* TCP服務(wù)器數(shù)據(jù)處理服務(wù)器回調(diào)函數(shù) */ static err_t TCPServerCallback(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err) {struct pbuf *tcp_send_pbuf;char echoString[]="This is the client content echo:\r\n";if (tcp_recv_pbuf != NULL){/* 更新接收窗口 */tcp_recved(pcb, tcp_recv_pbuf->tot_len);/* 將接收的數(shù)據(jù)拷貝給發(fā)送結(jié)構(gòu)體 */tcp_send_pbuf = tcp_recv_pbuf;tcp_write(pcb,echoString, strlen(echoString), 1);/* 將接收到的數(shù)據(jù)再轉(zhuǎn)發(fā)出去 */tcp_write(pcb, tcp_send_pbuf->payload, tcp_send_pbuf->len, 1);pbuf_free(tcp_recv_pbuf);tcp_close(pcb);}else if (err == ERR_OK){return tcp_close(pcb);}return ERR_OK; }這里我們只是實現(xiàn)了簡單的回環(huán)服務(wù)器操作功能,如果需要更為復(fù)雜的功能,甚至與更復(fù)雜的應(yīng)用層協(xié)議都可在此基礎(chǔ)上擴展。
4、結(jié)論
本篇我們基于LwIP實現(xiàn)了簡單的TCP服務(wù)器應(yīng)用。通過回調(diào)函數(shù)的實現(xiàn)方式,整個過程與UDP的實現(xiàn)基本類似。我們采用TCP客戶端軟件測試連接都沒有問題。如果想基于TCP服務(wù)器實現(xiàn)更為復(fù)雜的應(yīng)用,如Modbus TCP等只需要在回調(diào)函數(shù)中實現(xiàn)響應(yīng)的功能就可以了。
歡迎關(guān)注:
總結(jié)
以上是生活随笔為你收集整理的LwIP应用开发笔记之五:LwIP无操作系统TCP服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 比特币与手续费
- 下一篇: c语言酒店管理系统设计目的,C语言酒店管