1. 程式人生 > >網路程式設計雜談之TCP協議

網路程式設計雜談之TCP協議

TCP協議屬於網路分層中的傳輸層,傳輸層作用的就是建立埠與埠的通訊,而其下一層網路層的主要作用是建立"主機到主機"的通訊,所以在我們日常進行網路程式設計時只要確定主機和埠,就能實現程式之間的資料交流,在Unix系統中就把主機+埠,叫做"套接字"(socket),所以一般網路程式設計都是基於對於socket的操作來做的。

TCP協議其實是一個非常複雜的協議,做過網路程式設計開發的都聽過一句話‘’TCP本身是一種可靠的協議”,但正是為了保證可靠性,TCP 內部使用瞭如各種重傳與控制演算法,所以 TCP 是一個內部原理複雜,但是使用起來比較簡單的協議。

下面我們對TCP協議進行一個基本的介紹,本文只是站在應用的角度上闡述,相比與真正的深入還是比較淺顯的。

一、TCP協議格式

首先主要看下TCP協議的頭格式

其中各欄位的意義如下:

1、TCP源埠(Source Port):16位的源埠其中包含傳送方應用程式對應的埠。源埠和源IP地址標示報文傳送端的地址。 

2、TCP目的埠(Destination port):16位的目的埠域定義傳輸的目的。這個埠指明報文接收計算機上的應用程式地址介面。 

3、包序號(Sequence Number):32位的SN序列號標識了TCP報文中第一個byte在對應方向的傳輸中對應的位元組序號,用來記錄網路包順序,解決傳輸中的亂序、重複問題,比如傳送端傳送的一個TCP包淨荷(不包含TCP頭)為10byte,SN為5,則傳送端接著傳送的下一個資料包的時候,SN應該設定為5+10=15。通過序列號,TCP接收端可以識別出重複接收到的TCP包,從而丟棄重複包,同時對於亂序資料包也可以依靠系列號進行重排序,進而對高層提供有序的資料流。另外如果接收的包中包含SYN或FIN標誌位,邏輯上也佔用1個byte,應答號需加1。 

4、確認號(Acknowledgement Number):32位的ACK標識了報文傳送端期望接收的位元組序列,如果設定了ACK控制位,這個值表示一個準備接收的包的序列碼,注意是準備接收的包,比如當前接收端接收到一個淨荷為10byte的資料包,SN為5,則會回覆一個確認收到的資料包,如果這個資料包之前的資料也都已經收到了,這個資料包中的ACK Number則設定為10+5=15,表示之前的資料都已經收到了,準備接受SN=15的資料包。

5、視窗(Advertised-Window):著名的滑動視窗(Sliding Window),用於TCP的流量控制。

6、狀態位(TCP-FLAG):包的型別,用於操作TCP的狀態機,其8位狀態分別表示如下含義

  • CWR(Congestion Window Reduce)  0x80:擁塞視窗減少標誌set by sender,用來表明它接收到了設定ECE標誌的TCP包。並且sender 在收到訊息之後已經通過降低傳送視窗的大小來降低傳送速率。
  • ECE(ECN Echo) 0x40:ECN響應標誌被用來在TCP3次握手時表明一個TCP端是具備ECN功能的。在資料傳輸過程中也用來表明接收到的TCP包的IP頭部的ECN被設定為11。
  • URG(Urgent) 0x20:該標誌位表示緊急(The urgent pointer) 標誌有效,設定為1時,首部中的緊急指標有效;為0時,緊急指標沒有意義。緊急資料不進入接收緩衝區直接交給上層程序處理;
  • ACK 0x10:取值1代表Acknowledgment Number欄位有效,這是一個確認的TCP包,取值0則不是確認包。後續文章介紹中當ACK標誌位有效的時候我們稱呼這個包為ACK包,使用大寫的ACK稱呼。
  • PSH(Push) 0x08:該標誌置位時,一般是表示傳送端快取中已經沒有待發送的資料,接收端不將該資料進行佇列處理,而是儘可能快將資料轉由應用處理。在處理 telnet 或 rlogin 等互動模式的連線時,該標誌總是置位的。如果PSH=1的話,就不用等到整個快取都填滿,直接把快取區中的所有資料進行交付。
  • RST(Reset) 0x04:用於reset相應的TCP連線。通常在發生異常或者錯誤的時候會觸發復位TCP連線。
  • SYN 0x02:同步序列編號(Synchronize Sequence Numbers)有效。該標誌僅在三次握手建立TCP連線時有效。
  • FIN(Finish) 0x01:No more data from sender。當FIN標誌有效的時候我們稱呼這個包為FIN包。

7、校驗位(Checksum):16位TCP頭。傳送端基於資料內容計算一個數值,接收端要與傳送端數值結果完全一樣,才能證明資料的有效性。接收端checksum校驗失敗的時候會直接丟掉這個資料包。CheckSum是根據偽頭+TCP頭+TCP資料三部分進行計算的。

8、緊急指標(Urgent Pointer):16位,指向後面是優先資料的位元組,在URG標誌設定了時才有效。如果URG標誌沒有被設定,緊急域作為填充。 

9、選項(Option):長度不定,但長度必須以是32bits的整數倍。常見的選項包括MSS、SACK、Timestamp等等。

二、TCP狀態機

關於TCP的狀態機理解我們從幾張經典的示意圖開始

TCP狀態轉換圖

TCP三次握手、四次揮手時序圖

基於TCP的網路程式設計中連結的建立斷開、資料傳送都是依賴TCP狀態轉換實現的,例如所謂的建立連結並不是真正的連結,而是一種狀態的維持,表面上的連結其實是通訊雙方共同維護了一個“連結狀態”,而建立連結--資料傳輸--斷開連結的TCP通訊過程,也是這些狀態轉換的過程,這其中狀態的轉換一部分是收到或傳送的某個控制位欄位的變化而引起的,如SYN、FIN、ACK等,還有一些是由於應用程式的動作或計時器超時引發的。

瞭解了以上的內容,下面我們就結合實際報文資料,對TCP連結三次握手,資料傳輸,斷開四次揮手,進行一個簡單的跟蹤驗證;

三、TCP通訊

1、三次握手

建立連結的三次握手的作用主要是初始化Sequence Number 的初始值,同時把這個值通過Synchronize Sequence Numbers(SYN包)告知對端。

通過Wireshark可以捕獲到三次握手的報文

握手流程:

  • 1、client首先初始化該值,傳送一個SYN包給server端,告訴server端一個初始化的SN值
  • 2、server收到client傳送的資料,回覆一包資料,包括ACK確認與SYN,  既要告訴client端收到了資料,同時告知對方SYN值;
  • 3、client回覆ACK確認包,告知server端收到了資料;

2、資料傳輸

通過Wireshark,我們可以看下TCP傳輸中一包資料的組成,對照前面的協議組成,可以看到這裡我們傳送的是0x11,0x11兩個位元組的資料

 

可以看到接收一段回覆的確認包裡ACK從1變成了3,為保證資料的順序性與可靠性,TCP是有一整套的機制來控制的,如大家熟悉的滑動視窗、超時重傳等;

 

這裡有一個需要注意的細節,這裡ACK確認號的真實值其實是從0xffeb49ed 變為 0xffed49ef的,這是由於當某個主機開啟一個TCP會話時,他的初始序列號與確認號是隨機的,可能是0和4,294,967,295之間的任意值,在Wireshark裡顯示的都是相對序列號/確認號,而不是實際序列號/確認號,相對序列號/確認號是和TCP會話的初始序列號相關聯的。這裡Wireshark為方便大家跟蹤檢視顯示的是相對值,因為比起真實序列號/確認號,跟蹤更小的相對序列號/確認號會相對容易一些。

3、四次揮手

 斷開連結的四次揮手的作用主要是回收資源,停止資料傳輸。由於TCP是全雙工的,需要client與server兩端分別斷開各自的通向對方的通道。

通過Wireshark可以捕獲到四次揮手的報文

揮手流程:

  • 1、client端傳送一個FIN包告訴server服務端已經沒有資料要傳輸了,準備斷開連結;
  • 2、server端回覆一個ACK確認包,也就是告訴cient端,好,我知道你要斷開了;
  • 3、server端這時要看自己還有沒有資料要傳送給client,如果沒有了,也要傳送一個FIN包告訴client端,我也沒有資料要傳輸了,準備斷開連結;
  • 4、client端回覆一個ACK確認包,告訴server端,好的,我知道你要斷開了;

四次揮手的流程中,sever端在接收到client端的斷開要求後,ACK確認包與FIN包是否可以合併為一個包來發送,也就是四次揮手是否可能變成三次揮手,答案是可能的,但由於TCP是全雙工的,server端與client端資料傳輸的終止在時序上是獨立且可能相隔較長時間,那麼一般情況下一個完整的斷開連結操作都是需要四次揮手來完成的。

 

到這裡針對TCP協議,以及連結->傳輸->斷開連結流程的基本介紹與說明就結束了,後續針對網路程式設計這一塊我會接著寫幾篇文章,一是對自己工作中涉及到一些網路程式設計的內容進行梳理與總結,另一方面希望能從下至上的加強自己對網路程式設計這塊認知的深度,也希望對大家能有所幫助,其中如有不足與不正確的地方還望指出與海涵。

 

關注微信公眾號,檢視更多技術文章。