LwIP應用開發筆記之五:LwIP無操作系統TCP服務器


  前面我們實現了UDP服務器及客戶端以及基於其上的TFTP應用服務器。接下來我們將實現同樣廣泛應用的TCP協議各類應用。

1TCP簡述

  TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。在簡化的計算機網絡OSI模型中,它完成第四層傳輸層所指定的功能,與用戶數據報協議(UDP)是同一層內的,另一個重要的傳輸協議。在因特網協議族(Internet protocol suite)中,TCP層是位於IP層之上,應用層之下的中間層。不同主機的應用層之間經常需要可靠的、像管道一樣的連接,但是IP層本身不提供這樣的流機制,而是提供不可靠的包交換,恰好TCP協議不足了這一應用需求。

  應用層向TCP層發送用於網間傳輸的、用8位字節表示的數據流,然后TCP把數據流分區成適當長度的報文段。之后TCP把結果包傳給IP層,由它來通過網絡將包傳送給接收端實體的TCP層。TCP為了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然后接收端實體對已成功收到的包發回一個相應的確認(ACK);如果發送端實體在合理的往返時延(RTT)內未收到確認,那么對應的數據包就被假設為已丟失將會被進行重傳。TCP用一個校驗和函數來檢驗數據是否有錯誤;在發送和接收時都要計算校驗和,以確保數據的正確性。TCP協議的數據包結構如下:

 

  TCP數據包中各部分的含義如下:

1)源端口和目標端口

  源端口和目標端口各占2個字節。用來告知主機該報文段是來自哪里以及傳送給哪里。進行 TCP 通訊時,客戶端通常使用系統自動選擇的臨時端口號,而服務器則根據應用不同使用知名服務端口號。

2)序列號

  序列號占4個字節。 TCP是面向字節流的,在一個 TCP 連接中傳輸的字節流中的每個字節都按照順序編號。 由於序列號由32位表示,所以最大值為2的32次方,序號增加到最大值的時候,下一個序號又回到了0。也就是說 TCP 協議可對 4GB 的數據進行編號,在一般情況下可保證當序號重復使用時,舊序號的數據早已經通過網絡到達終點或者丟失了。

3)確認號

  確認號也是占4個字節。表示期望收到對方下一個報文段的序號值。 表明該序號之前的所有數據已經正確無誤的收到。確認號只有當ACK標志為1時才有效。

4TCP首部長度

  TCP首部長度也稱為數據偏移占半個字節 (4 位)。 它指出了 TCP報文段的數據起始處距離TCP報文的起始處有多遠。當了解了LwIP中TCP存儲數據結構后,會發現這個值是很有用的。

5TCP標志位

  TCP標志位,一共有 6 個,分別占 1 位,共 6 位 。每一位的值只有0和 1,分別表達不同意思。

  • URG標志,稱為緊急標志,當URG=1的時候,表示緊急指針有效。它告訴系統此報文段中有緊急數據,應盡快傳送,而不要按原來的排隊順序來傳送。URG標志要與首部中的“緊急指針”字段配合使用。
  • ACK標志,稱為確認標志,當ACK=1的時候,確認號有效。一般稱帶有ACK標志的TCP報文段為“確認報文段”。TCP規定,在連接建立后所有傳送的報文段都必須把ACK設置為1。
  • PSH標志,稱為推送標志,當PSH = 1的時候,表示該報文段高優先級,接收方TCP應該盡快推送給接收應用程序,而不用等到整個TCP緩存都填滿了后再交付。
  • RST標志,稱為復位標志,當RST =1的時候,表示TCP連接中出現嚴重錯誤,需要釋放並重新建立連接。一般稱攜帶RST標志的TCP報文段為“復位報文段”。
  • SYN標志,稱為同步標志,當SYN = 1的時候,表明這是一個請求連接報文段。一般稱攜帶SYN標志的TCP報文段為“同步報文段”。在TCP 三次握手中的第一個報文就是同步報文段,在連接建立時用來同步序號。 對方若同意建立連接,則應在響應的報文段中使SYN = 1和ACK = 1。
  • FIN標志,稱為終止標志,當FIN = 1時,表示此報文段的發送方的數據已經發送完畢,並要求釋放TCP連接。 一般稱攜帶FIN的報文段為“結束報文段”。在TCP四次揮手釋放連接的時候,就會用到該標志。

6)窗口大小

  窗口大小占2字節。該字段明確指出了現在允許對方發送的數據量,它告訴對方本端的TCP接收緩沖區還能容納多少字節的數據,這樣對方就可以控制發送數據的速度。窗口大小的值是指,從本報文段首部中的確認號算起,接收方目前允許對方發送的數據量。

7)校驗和

  校驗和占2個字節。由發送端填充,接收端對 TCP 報文段執行 CRC 算法,以檢驗 TCP 報文段在傳輸過程中是否損壞,如果損壞這丟棄。檢驗范圍包括首部和數據兩部分,這也是 TCP 可靠傳輸的一個重要保障。

8)緊急指針

  緊急指針占2個字節。僅在URG=1時才有意義,它指出本報文段中的緊急數據的字節數。 當URG = 1時,發送方TCP就把緊急數據插入到本報文段數據的最前面,而在緊急數據后面的數據仍是普通數據。因此,緊急指針指出了緊急數據的末尾在報文段中的位置。

2TCP服務器設計

  我們已經對TCP協議及其報文格式做了簡單說明,接下來我們將結合LwIP協議棧,使用RAW API實現一個TCP服務器的簡單應用。

2.1TCP相關的RAW API函數

  在開始實現TCP服務器之前,我們首先來看一看LwIP中與TCP相關的RAW API函數有哪些。並簡單的了解一下其功能。

2.1.1、建立TCP連接的API函數:

 

2.1.2、發送TCP數據的API函數:

 

2.1.3、接收TCP數據的API函數:

 

2.1.4TCP輪詢API函數:

 

2.1.5、關閉和中止TCP連接的API函數:

 

2.2TCP服務器的工作流程

  我們已經了解了TCP所涉及到的API函數,那么使用這些函數怎么實現一個TCP服務器呢?我們先簡單說明一下其基本的流程。

2.2.1、新建控制塊

  使用tcp_new()函數建立一個TCP控制塊。

2.2.2、綁定控制塊

  對於服務器來說,新建一個控制快后,需要在控制塊上綁定本地IP和端口,以方便客戶端的連接。

2.2.3、控制塊偵聽

  使用tcp_listen函數,對於服務器來說,我們需要顯性調用tcp_listen函數以使控制塊進入監聽狀態,等待客戶端的連接請求。

2.2.4、建立連接

  其實在我們調用tcp_listen函數進入服務器監聽狀態后,需要馬上使用tcp_accept函數來注冊一個接收處理函數,因為一旦有客戶端連接請求被成功建立后,服務器就會調用這個處理函數。

2.2.5、接受並處理數據

  一旦連接成功,accept回調函數會調用tcp_recv函數注冊一個接收完成的處理函數。對於服務器來說,接收到了客戶端的數據或操作要求,就會調用這一回調函數進行處理。這其實是一個復雜的過程:接收到數據后,首先通知更新接受窗口(使用tcp_recved函數),處理並發送數據(使用tcp_write函數),數據發送成功則清除已發送的數據(使用tcp_sent函數),最后關閉連接(使用函數tcp_close)。

  用流程圖表述如下:

 

  在上述流程圖中我們列出了每一環節所用到的主要函數,其他一些函數用到了但未列出,有興趣可以免查閱源碼或者看相關的手冊。

2.3、常用端口

  TCP所使用的端口有很多與UDP是相同的,也有一些不一樣。為了方便操作我們已經將常用的端口以宏定義的形式存儲在一個文件中。現將常用的端口列於下,我們也是使用下列端口來實現我們的操作。

 

  在這里我們只是設計一個簡單的TCP服務器,並不設定任何復雜的應用,所以我們選擇使用TCP回顯協議端口。

3TCP服務器實現

  我們已經分析了TCP服務器的工作流程,我們將其划分為三個部分來實現:首先是TCP服務器的初始化。其實現代碼如下:

 1 /* TCP服務器初始化 */
 2 void Tcp_Server_Initialization(void)
 3 {
 4   struct tcp_pcb *tcp_server_pcb;
 5  
 6   /* 為tcp服務器分配一個tcp_pcb結構體 */
 7   tcp_server_pcb = tcp_new();
 8  
 9   /* 綁定本地端號和IP地址 */
10   tcp_bind(tcp_server_pcb, IP_ADDR_ANY, TCP_SERVER_PORT);
11  
12   /* 監聽之前創建的結構體tcp_server_pcb */
13   tcp_server_pcb = tcp_listen(tcp_server_pcb);
14  
15   /* 初始化結構體接收回調函數 */
16   tcp_accept(tcp_server_pcb, TCPServerAccept);
17 }

  其次是實現TCP服務器接收回調函數,該函數為tcp_accept_fn類型,注冊到了監聽控制塊的accept字段。在服務器上有新連接建立時就會被內核調用。在這個函數中,我們必須要實現一個非常重要的功能,就是注冊TCP服務器數據接收處理函數。

1 /* TCP服務器接收回調函數,當客戶端建立連接后本函數被調用 */
2 static err_t TCPServerAccept(void *arg, struct tcp_pcb *pcb, err_t err)
3 {
4   /* 注冊接收回調函數 */
5   tcp_recv(pcb, TCPServerCallback);
6  
7   return ERR_OK;
8 }

  最后,不用說就是要實現TCP服務器的具體實現功能。這個函數其實就是我們前面注冊過的TCP服務器數據接收處理函數。這個函數是tcp_recv_fn類型。這是使用RAW API實現TCP服務器最重要的函數,因為我們實現的TCP服務器究竟有什么功能,完全依賴於這個函數及其所掉用的函數。

 1 /* TCP服務器數據處理服務器回調函數 */
 2 static err_t TCPServerCallback(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err)
 3 {
 4   struct pbuf *tcp_send_pbuf;
 5   char echoString[]="This is the client content echo:\r\n";
 6  
 7   if (tcp_recv_pbuf != NULL)
 8   {
 9     /* 更新接收窗口 */
10     tcp_recved(pcb, tcp_recv_pbuf->tot_len);
11  
12     /* 將接收的數據拷貝給發送結構體 */
13     tcp_send_pbuf = tcp_recv_pbuf;
14     tcp_write(pcb,echoString, strlen(echoString), 1);
15     /* 將接收到的數據再轉發出去 */
16     tcp_write(pcb, tcp_send_pbuf->payload, tcp_send_pbuf->len, 1);
17  
18     pbuf_free(tcp_recv_pbuf);
19     tcp_close(pcb);
20   }
21   else if (err == ERR_OK)
22   {
23     return tcp_close(pcb);
24   }
25  
26   return ERR_OK;
27 }

  這里我們只是實現了簡單的回環服務器操作功能,如果需要更為復雜的功能,甚至與更復雜的應用層協議都可在此基礎上擴展。

4、結論

  本篇我們基於LwIP實現了簡單的TCP服務器應用。通過回調函數的實現方式,整個過程與UDP的實現基本類似。我們采用TCP客戶端軟件測試連接都沒有問題。如果想基於TCP服務器實現更為復雜的應用,如Modbus TCP等只需要在回調函數中實現響應的功能就可以了。

歡迎關注:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM