Linux 高效能伺服器程式設計- TCP 協議詳解

Dragonbuf發表於2018-10-22
TCP 協議是 更靠近應用層,因此在應用程式中具有更強可操作性,一些重要 socket 選項都和 TCP  協議相關。
  • TCP 頭部資訊:TCP 頭部資訊出現在每個 TCP 報文段中,用於指定通訊的源端埠號、目的端埠號、管理 TCP 連線、控制兩個方向的資料流。
  • TCP 狀態轉移資訊:TCP 連線的任意一端都是一個狀態機。在 TCP 連線從建立到斷開的整個過程中,連線兩端的狀態機將經歷不同的狀態變遷。
  • TCP 資料流:通過分析 TCP 資料流,我們可以從網路應用程式外部來了解應用層協議和通訊雙方交換的應用層資料。
  • TCP 資料流的控制:為了保證可靠傳輸和提高網路通訊質量,核心需要對 TCP 資料流進行控制。

3.1 TCP 服務的特點

TCP 協議相對 UDP 協議主要:面向連線、資料流、可靠傳輸。

使用 TCP 協議的雙方必須先建立連線,然後才能開始資料的讀寫。雙方必須為連線分配必要的核心資源,來管理連線的狀態和連線上傳輸的資料。TCP 是全雙工的,即雙方的讀寫可以通過一個連線進行。資料交換完畢後,雙方必須斷開連線以釋放資源。

TCP 連線是一對一的,所以基於廣播和多播(目標是多個主機地址)的應用程式不能使用 TCP 服務,而無連線的 UDP 則非常適合廣播和多播。
當傳送端應用程式連續執行多次寫操作時,TCP 模組先將這些資料放入 TCP 傳送快取區中,當 TCP 模組真正開始傳送資料時,傳送緩衝區將這些等待的資料可能封裝成一個或多個 TCP 報文段發出,因此 TCP 模組發出的 TCP 報文段的個數和應用程式的寫操作之間沒有固定的數量關係。

這就是位元組流:應用程式對資料的傳送和接收是沒有邊界限制的。

UDP 傳送端每一次寫操作, UDP 模組就將其封裝成 UDP 資料包併傳送。接收端必須及時針對每一個 UDP 資料包執行讀取操作(通過 recvform 系統呼叫),否則會丟包。並且如果使用者沒有指定足夠的應用程式緩衝區來讀取 UDP 資料,則 UDP 資料將被截斷。

file

TCP 協議是可靠的

TCP 協議採用傳送應答機制,即傳送端傳送的每個 TCP 報文段都必須得到接收方的應答,才能認為這個 TCP 報文段傳輸成功。TCP 協議採用超時重傳機制,傳送端在傳送一個 TCP 報文段後啟動定時器,如果在指定時間內未收到應答,它將重發該報文段。因為 TCP 報文段最終是以 IP 資料包傳送的,而 IP 資料包達到接收端可能亂序、重複,所以 TCP 協議還會對接受到的 TCP 報文段重排、整理,在交付給應用層。
UDP 協議和 IP 協議一樣,提供不可靠服務。他們都需要上層協議來處理資料確認和超時重傳。

3.2 TCP 頭部結構

TCP 頭部資訊出現在每個 TCP 報文段中,用於指定通訊的源端埠、目的端埠、管理 TCP 連線等。

3.2.1 TCP 固定頭部結構

file

  • 16位埠號:告知主機該報文段來自哪裡(源埠號)以及傳給哪個上層協議或應用程式(目的埠)。進行 TCP 通訊時,客戶端通常使用系統自動選擇的臨時埠號,而伺服器使用知名伺服器埠好。
  • 32位序號(sequence number): 一次 TCP 通訊過程中某一個傳輸方向上的位元組流的每個位元組的編號。 假設主機 A 傳送給 B 第一個 TCP 報文段,序號值被初始化為某個隨機值 ISN ,那麼在該傳輸方向上(從 A 到 B ),後續的 TCP 報文段中序號值將被系統設定成 ISN 加上該報文段所攜帶資料的第一個位元組在整個位元組流中的偏移, 如某個 TCP 報文段傳送的資料是位元組流中的第 1025 - 2048 位元組,那麼該報文段的序號值就是 ISN + 1025 。
  • 32 位確認號(acknowlegegment number): 用作對另一方傳送來的 TCP 報文段的響應,其值是收到的 TCP 報文段的序號 + 1. 假設 A 和 B 進行 TCP 通訊,那麼 A 傳送出的 TCP 報文段不僅攜帶自己的序號,而且包含對 B 傳送來的 TCP 報文段的確認號,反之,B 傳送出的 TCP 報文段也同時攜帶自己的序號和對 A 傳送來的報文段的確認號。
  • 4位頭部長度:標識該 TCP 頭部有多少個 32 bit 字(4位元組),因為 4 位元組最大能表示 15,所以 TCP 頭部最長 60 位元組。
  • 6位標誌位包含:
    • URG 標誌: 表示緊急指標是否有效
    • ACK 標誌: 表示確認號是否有效。 攜帶 ACK 標誌的 TCP 報文段為確認報文段
    • PSH 標誌: 表示接收端應用程式應該立即從 TCP 緩衝區中讀取資料,為接受後續資料騰出空間(應用程式不將接受到的資料讀走,他們會一直停留在 TCP 接收緩衝區中)
    • RST 標誌:表示要求對方重新建立連線。攜帶 RST 標誌的 TCP 報文段為復位報文段。
    • SYN 標誌: 表示請求建立一個連線。 攜帶 SYN 標誌的 TCP 報文段為結束報文段。
  • 16 位視窗大小: TCP 控制流量的手段。這裡的視窗是指通告視窗(Reveiver Window,RWND),它告訴對方本段的 TCP 接受緩衝區還能容納多少位元組的資料。
  • 16 位校驗和(TCP checksum): 由傳送端填充,接收端對 TCP 報文段執行 CRC 演算法以校驗 TCP 報文段在傳輸過程中是否損壞。校驗頭部及資料部分。是 TCP 可靠傳輸的一個重要保障
  • 16 位緊急指標:偏移量。 它和序號欄位的值相加表示最後一個緊急資料的下一位元組的序號。TCP 的緊急指標是傳送端向接收端傳送緊急資料的方法。

3.2.2 TCP 頭部選項

TCP頭部的最後一個選項欄位是可變長的可選資訊。這部分最多包含 40 位元組,因為TCP 頭部最長 60 位元組(20 位元組固定部分)。

file
kind : 選項的型別。 有的 TCP 選項沒有後面兩個欄位。
length : 如果有,指定該選項的總長度。包括 kind 、length 佔據的 2 位元組
info : 如果有,是選項的具體資訊
file
kind = 0 表示選項表結束選項。
kind = 1 是空操作(nop)選項,沒有特殊含義,一般用於將 TCP 選項的總長度填充位 4 位元組的整倍數。
kind = 2 是最大報文段長度選項。TCP 連線初始化時,通訊雙方使用該選項來協商最大報文長度(Max Segment Size).TCP 模組通常將 MSS 設定為 (MTU-40)位元組(減掉40 位元組 為 20位元組的 TCP 頭部 和 20 位元組的 IP 頭部)。這樣攜帶 TCP 報文段的 IP 資料包長度就不會超過 MTU,從而避免本機發生 IP 分片。對乙太網而言,MSS 的值是 1460 (1500-40)位元組。
kind = 3 是視窗擴大因子選項。 TCP 連線初始化時,通訊雙方使用該選項來協商接受通告視窗的擴大因子。在 TCP 的頭部中,接受通告視窗大小是用 16 位表示的,故最大位 65535 位元組,但實際上 TCP 模組允許的接受通告視窗大小遠不止這個數(為了提高 TCP 通訊的吞吐量)。視窗擴大因子 解決了這個問題。假設 TCP 頭部中的接受通告視窗大小是 N ,視窗擴大因子(位移數)是 M ,那麼 TCP 報文段的實際接受通告視窗大小是N 乘 2^M ,或者說 N 左移 M 位。 M取值範圍 0-14。視窗擴大因子只能出現在同步報文中,否則將被忽略。

3.3 TCP連線的建立和關閉

3.3.1

TCP 連線的建立和關閉時序圖

file

第 1 個 TCP 報文段包含 SYN 標誌,因此為同步報文段,即客戶端向伺服器發起連線請求。該同步報文段包含一個 ISN值為 535734930
的序號。
第 2 個 TCP 報文段也是同步報文段,表示伺服器同意與客戶端建立連線,同時傳送自己的 ISN 值為 2159701207 的序號,並對第 1 個同步報文段進行確定,確認值是535734931,即源值 +1,序號值是用來標識 TCP 資料流重的每一個位元組的,但是同步報文段比較特殊,即使沒有攜帶任何應用程式資料,它也要佔用一個序號值
第 3 個 TCP 報文段是客戶端對第 2 個同步報文段的確認,至此,TCP 連線建立起來了。

建立 TCP 連線的這 3 個步驟被成為 TCP三次握手
第 4 個 TCP 報文段包換 FIN 標誌,因此它是一個結束報文段,即客戶端要求關閉連線。結束報文段和開始報文段一樣,都要佔用一個序號值。
伺服器用 TCP 報文段 5 來確認該結束報文段,同時傳送自己的結束報文段 6, 客戶端則用 TCP 報文段 7 給予確認。
實際上 僅用確認的 報文段 5 是可以省略的,因為結束報文段 6 也攜帶了該確認資訊,確認報文段 5 是否出現在連線斷開的過程中,取決於 TCP 的延遲確認特性。

3.3.2 半關閉狀態

TCP 連線是全雙工的,所以它允許兩個方向的資料傳輸被獨立關閉。

通訊的一端可以傳送結束報文段給對方,告訴它本段已經完成了資料的傳送,但允許繼續接受來自對方的資料,直到對方也傳送結束報文段以關閉連線。 TCP 連線的這種狀態成為半關閉(half close)狀態。
file
伺服器和客戶端應用程式判斷對方是否已經關閉連線的方法是: read 系統呼叫返回 0 (收到結束報文段).

3.3.3 連線超時

  對於提供可靠服務的 TCP 來說,它必然是先進行重連(可能執行多次),如果重連無效,通知應用程式連線超時。

3.4 TCP 狀態轉移

TCP 連線的任意一端在任一時刻都處於某種狀態。

file
粗虛線表示典型的伺服器端連線的狀態轉移:粗實線表示典型的客戶端連線的狀態轉移。

3.4.1 TCP 狀態轉移總圖

伺服器通過 listen 系統呼叫進入 LISTEN 狀態,被動等待客戶端連線,因此執行的是所謂的被動開啟。

伺服器一旦監聽到某個連線請求(收到同步報文段),就將該連線放入核心等待佇列中,並向客戶端傳送 SYN 標誌的確認報文段。此時該連線處於 SYN_RCVD 狀態。如果伺服器成功地接受到客戶端發回的確認報文段,則該連線轉移到 ESTABLISHED 狀態(連線雙方都能夠進行雙向資料傳輸的狀態)。
當客戶端主動關閉連線時(通過 close 或 shutdown 系統呼叫向伺服器傳送結束報文段),伺服器通過返回確認報文段使連線進入 CLOSE_WAIT 狀態(等待伺服器應用程式關閉連線)。通常伺服器檢測到客戶端關閉連後,也會立即向客戶端傳送一個結束報文段來關閉連線。狀態變為 LAST_ACK 狀態,以等待客戶端對結束報文段的最後一次確認。確認完成,連線徹底關閉。

客戶端通過 connect 系統呼叫主動與伺服器建立連線, connect 系統呼叫首先給伺服器傳送一個同步報文段,使連線轉移到 SYN_SENT 狀態。此後 connect 系統呼叫可能因為 如下兩個原因失敗返回:
1 如果 connect 連線的目標埠不存在(未被任何程式監聽),或者該埠仍被處於 TIME_WAIT 狀態的連線所佔用,則伺服器將給客戶端傳送 復位報文段, connect 呼叫失敗
2 如果 connect 目標埠存在,在超時時間範圍內伺服器未 返回確認報文段,則 connect 呼叫失敗
connect 呼叫失敗,將使連線返回到初始的 CLOSE 狀態。 如果客戶端成功收到伺服器的 同步報文段和確認,則 connect 呼叫成功,連線轉移至 ESTABLISHED 狀態。
當客戶端執行主動關閉時,它向伺服器傳送一個 結束報文段,同時連線進入 FIN_WAIT_1 狀態。如果客戶端此時收到伺服器專門使用者確認的確認報文段,連線進入 FIN_WAIT_2 狀態,伺服器處於 CLONSE_WAIT 狀態,這一對狀態是可能發生半關閉的狀態。此時如果伺服器也傳送結束報文段,則客戶端予以確認並進入 TIME_WAIT 狀態。

客戶端直接從 FIN_WAIT_1 狀態進入 TIME_WAIT 狀態,是 FIN_WAIT_1狀態的伺服器直接收到帶確認資訊的結束報文段(不是先收到確認報文段,在收到結束報文段)

處於 FIN_WAIT_2狀態的客戶端需要等待伺服器傳送結束報文段才能轉移至 TIME_WAIT 狀態,否則將一直停留在此狀態。如果不是為了在半關閉狀態接受資料,連線長時間停留在 FIN_WAIT_2 毫無意義。 連線停留在 FIN_WAIT_2 原因:

  • 客戶端執行半連線後,未等待伺服器關閉連線就強行退出了。(此時客戶端連線由核心接管,可成為孤兒連線,Linux 為了防止孤兒連線長時間停留在核心中,定義了兩個核心變數,指定核心能接管的孤兒連線數量和孤兒連線在核心中的生存時間)
    file

3.4.2 TIME_WAIT 狀態

客戶端連線在收到伺服器的結束報文段後,並沒有直接進入 CLOSE 狀態,而是轉移到了 TIME_WAIT 狀態。
在這個狀態客戶端需要在 2 MSL (maximum segment life 報文段最大生存時間,2 min)的時間,才能完全關閉。

TIME_WAIT 狀態存在的原因:

  • 可靠的終止 TCP連線
  • 保證 讓遲來的 TCP 報文端由足夠的時間被識別並丟棄

1 假設使用者確認伺服器結束報文段6 的TCP 報文段 7 丟失, 那麼伺服器將重結束報文段,因為客戶端需要停留在某個狀態來處理伺服器重發的結束報文段(即向伺服器傳送確認報文段),否則客戶端將以復位報文段回覆伺服器,伺服器認為這是一個錯誤,因為它期望的是 像報文段 7 那樣的確認報文段。
2 在 LINUX 系統中,一個 TCP 埠不能被同時開啟多次。如果不存在 TIME_WAIT 狀態,則應用程式可能建立一個和剛關閉連線相似的連線,這個連線可能收到原來連線的 TCP 報文段。

3.5 復位報文段

3.5.1 訪問不存在的埠

當客戶端訪問一個不存在的埠時,目標主機將給它傳送一個復位報文段。

收到復位報文段的一端應該關閉連線或者重新連線,而不能回應這個復位報文段。實際上,當客戶端向伺服器的某個埠發起連線,而該埠處於 TIME_WAIT狀態的連線所佔用時,客戶端程式也將收到復位報文段。

3.5.2 異常終止連線

TCP 提供了異常終止一個連線的方法,即給對方傳送 一個復位報文段。一旦傳送了復位報文段,傳送端所有排隊等待傳送的資料將被丟棄。

3.5.3 處理半開啟連線

如下情況:
1 伺服器或客戶端關閉或異常終止了連線,而對方沒有收到結束報文段,此時客戶端或伺服器還保持著原來的連線,而伺服器或客戶端即使重啟,也沒有該連線的任何資訊了,這種狀態被成為半開啟連線。
如果往半開啟的連線寫入資料,則對方回應一個復位報文段。

3.6 TCP 互動資料流

TCP 報文段所攜帶的應用程式資料根據長度分為:互動資料和成塊資料。

互動資料僅包含較少的位元組,使用互動資料的應用程式對實時性要求較高,如 telnet、ssh 等。成塊資料一般為 TCP 報文段允許的最大長度,使用成塊資料的對傳輸效率要求高,如 ftp。以下介紹互動資料:
廣域網上的互動資料可能收到很大的延遲,並且攜帶互動資料的 微小TCP報文段特別多,這些因素可能導致阻塞。可使用 Nagle 演算法。
Nagle 演算法要起一個 TCP 連線的通訊雙方任意時刻只能傳送一個未被確認的TCP 報文段,在該 TCP 報文段未被確認前不能傳送其他 TCP 報文段,另一方面,傳送方在等待確認的同時蒐集本端需要傳送的微量資料,並在確認到來時以一個TCP 報文段將他們全部發出,這樣就極大減少了網路上的微小TCP報文段的數量,該演算法的另一個優點:確認到達的越快,資料也就傳送的越快。

3.7 TCP 成塊資料流

當傳輸大量大塊資料的時候,傳送方會連續傳送多個TCP 報文段,接收方可以一次確認這些報文段。

伺服器每傳送 4 個TCP 報文段就傳輸一個 PSH標誌給客戶端,通知客戶端的應用程式儘快讀取資料,不過這對伺服器不是必須的,因為它知道客戶端的 TCP 接受緩衝區中還有空間(接受通告視窗不為0)。

3.8 帶外資料

相關文章