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