後臺開發-核心技術與應用實踐--TCP協議

zhhfan發表於2021-03-16

網路模型

為使不同計算機廠家的計算機能夠互相通訊,國際標準化組織 ISO 1981 年正式推薦了一個網路系統結構一一七層參考模型,也叫作開放系統互連模型。

ISO 七層網路模型及其功能展示:

這個七層網路模型在資料的傳輸過程中還會對資料進行封裝,封裝過程如圖所示:

ISO 七層網路模型中,當一臺主機需要傳送使用者的資料( data 時,資料首先通過應用層的介面進入應用層。在應用層,使用者的資料被加上應用層的報頭( Ppplication Header, AH ),形成應用層協議資料單元( Protocol Data Unit, PDU ),然後被遞交到下層表示層。表示層並不‘關心’上層應用層的資料格式而是把整個應用層遞交的資料包看成是一個整體(應用層資料)進行封裝,即加上表示層的報頭( Presentation Header, PH 然後,遞交到下層會話層 同樣,會話層、傳輸層、網路層(假設用 TCP 傳輸,則是 TCP 資料+ IP 包頭)、資料鏈路層(把上層的 TCP 資料+ IP 頭統一稱為幀資料,即幀 +幀資料+幀尾( CRC)) 也都要分別給上層遞交下來的資料加上自己的報頭。它們是:會話層報頭( Session Header, SH )、傳輸層報頭( Transport Header, TH )、 網路層報頭( Network Header, NH )和資料鏈路層報頭( Data link Header, DH 其中,資料鏈路層還要給網路層遞交的資料加上資料鏈路層報尾(Data link Termination, DT )形成最終的一幀資料。

當一幀資料通過物理層傳送到目標主機的物理層時,該主機的物理層把它遞交到上層一一資料鏈路層。資料鏈路層負責去掉資料幀的幀頭部 DH 和尾部 DT (同時還進行資料校驗)。如果資料沒有出錯,則遞交到上層網路層。同樣,網路層、傳輸層、會話層、表示層、
應用層也要做類似的工作。最終,原始資料被遞交到目標主機的具體應用程式中。

五層網路模型:

  1. 應用層:確定程式之間通訊的性質以滿足使用者需求。應用層協議有很多,如支援全球資訊網應用的 http 協議、支援電子郵件的 SMTP 協議、支援檔案傳送的句協議,等等
  2. 運輸層:負責主機間不同程式的通訊。這一層中的協議有面向連線的 TCP (傳輸控制協議)、無連線的 UDP (使用者資料包協議);資料傳輸的單位稱為報文段或使用者資料包
  3. 網路層:負責分組交換網中不同主機間的通訊。作用為:傳送資料時,將運輸層中的報文段或使用者資料包封裝成 IP 資料包,並選擇合適路由
  4. 資料鏈路層:負責將網路層的 IP 資料包組裝成幀
  5. 物理層:透明地傳輸位元流

使用最廣泛的為四層模型--TCP/IP 分層模型(TCP/IP Layering Model)。它有因特網分層模型( Internet Layering Model )和因特網參考模型( nternet Reference Model )之稱。

四層網路模型表示:

TCP/IP 分層模型的4個協議層分別完成以下的功能:

  1. 網路介面層

網路介面層包括用於協作 IP 資料在已有網路介質上傳輸的協議 實際上 TCP/IP 標準並不定義與 ISO 資料鏈路層和物理層相對應的功能。相反,它定義了像 APP ( Address Resolution Protocol ,地址解析協議)這樣的協議,提供 TCP/IP 協議的資料結構和實際物理硬體之間的介面。

  1. 網間層

網間層對應於 OSI 七層參考模型的網路層。本層包含 IP 協議、RIP 協議( Routing Information Protocol ,路由資訊協議),負責資料的包裝、定址和路由。同時還包含 ICMP (Internet Control Message Protocol ,網間控制報文協議)用來提供網路診斷資訊

  1. 傳輸層

傳輸層對應於 OSI 七層參考模型的傳輸層,它提供兩種端到端的通訊服務。其中 TCP 協議( Transmission Control Protocol )提供可靠的資料流運輸服務, UDP 協議( Use Datagram Protocol )提供不可靠的使用者資料包服務

  1. 應用層

應用層對應於 OSI 七層參考模型的應用層和表示層。因特網的應用層協議包括 Finger Who is FTP (檔案傳輸協議) Gopher HTTP (超文字傳輸協議)、 Telent (遠端終端協議)、SMTP (簡單郵件傳送協議)、 IRC (因特網中繼會話)、NNTP (網路新聞傳輸協議)等

綜上,TCP 協議在網路 OSI 的七層模型中的第四層傳輸層, IP 協議在第三層網路層, ARP 協議在第二層資料鏈路層;在第二層上的資料叫 Frame ,在第三層上的資料叫 Packet ,第四層的資料叫 Segment。所有程式的資料首先會打包到 TCP Segment 中,然後 TCP Segment 會打包到 IP Packet ,然後再打包到乙太網 Ethernet Frame 中,傳到對端後,各個層解析自己的協議,然後把資料交給更高層的協議處理。

TCP頭部
TCP頭部格式如下:

  1. 16位埠號:告知主機該報文段是來自哪裡(源埠)以及傳給哪個上層協議或應用程式(目的埠)的
  2. 32位序列號:TCP 通訊(從 TCP 連線建立到斷開)過程中某一個傳輸方向上的位元組流的每個位元組的編號
  3. 32位確認號:用作對另一方傳送來的 TCP 報文段的響應,其值是收到的 TCP 報文段的序號值加一。假設主機A和主機B進行 TCP 通訊,那麼A傳送出的 TCP 報文段不僅攜帶自己的序號,而且包含對B傳送來的 TCP 報文段的確認號。反之,B傳送出的 TCP 報文段也同時攜帶自己的序號和對A傳送來的報文段的確認號。
  4. 4位頭部長度::標識該 TCP 頭部有多少個 32bit (4 Byte)。 因為最大能表示 15 ,所以 TCP 頭部最長是 60 Byte。
  5. 6位標誌位包含如下幾項:
  1. URG 標誌,表示緊急指標(urgent pointer )是否有效
  2. ACK 標誌,表示確認號是否有效,一般稱攜帶 ACK 標誌的 TCP 報文段為“確認報文段”
  3. PSH 標誌,提示接收端應用程式應該立即從 TCP 接收緩衝區中讀走資料,為接收後續資料騰出空間(如果應用程式不將接收到的資料讀走,它們就會一直停留在 TCP 接收緩衝區中)
  4. RST 標誌,表示要求對方重新建立連線,一般稱攜帶 RST 標誌的 TCP 報文段為“復位報文段”
  5. SYN 標誌,表示請求建立一個連線,一般稱攜帶 SYN 標誌的 TCP 報文段為“同步報文段”
  6. FIN 標誌,表示通知對方本端要關閉連線了,一般稱攜帶 FIN 標誌的 TCP 報文段為“結束報文段”
  1. 16 位視窗大小( window size ):是 TCP 流量控制的一個手段。這裡說的視窗,指的是接收通告視窗 (Receiver Window), RWND 它告訴對方本端的 TCP 接收緩衝區還能容納多少位元組的資料,這樣對方就可以控制傳送資料的速度
  2. 16 位校驗和(TCP checksum ):由傳送端填充,接收端對 TCP 報文段執行 CRC 演算法,以檢驗 TCP 報文段在傳輸過程中是否損壞。注意,這個校驗不僅包括 TCP 頭部,也包括資料部分。這也是 TCP 可靠傳輸的一個重要保障
  3. 16 位緊急指標( urgent pointer ):是一個正的偏移量。它和序號欄位的值相加表示最後一個緊急資料的下一位元組的序號。因此,確切地說,這個欄位是緊急指標相對當前序號的偏移,不妨稱之為“緊急偏移”。TCP 的緊急指標是傳送端向接收端傳送緊急資料的方法。

需要注意以下幾點:

  1. TCP 的包是沒有 IP 地址的,那是 IP 層上的事,但是有源埠和目的埠
  2. 一個TCP 連線需要四個元組(src_ip, src_port, dst_ip, dst_port) 來表示是同一個連線。準確說是五元組,還有一個是協議
  3. Sequence Number 是包的序號,用來解決網路包亂序(reordering )問題
  4. Acknowledgement Number 就是 ACK ,用於確認收到,用來解決不丟包的問題
  5. Window Advertised Window ,也就是著名的滑動視窗 Sliding Window ),用於解決流控問題
  6. TCP Flag ,也就是包的型別,主要是用於操控 TCP 的狀態機的

TCP的三次握手與四次揮手

TCP 連線的建立可以簡單地稱為3次握手,而連線的中止則可以稱為4次握手

  1. 第一次握手:建立連線時,客戶端傳送 SYN 包( SYN )到伺服器,並進入 SYN_SEND 狀態,等待伺服器確認
  2. 第二次握手:伺服器收到 SYN 包,必須確認客戶的 SYN ( ACK=J+ 1 ),同時自己也傳送一個 SYN 包( SYN=K ),即 SYN+ACK 包,此時伺服器進入 SYN_RECV 狀態
  3. 第三次握手:客戶端收到伺服器的 SYN+ACK 包,向伺服器傳送確認包 ACK(ACK=K+l ),此包傳送完畢,客戶端和伺服器進入 ESTABLISHE狀態,完成三次握手

完成三次握手,客戶端與伺服器開始傳送資料,也就是 ESTABLISHED 狀態

結束連線:
TCP 一個特別的概念叫作半關閉,這個概念是說,TCP 的連線是全雙工(可以同時傳送和接收)連線,因此在關閉連線的時候,必須關閉傳和送兩個方向上的連線。客戶機給伺服器 FIN的TCP 報文,然後伺服器返回給客戶端一個確認 ACK 報文,並且傳送一個 FIN 報文,當客戶機回覆 ACK 報文後(4次握手),連線就結束了

為什麼建連線要3次握手,斷連線需要4次揮手?
對於建連線的3次握手,主要是要初始化 Sequence Number 的初始值。通訊的雙方要互相通知對方自己的初始化的 Sequence Numbe -- 所以叫 SYN。 也就上圖中的J 和 K。這個號要作為以後的資料通訊的序號,以保證應用層接收到的資料不會因為網路上的傳輸問題而亂序( TCP 會用這個序號來拼接資料)

對於4次揮手,其實仔細看則是兩次,因為 TCP 是全雙工的,所以,傳送方和接收方都需要 FIN ACK 只不過,有一方是被動的,所以看上去就成了所謂的4次揮手。如果兩邊同時斷連線,那就會就進入到 CLOSING 狀態,然後到達 TIME_WAIT狀態

TCP狀態圖

  1. CLOSED:表示初始狀態
  2. LISTEN:表示伺服器端的某個 socket 處於監昕狀態,可以接受連線
  3. SYN_SENT:在服務端監聽後,客戶端 socket 執行 CONNECT 連線時,客戶端傳送 SYN 報文,此時客戶端就進入 SYN_SENT 狀態,等待服務端的確認
  4. 表示服務端接收到了SYN 報文
  5. ESTABLISHED:表示連線已經建立了
  6. FIN_WAIT_1:這個是已經建立連線之後,其中一方請求終止連線,等待對方的 FIN 報文
  7. FIN_WAIT_2:實際上 FIN_WAIT_2 狀態下的 socket ,表示半連線,即有一方要求關閉連線,但另外還告訴對方:我暫時有一點資料需要傳送給你,請稍後再關閉連線
  8. TIME_WAIT:表示收到了對方的 FIN 報文,併傳送出了 ACK 報文,就等 2MSL後即可回到 CLOSED 可用狀態了。如果在 FIN_WAIT 狀態下,收到了對方同時帶 FIN 標誌和ACK 標誌的報文時,可以直接進入到 TIME_WAIT 狀態,而無需經過 FIN_WAIT_2 狀態
  9. CLOSING:屬於一種比較罕見的例外狀態,當傳送 FIN 報文後,按理來說是應該先收到(或同時收到)對方的 ACK 報文,再收到對方的 FIN 報文。但是 CLOSING 狀態表示你傳送 FIN 報文後,並沒有收到對方的 ACK 報文,反而收到了對方的 FIN 報文。如果雙方几乎在同時關閉一個 socket 的話,那麼就出現了雙方同時傳送 FIN 報文的情況,就會出現 CLOSING 狀態,表示雙方都正在關閉 socket 連線。
  10. CLOSE_WAIT :這種狀態的含義其實是表示在等待關閉
  11. LAST_ACK :這個狀態還是比較好理解的,它是被動關閉一方在傳送 FIN 報文後,最後等待對方的 ACK 報文
  12. CLOSED 當收到 ACK 報文後,也即可以進入到 CLOSED 可用狀態了

2MSL 等待狀態:有一個 TIME_WAIT 等待狀態,這個狀態又叫作 2MSL狀態,說的是在 TIME_WAIT_2 傳送了最後一個 ACK 資料包以後,要進入 TIME_WAIT狀態。這個狀態是防止最後一次握手的資料包沒有傳送到對方那裡而準備的

FIN_WAIT_2 狀態:這就是著名的半關閉狀態了,這是在關閉連線時,客戶端和伺服器兩次握手之後的狀態。在這個狀態下,應用程式還有接收資料的能力,但是已經無法傳送資料,但是也有一種可能是,客戶端一直處於 FIN_WAIT 狀態,而伺服器則一直處於 WAIT_CLOSE 狀態,直到應用層來決定關閉這個狀態。

TCP超時重傳

下圖給出了正常與3中異常的網路傳輸情況:

當出現以上異常情況時,TCP就會超時重傳。TCP 每傳送一個報文段,就對這個報文段設定一次計時器,只要計時器設定的重傳時間到了,但還沒有收到確認,就要重傳這一報文段,這個就叫作 超時重傳

TCP 協議必須適應兩個方面的時延差異:一個是達到不同目的端的時延的差異;另一個是統一連線上的傳輸時延隨業務量負載的變化而出現的差異。為此, TCP 協議使用自適應演算法( Adaptive Retransmission Algorithm )以適應網際網路分組傳輸時延的變化。

這種演算法的基本要點是 TCP 監視每個連線的效能(即傳輸時延),由每一個 TCP 的連線情況推算出合適的 RTO 值, 當連線時延效能變化時, TCP 也能夠相應地自動修改 RTO 的設定,以適應這種網路的變化。

注:RTO ( Retransmission Timeout ,重傳超時時間),指傳送端傳送資料後、重傳資料前等待接受方收到該資料包文的 ack 時間

為了動態地設定, TCP 引入了 RTT (Round Trip Time ),也就是連線往返時間,指傳送端從傳送 TCP 包開始到接收它的立即響應所耗費的傳輸時間。

自適應重傳演算法的關鍵就在於對當前 RTT 的準確估計,以便適時調整 RTO。
RFC793 中定義的經典演算法是這樣的: 1. 先取樣 RTT ,記下最近幾次的 RTT 值;2. 然後做平滑計算 SRTT (Smoothed RTT)。公式中的 \(\alpha\) 取值為 0.8-0.9 ,這個演算法叫加權移動平均:

\[SRTT = \alpha * SRTT + (1-\alpha) * RTT \]

\[RTO = min[UBOUND, max[LBOUND, (\beta * SRTT)]] \]

UBOUND 是最大的 timeout 時間,上限值; LBOUND 最小的 timeout 時間 ,下限值;\(\beta\) 取值1.3-2.0

TCP 滑動視窗

TCP 的滑動視窗主要有兩個作用:一是提供 TCP 的可靠性;二是提供 TCP 的流控特性。同時滑動視窗機制還體現了 TCP 面向位元組流的設計思路

TCP 頭部中滑動視窗所處的位置:

TCP 的視窗是 16bit 位欄位它代表的是視窗的位元組容量,也就是 TCP 的標準視窗最大為 \(2^{16}-1 = 65535\)位元組
TCP 的選項欄位中還包含了 TCP 視窗擴大因子, option-kind為3, option-length為3, option-data 取值範圍 0-14。視窗擴大因子用來擴大 TCP 視窗,可把原來 16bit 的視窗,擴大為 32 bit。

對於 TCP 話的傳送方,任何時候在其傳送快取內的資料都可以分為4類:1. 已經傳送並得到對端 ACK;2. 傳送但還未收到對端 ACK;3. 未傳送但對端允許傳送; 4. 未傳送且對端不允許傳送。其中“已傳送但還未收到對端 ACK 的”和“未傳送但對端允許傳送的”這兩部分資料稱之為傳送視窗

對於 TCP 的接收方,在某一時刻在它的接收快取記憶體在3種狀態:1. 已接收 2. 未接收準備接收 3. 未接收且為準備接收。其中“未接收準備接收”稱之為接收視窗

TCP是雙工協議,會話的雙方都可以同時接收、傳送資料 TCP 會話的雙方都各自維一個“傳送視窗”和一個“接收視窗”。其中各自的“接收視窗”大小取決於應用、系統、硬體的限制(TCP 傳輸速率不能大於應用的資料處理速率),各自的“傳送視窗”則要求取決於對端通告的“接收視窗”,要求相同

滑動視窗實現面向流的可靠性來源於“確認重傳”機制。TCP 的滑動視窗的可靠性也是建立在“確認重傳”基礎上的。

TCP擁塞控制
TC 的擁塞控制由4個核心演算法組成:慢開始(Slow Start)、擁塞避免(Congestion Voidance)、快速重傳(Fast Retransmit)和快速恢復(Fast Recovery)

傳送方維持一個叫作擁塞視窗 cwnd ( congestion window )的狀態變數。擁塞視窗的大小取決於網路的擁塞程度,並且動態地在變化,傳送方讓自己的傳送視窗等於擁塞視窗,另外考慮到接受方的接收能力,傳送視窗可能小於擁塞視窗
慢開始演算法的思路就是,不要一開始就傳送大量的資料,先探測一下網路的擁塞程度,也就是說由小到大逐漸增加擁塞視窗的大小

慢開始的原理如下所述:

  1. 當主機開始傳送資料時,如果立即將較大的傳送視窗的全部資料位元組都注入到網路中,那麼由於不清楚網路的情況,有可能引其網路擁塞
  2. 比較好的方法是試探一下,即從小到大逐漸增大傳送端的擁塞控制視窗數值
  3. 在剛剛開始傳送報文段時可先將擁塞視窗 cwnd 設定為一個最大報文段的 MSS 的數值。在每收到一個對新報文段確認後,將擁塞視窗增加至多 MSS 的數值,當 rwind 足夠大的時候,為了防止擁塞視窗 cwind 的增長引起網路擁塞,還需要另外一個變數,即慢開始門限 ssthresh

ssthresh的用法如下:(傳送方每收到一個確認就把視窗 cwnd+l)

擁塞避免演算法讓擁塞視窗緩慢增長,即每經過一個往返時間 RTT 就把傳送方的擁塞視窗cwnd 加1,而不是加倍

擁塞控制具體過程如下所述:

  1. TCP 連線初始化,將擁塞視窗設定為1
  2. 執行慢開始演算法, cwind 按指數規律增長,直到 cwind = ssthress 時,開始執行擁塞避免演算法, cwnd 按線性規律增長
  3. 當網路發生擁塞,把 ssthresh 更新為擁塞前 ssthresh 值的一半, cwnd 重新設定為1,按照步驟(2)執行

慢開始和擁塞避免演算法的實現舉例:

快重傳和快恢復

快重傳要求接收方在收到一個失序的報文段後就立即發出重複確認(為的是使傳送方及早知道有報文段沒有到達對方),而不要等到自己傳送資料時捎帶確認。快重傳演算法規定,傳送方只要一連收到3個重複確認就應當立即重傳對方尚未收到的報文段,而不必繼續等待設定的重傳計時器時間到期。

快重傳示意圖:

快重傳配合使用的還有快恢復演算法,有以下兩個要點:

  1. 當傳送方連續收到三個重複確認時,就執行“乘法減小”演算法,把 ssthresh 門限減半,但是接下去並不執行慢開始演算法
  2. 考慮到如果網路出現擁塞的話就不會收到好幾個重複的確認,所以傳送方現在認為網路可能沒有出現擁塞。所以此時不執行慢開始演算法,而是將 cwnd 設定為 ssthresh 的大小,然後執行擁塞避免演算法

從連續收到3個重複的確認轉入擁塞避免示意圖:

從整體上來講, TCP 擁塞控制視窗變化的原則是加法增大、乘法減小

TCP網路程式設計

在網路中,程式使用三元組 (Ip地址,協議,埠)來標識網路中的程式,網路中的程式通訊就可以利用這個標誌與其他程式進行互動。

網路中的程式是通過 socket 來通訊的,用 "開啟 (open) -> 讀寫 (write/read) -> 關閉 (close)" 模式來操作。 socket 是一種特殊的檔案, 一些 socket 函式就是對其進行的操作(讀/寫 開啟、關閉)。

使用 TCP/IP 協議的應用程式通常採用應用程式設計介面: UNIX BSD 的套接字 (socket),來實現網路程式之間的通訊。

以 TCP 協議通訊的 socket 為例,其互動流程大概如圖所示:

具體流程如下:

  1. 伺服器根據地址型別( ipv4, ipv6 )、 socket 型別、協議建立 socket
  2. 伺服器為 socket 繫結 IP 地址和埠號
  3. 伺服器 socket 監聽埠號請求,隨時準備接收客戶端發來的連線,這時候伺服器的 socket 並沒有被開啟
  4. 客戶端建立 socket
  5. 客戶端開啟 socket,根據伺服器 IP 地址和埠號試圖連線伺服器 socket
  6. 伺服器 socket 接收到客戶端 socket 請求,被動開啟,開始接收客戶端請求,直到客戶端返回連線資訊,這時候 socket 進入阻塞狀態,所謂阻塞即 accept()方法一直到客戶端返回連線資訊後才返回,開始接收下一個客戶端連線請求
  7. 客戶端連線成功,向伺服器傳送連線狀態資訊
  8. 伺服器 accept 方法返回,連線成功
  9. 客戶端向 socket 寫入資訊
  10. 伺服器讀取資訊
  11. 客戶端關閉
  12. 伺服器端關閉

TCP 頭部的選項部分是為了 TCP 適應複雜的網路環境和更好地服務於應用層而進行設計的。TCP 選項部分最長可以達到 40 Byte ,再加上 TCP 選項外的固定的 20 Byte 部分, TCP的最長頭部可達 60 Byte。TCP 頭部長度可以通過 TCP 頭部中的“資料偏移”位來檢視。值得注意的是: TCP 偏移量的單位是 32bit ,也就是 4Byte。TCP 偏移量共佔 4bite,取最大 llll (B) 計算也就是十進位制的 15。15*4 Byte=60 Byte,這個也是 TCP 首部不超過 60 Byte 的原因。

網路位元組序與主機序

不同的 CPU 有不同的位元組序型別,這些位元組序是指整數在記憶體中儲存的順序,稱為主機序。最常見的有兩種:1. Little Endian ,將低序位元組儲存在起始地址;2. Big Endian,將高序位元組儲存在起始地址

大小端儲存數字 Oxl2345678 示意圖:

C/C++ 語言編寫的程式裡資料儲存順序是跟編譯平臺所在的 CPU 相關的,而 Java 編寫的程式則唯一採用 Big Endian 方式來儲存資料,所有網路協議也都是採用 Big Endian 的方式來傳輸資料的。所以有時也會把 Big Endian 方式稱之為網路位元組序。當兩臺採用不同位元組序的主機通訊時,在傳送資料之前都必須經過位元組序的轉換成為網路位元組序後再進行傳輸。

TCP 是個“流”協議,所謂流,就是沒有界限的一串資料。在進行資料傳輸時,由於網路或Nagle 演算法的原因會導致“粘包”的情況,即,加上要同時傳送A和B資料,A中的部分資料和B一起被接收或B中的部分資料和A一起被接收。可使用封包和拆包的方法來解決該問題。由於UDP本身就是個資料包協議,因此不存在該問題。

封包就是給一段資料加上包頭,這樣一來資料包就分為包頭和包體兩部分內容了。包頭其實上是個大小固定的結構體,其中有個結構體成員變數表示包體的長度,這是個很重要的變數,其他的結構體成員可根據需要自己定義。根據固定的包頭長度以及包頭中含有的包體長度的變數值就能正確的拆分出一個完整的資料包。所以為了解決“粘包”的問題,大家通常會在所傳送的內容前加上傳送內容的長度,所以
對方就會先收4 Byte,解析獲得接下來需要接收的長度,再進行收包。

相關文章