【計算機網路筆記】3.5 Connection-Oriented Transport: TCP

Hello Guys, I’m LukeTseng. 歡迎你也感謝你點入本篇文章,本系列主要讀本為《Computer Networking: A Top-Down Approach, 8th Edition》,就是計算機網路的聖經,會製作該系列也主要因為修課上會用到。若你喜歡本系列或本文,不妨動動你的手指,為這篇文章按下一顆愛心吧,或是追蹤我的個人公開頁也 Ok。

3.5.1 The TCP Connection(TCP 連線)

TCP 連線是一種在兩個通訊端點之間所建立的「邏輯性、全雙工(full-duplex)且點對點(Point-To-Point)」的可靠傳輸管道。

底層的網路層(Network Layer)所使用的網際網路協定(Internet Protocol, IP)只提供盡力而為(best-effort)且不可靠的服務。而 TCP 透過在端點建立連線狀態,補足底層的缺陷,確保資料不會遺失、損壞或失序。

TCP 適用於網頁瀏覽、檔案傳輸等不容許資料遺失的應用。

TCP 最大限制是只能一對一通訊,即「兩個主機恰恰好,三個主機太擁擠」,TCP 絕對無法支援將資料一次發送給多個接收者的群播(Multicasting)機制。

術語解析

  • 連線導向(Connection-oriented):雙方應用程式在開始互傳資料前,必須先互相握手(它們必須送出一些初始區段(Segment)給對方),以初始化各種 TCP 狀態變數與緩衝區。
  • 全雙工服務(Full-duplex service):連線建立後,資料可以同時在兩個方向上流動。也就是主機 A 傳資料給主機 B 的同時,主機 B 也能將資料傳給主機 A。
  • 最大傳輸單位(Maximum Transmission Unit, MTU):本地端主機所能送出的最大連結層訊框(Link-layer frame)大小(在乙太網路中通常為 1500 bytes)。
  • 最大區段大小(Maximum Segment Size, MSS):
    • TCP 從緩衝區中抓取並放入單一個 TCP 區段內的最大應用層資料量。
    • MSS 的大小取決於 MTU,以確保 TCP 區段加上標頭後剛好能塞進一個 MTU 中。

邏輯連線

TCP 連線是一種邏輯連線(Logical connection),一種不依賴實體線路,而透過協定與軟體在兩個端點之間建立的虛擬通道。

而邏輯連線會導致:

  • 狀態只存在於邊緣:TCP 連線的狀態(含變數與緩衝區)只存在於通訊的兩終端系統(End systems)之中。
  • 中間路由器一無所知:位於網路核心的路由器或交換器,完全不知道 TCP 連線的存在,它們看到的是資料包(Datagrams),並不會為 TCP 連線保留任何記憶體或狀態。

三次握手(three-way handshake)

在資料傳輸前,客戶端(Client)與伺服器(Server)必須進行三次交握:

  1. 客戶端送出一個不含應用程式資料的特殊 TCP 區段(SYN 區段)給伺服器。
  2. 伺服器收到後,回傳第二個特殊的 TCP 區段(SYNACK 區段),表示同意建立連線,並分配所需資源。
  3. 客戶端再回傳第三個區段做為確認,此時該區段已可攜帶實際的應用程式資料了。

在撰寫網路程式時,只要呼叫 connect(),作業系統底層就會自動完成上述的三個步驟:

1
clientSocket.connect((serverName, serverPort))

資料傳輸的生命週期

步驟發生位置說明
1. 寫入發送端(Sender)應用程式行程(Process)將資料流寫入 Socket。
2. 暫存發送端資料進入 TCP 為該連線專屬保留的傳送緩衝區(Send Buffer)。
3. 切割發送端TCP 依照自己的步調,不定期從緩衝區抓取不超過 MSS 大小的資料片段。
4. 封裝發送端TCP 為每個資料片段加上 TCP 標頭(TCP header),形成 TCP 區段(TCP Segment),並往下交給網路層封裝成 IP 資料包。
5. 接收接收端(Receiver)資料抵達後,TCP 會將資料放入該連線專屬的接收緩衝區(Receive Buffer)中。
6. 讀取接收端接收端的應用程式再依照自己的需求與速率,從接收緩衝區中讀取資料流。

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 259, Figure 3.28)

3.5.2 TCP Segment Structure(TCP 區段結構)

TCP 區段(Segment)是 TCP 協定用來封裝與傳輸資料的基本單位,由以下兩種東西構成:

  1. 標頭欄位
  2. 資料欄位

術語解析

  • 最大區段大小(Maximum Segment Size, MSS):在此指的是 TCP 區段中資料欄位所能容納的最大位元組數,並不包含標頭大小。發送大檔案時,資料會被切割成多個 MSS 大小的區塊。
  • 位元組串流(Byte Stream):TCP 將資料視為一連串無結構但有順序的位元組流,而非一個個獨立的封包,TCP 的序號與確認號都是基於位元組來計算的。
  • 累積式確認(Cumulative Acknowledgment):TCP 的確認機制不會針對每個收到的封包回傳確認,而是告訴發送方在該編號之前的所有位元組,都已收到了。
  • 便車(Piggybacking):TCP 允許把「對先前收到資料的確認號」與「自己要發送的資料」打包在同一個 TCP 區段中一起送出,以節省網路頻寬。

TCP 標頭欄位總覽

TCP 標頭長度通常為 20 個 Bytes,比 UDP 標頭多了 12 個位元組。

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 261, Figure 3.29)

可從以下表格來對照各個欄位的功能:

欄位名稱長度核心功能與說明
來源/目的埠號(Source / Dest Port #)各 16 bits用於多工與解多工(Multiplexing / Demultiplexing),將資料正確送達指定的應用程式。
序列號碼(Sequence Number)32 bits標示此區段資料欄位中第一個位元組在整體資料流中的編號,用於可靠傳輸。
確認號碼(Acknowledgment Number)32 bits標示接收方期望收到的下一個位元組編號,用於累積式確認。
標頭長度(Header Length)4 bits標示 TCP 標頭的總長度(以 32-bit 的字組 [word] 為單位),因為有選項欄位,標頭長度是可變的(通常是 20 bytes)。
旗標欄位(Flags)6 bits包含 ACK, SYN, FIN, RST, PSH, URG,用於連線管理或狀態標示(詳見後述)。
接收視窗(Receive Window)16 bits告訴發送方目前接收端還有多少緩衝區空間,用於流量控制(Flow control)。
檢查和(Internet Checksum)16 bits用於偵測資料在傳輸過程中是否發生位元錯誤。
緊急資料指標(Urgent Data Pointer)16 bits搭配 URG 標記使用,指出緊急資料的結尾位置(實務上已極少使用)。
選用欄位(Options)可變長度用於協調 MSS 大小、視窗縮放因數(Window scaling)或時間戳記(Time-stamping)。

序列號碼與確認號碼

這兩個欄位是 TCP 區段標頭中最重要的兩個,另外在此需要注意 TCP 是在算「位元組」,不是算「封包」。

  1. 序號的計算:假設使用者正在傳送一個 $500,000$ bytes 的檔案,MSS 為 $1,000$ bytes,如果初始序號(第一個位元組)是 $0$,那麼 TCP 會從這筆檔案建立出 500 份區段:
    • 第 1 個區段:裝載 bytes 0 ~ 999,指派給序號 0。
    • 第 2 個區段:裝載 bytes 1000 ~ 1999,指派給序號 1000。
    • 第 3 個區段:裝載 bytes 2000 ~ 2999,指派給序號 2000。
    • (註:實務上,連線雙方會隨機選擇初始序號,以防止舊連線的延遲封包造成干擾。)
  2. 確認號的計算(累積式確認):假設主機 A 已從主機 B 成功收到了 bytes 0 ~ 535。
    • 現在 A 要傳資料給 B,A 會在 TCP 標頭的確認號欄位填入 536,表示 535 以前的都已收到,現在請求位元組 536。
    • 如果 A 先收到了 bytes 0 ~ 535,卻又提早收到了 bytes 900 ~ 1000(失序到達)。
      • 此時 A 送給 B 的確認號仍然是 536,因為 536 才是 A 真正缺失並等待的第一個位元組。

上面在做的事情就如下圖所示:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 262, Figure 3.30)

旗標欄位(Flags)

標頭中的 6 個標記位元就像封包的行為指示燈:

  • ACK:若設為 1,表示此區段內的確認號是有效且有意義的。
  • SYN, FIN, RST:分別用於建立連線、結束連線、以及重設中斷異常連線(連線管理的基石)。
  • PSH(Push):指示接收方的 TCP 應盡快將資料往上交給應用程式,不要在緩衝區逗留。
  • URG(Urgent):表示資料中有需要優先處理的緊急資料。

Telnet:序號與確認編號的案例

Telnet 定義於 RFC 854,是常用的遠端登入應用層協定,執行於 TCP 上,用於在任意一對主機間運作。(現今常用 SSH(Secure Shell)連線,因為 Telnet 沒加密)

假設主機 A 開啟(客戶端)一筆與主機 B(伺服器)的 Telnet 會談(Session),Client 初始序號為 42,Server 初始序號為 79。

使用者(於客戶端)鍵入的每個字元會傳給遠端主機,而遠端主機也會回傳每個字元的副本,並顯示於 Telnet 使用者的螢幕上。

現在 Client 的使用者在鍵盤上敲了一個字元 C

步驟方向欄位變化說明
1Client -> ServerSeq=42(Client 送出之區段序號),
ACK=79(Server 送出之區段序號),
Data=‘C’(1 byte)
1. Client 傳送字母 ‘C’。
2. 因為尚未收到 Server 的資料,所以預期 Server 送來的下一筆資料序號仍是 79。
2Server → ClientSeq=79, ACK=43, Data=‘C’(1 byte)1. Server 收到字元 ‘C’ 後,為了讓使用者的螢幕能顯示,將 ‘C’ 迴響(Echo back)給 Client。
2. 此時利用便車(piggybacked)機制,將 ACK 設為 43(因為 42 已收到,期待 43)
3Client → ServerSeq=43, ACK=80, Data=(空)1. Client 收到 Server 的迴響 ‘C’,回覆確認。
2. 期待 Server 的下一個位元組是 80(79+1),此封包不含應用層資料

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 264, Figure 3.31)

小結

  1. 位元組流:TCP 的 Seq/ACK 並不是以第幾個封包來計算,TCP 永遠是在算第幾個位元組(Byte)。
  2. ACK 是一種期待:TCP 的確認號(ACK number)不是回傳收到了什麼,而是宣告接下來想要什麼。
  3. 標頭大小的代價:TCP 雖提供了極可靠的服務,但每個封包至少要付出 20 bytes 的標頭成本。
    • 若如 Telnet 一次只傳送 1 byte 的按鍵字元,加上 IP 標頭的 20 bytes,總封包大小至少高達 41 bytes(其中只有 1 byte 是真的資料)。

3.5.3 Round-Trip Time Estimation and Timeout(來回時間的評估與逾時)

這節簡單來講就是:TCP 透過持續觀測網路當下的傳輸延遲,動態計算並調整其逾時重傳計時器(Retransmission Timer)。

在實際實作 TCP 逾時 / 重送機制時會有以下問題:

在網路中,封包遺失的主要特徵是等不到確認回覆(ACK),如果 TCP 的等待時間(逾時區間)設定得太短,會導致不必要的提早重傳(浪費頻寬);如果設定得太長,當封包真的遺失時,TCP 的反應會太慢(降低傳輸吞吐量)。

那這等待時間到底要設多少?接下來該節的討論會告訴我們。

術語解析

  • 樣本來回時間(SampleRTT):針對某一個剛剛發送的 TCP 區段,從送出到收到對應 ACK 所花費的實際測量時間。
  • 估計來回時間(EstimatedRTT):TCP 在內部維護的 RTT 平滑平均值,過濾單一封包延遲的劇烈波動,代表目前網路的延遲狀況。
  • 來回時間變異值(DevRTT):估計 SampleRTT 偏離 EstimatedRTT 程度的指標,若網路延遲起伏很大,這個值就會變大。
  • 逾時區間(Timeout Interval):TCP 計時器最終設定的倒數時間,只要超過這個時間沒收到 ACK,TCP 就會認定封包遺失並觸發重傳。

估計來回時間

步驟一:測量與取樣

TCP 會在運作過程中持續抓取封包來測量 SampleRTT。

但在此有非常重要的細節:TCP 絕對不會去測量重傳封包的 SampleRTT,只會針對傳輸過一次的封包進行測量。

為什麼不測重傳的封包?當收到 ACK 時,根本不知道該 ACK 是針對第一次發送的,還是針對第二次重傳的。

因此 TCP 只會估計其中某一區段的 SampleRTT,如此一來,大約每隔一次 RTT 就會得到一筆新的 SampleRTT 值。

步驟二:計算平滑平均值

由於路由器緩衝區的壅塞程度隨時在變,單一個 SampleRTT 可能具有極端值。

TCP 採用了統計學上的指數加權移動平均(Exponential Weighted Moving Average, EWMA)來更新 EstimatedRTT: $$\text{EstimatedRTT} = (1 - \alpha) \cdot \text{EstimatedRTT} + \alpha \cdot \text{SampleRTT}$$

  • RFC 6298 建議的 $\alpha$ 值為 $0.125$(亦即 $\frac{1}{8}$)。
  • 物理意義:新的估計值是歷史經驗( $87.5%$ )與最新現況( $12.5%$ )的加權總和,越近的樣本權重越高,越舊的樣本權重會以指數方式衰減,讓 TCP 能平滑地適應網路壅塞的變化。

步驟三:計算延遲的變異程度

只知平均值不夠,若網路極度不穩定(忽快忽慢),必須把逾時時間設得比平均值更寬裕一點,因此 TCP 會計算樣本與平均值之間的差異(DevRTT): $$\text{DevRTT} = (1 - \beta) \cdot \text{DevRTT} + \beta \cdot \lvert \text{SampleRTT} - \text{EstimatedRTT} \rvert$$

  • RFC 6298 建議的 $\beta$ 值為 $0.25$(即 $\frac{1}{4}$)。
  • DevRTT 為 SampleRTT 跟 EstimatedRTT 差值的 EWMA,若 SampleRTT 值少變動,則 DevRTT 的值就會變很小;反之,SampleRTT 值變動大,則 DevRTT 的值就會變很大。

步驟四:設定最終的逾時區間

有了平均值與變異值,TCP 就能計算出兼顧效率與安全的逾時區間: $$\text{TimeoutInterval = EstimatedRTT} + 4 \cdot \text{DevRTT}$$

  • 物理意義:逾時時間 = 平均延遲時間 + 4 倍的安全邊際(Margin)。
    • 當網路震盪劇烈時,DevRTT 變大,安全邊際擴大,避免太早觸發逾時;當網路穩定時,DevRTT 變小,逾時s時間緊貼平均值,提升偵測遺失的效率。

下圖 3.32 表示對於某比介於 gaia.cs.umass.edufantasia.eurecom.fr 間的 TCP 連線,以 $\alpha = \frac{1}{8}$ 估計出的 SampleRTT 跟 EstimatedRTT 值。SampleRTT 的變動會在 EstimatedRTT 的計算過程中被平滑化。

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 268, Figure 3.32)

觸發逾時後的特殊處理機制

當逾時發生時,TCP 的行為會有所改變,可用下表來對照:

事件TimeoutInterval 的處理方式發生原因與設計方法
正常收到 ACK根據最新測量的 RTT 更新,並代入上述公式重新計算。網路暢通,依據最新的觀測數據動態調整計時器。
發生逾時直接將目前的 TimeoutInterval 乘以 2。逾時通常表示網路遇到嚴重的壅塞。
如果此時還用短時間的計時器一直重傳,只會讓網路塞得更嚴重。因此 TCP 會選擇主動放慢自己的重傳頻率,讓網路有喘息的空間。此為一種早期的壅塞控制機制。

註:TCP 連線剛建立時,因為還沒有任何樣本,初始的 TimeoutInterval 建議值為 1 秒。

3.5.4 Reliable Data Transfer(可靠資料傳輸)

回顧:底層的網際網路協定(Internet Protocol, IP)僅提供盡力而為(best-effort)的服務,不保證資料包(Datagram)的傳輸,也不保證順序與完整性。

TCP 透過結合「序號、累積式確認、單一計時器與重傳機制」,在不可靠的 IP 網路上,為應用程式提供無損壞、無間隙、無重複且按序到達的位元組資料流服務。

術語解析

  • 單一重傳計時器(Single Retransmission Timer):TCP 雖可能同時送出多個未被確認的區段,但為了減少系統負擔,TCP 建議只使用單一個計時器,通常與最舊的未確認區段綁定。
  • 指數退讓(Exponential Backoff):當發生逾時重傳時,TCP 會將下一次的逾時區間加倍,而不是用原本估算的 RTT,避免在網路壅塞時火上加油。
  • 快速重傳(Fast Retransmit):利用連續收到三個重複的確認(Duplicate ACKs)作為封包遺失的強烈暗示,在計時器倒數結束前就提早觸發重傳。

簡化版 TCP 傳送端的三大事件

要了解 TCP 傳送端的行為,可將其視為一個狀態機,主要對以下三個事件做出反應:

  1. 從上層應用程式收到資料:
    • TCP 將資料封裝成區段,賦予序號。
    • 如果計時器沒有在運作,就啟動計時器。
  2. 發生逾時:
    • 重傳序號最小且尚未被確認的區段。
    • 重新啟動計時器。
  3. 收到確認:
    • 將收到的 ACK 號碼與變數 SendBase(最早未確認的位元組序號)進行比較。
    • 如果 ACK 號碼大於 SendBase,代表有新的資料被成功確認,更新 SendBase
    • 如果更新後還有其他未被確認的區段,則重新啟動計時器。

以上這三個的邏輯如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Assume sender is not constrained by TCP flow or congestion control, that data from above is less than MSS in size, and that data transfer is in one direction only. */

NextSeqNum=InitialSeqNumber
SendBase=InitialSeqNumber

loop (forever) {
switch(event)

event: data received from application above
create TCP segment with sequence number NextSeqNum

if (timer currently not running)
start timer
pass segment to IP
NextSeqNum=NextSeqNum+length(data)
break;

event: timer timeout
retransmit not-yet-acknowledged segment with
smallest sequence number
start timer
break;

event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase=y
if (there are currently any not-yet-acknowledged segments)
start timer
}
break;
} /* end of loop forever */

三個經典的可靠傳輸情境

書中提到了三個經典情境,展示 TCP 如何應對網路異常:

異常情境發生經過TCP 的應對方式
1. ACK 遺失傳送端送出 Seq=92(8 bytes),接收端收到並回傳 ACK=100,但該 ACK 途中遺失。1. 傳送端發生逾時,重傳 Seq=92。
2. 接收端發現收到重複資料,將其丟棄,並再次回傳 ACK=100。
2. 過早的逾時傳送端連送 Seq=92(8 bytes), Seq=100(20 bytes)。
接收端依序回傳 ACK=100, ACK=120,但在抵達前 Seq=92 就逾時了。
1. 傳送端重傳 Seq=92。
2. 接著 ACK=100 與 ACK=120 陸續抵達傳送端。
3. 因為 ACK=120 已涵蓋第二個區段,所以 Seq=100 不會被錯誤重傳。
3. 累積式確認的救援傳送端連送 Seq=92, Seq=100。
接收端的 ACK=100 遺失,但 ACK=120 成功在逾時前抵達。
傳送端收到 ACK=120,已知 119 以前的資料都收到了,因此 Seq=92 和 Seq=100 都不會被重傳。

第一種情況(傳送端為 A,接收端為 B):

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 271, Figure 3.34)

第二種情況:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 272, Figure 3.35)

第三種情況:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 272, Figure 3.36)

快速重傳(Fast Retransmit)

逾時重傳缺點:計時器的時間通常設得比較長,若傻傻地等計時器走完才重傳,傳輸延遲會非常嚴重。

TCP 提供了聰明的解法:

  • 當接收端收到一個失序的區段(例如中間漏了一個封包),會立刻回傳一個重複的 ACK,告訴傳送端目前正等待遺失的封包。
  • 傳送端若連續收到 3 個重複的 ACK(加上原本正常的 ACK,總共 4 個相同的 ACK),就會果斷判定該區段已經遺失。
  • 此時傳送端會執行快速重傳,在計時器逾時之前立刻重傳該區段。

下表 3.2 為允許接收端在這種情形下無須捨棄脫序的區段:

事件TCP 接收端動作
擁有預期序號的區段依序到來,所有在預期序列編號前的資料皆已被確認。延遲 ACK。最多花 500 msec 等另一個依序的區段到來,若下個依序區段位在此段期間收到,就送出 ACK。
擁有預期序號的區段依序到來,另一個依序的區段正等待傳輸 ACK。立即送出單一累積式 ACK,回應全部依序的區段。
擁有比預期來得大的區段不依序抵達,偵測到縫隙。立即送出重複 ACK,指示下個預期的位元組序號(縫隙底端)。
抵達的區段可部分或完整地填滿收到的資料中的縫隙。立即送出 ACK,假設區段從縫隙底端開始填滿。

Table Source:Computer Networking: A Top-Down Approach (8th ed., p. 274, Table 3.2 TCP ACK Generation Recommendation [RFC 5681])

在程式邏輯中,這段機制可表示為:

1
2
3
4
5
6
7
8
9
10
11
12
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y;
if (there are currently any not yet acknowledged segments)
start timer;
} else { /* 收到重複的 ACK */
increment number of duplicate ACKs received for y;
if (number of duplicate ACKs received for y == 3) {
/* TCP 快速重傳 (Fast Retransmit) */
resend segment with sequence number y;
}
}

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 275, Figure 3.37)

TCP 是 GBN 還是 SR?

特性GBN(回朔 N)SR(選擇性重複)TCP 的實際作法
確認方式累積式確認獨立確認累積式確認(類似 GBN)
封包遺失處理重傳遺失封包及其後所有已送出的封包只重傳遺失的單一封包通常只重傳遺失的封包(類似 SR)
失序封包處理直接丟棄,不暫存緩衝暫存,等待缺漏補齊大多數實作會緩衝暫存失序的區段(類似 SR)

TCP 為 GBN 與 SR 的 混合體。

使用 GBN 的累積確認機制(只需維護最舊未確認的狀態變數 SendBase 和下一個要傳的序號 NextSeqNum),也像 SR 一樣會暫存失序封包,且在發生遺失時,TCP 通常最多只重傳一個遺失的區段,而非退回 N 步重傳所有區段。

3.5.5 Flow Control(流量控制)

流量控制(Flow control)是一種速度匹配(speed-matching)服務,用來讓發送端的傳送速率與接收端應用程式讀取資料的速率保持一致。

為什麼需要流量控制?當 TCP 連線建立時,雙方都會配置專屬的接收緩衝區(Receive Buffer),如果發送端送資料的速度,遠大於接收端應用程式從緩衝區讀取資料的速度,緩衝區就會被塞滿,導致後續到達的資料遺失(接收端緩衝區溢位)。

流量控制適用於 TCP 這種保證可靠傳輸的連線,相對地,UDP 就沒有提供流量控制,如果應用程式讀取不夠快,UDP 的緩衝區就會溢位並開始丟棄封包。

術語解析

  • 接收緩衝區(Receive Buffer, RcvBuffer):TCP 連線建立時,為該連線分配的可用記憶體空間大小。
  • 接收視窗(Receive Window, rwnd):一個動態變數,代表接收端緩衝區目前的剩餘可用空間。
  • 最後讀取位元組(LastByteRead):接收端的應用程式行程從緩衝區讀取走的資料串流的最後一個位元組編號。
  • 最後接收位元組(LastByteRcvd):資料串流內,接收端 TCP 從網路收到、並放入緩衝區的最後一個位元組編號。

流量控制 vs. 壅塞控制(Flow Control vs. Congestion Control)

雖然兩者的動作皆為限制發送端的發送速度,但原因與保護對象完全不同:

比較項目流量控制(Flow Control)壅塞控制(Congestion Control)
保護對象接收端(Receiver):讓其緩衝區不被塞爆網路核心(Network Core):讓其路由器緩衝區不被塞爆
控制依據接收端明確告知的剩餘空間(以 rwnd 表示)發送端自行推測的網路壅塞程度(如封包遺失)
運作範圍端到端(End-to-End)的點對點協商涉及整個網路鏈路與路由器的總體狀態

接收端如何計算剩餘空間?

為了保證緩衝區不溢位,接收端必須確保「已接收但尚未被應用程式讀走」的資料量不能超過 $\text{RcvBuffer}$ : $$\text{LastByteRcvd} - \text{LastByteRead} \le \text{RecvBuffer}$$

接收視窗被標記為 $\text{rwnd}$,被設定為緩衝區中剩餘的空間總量: $$\text{rwnd} = \text{RcvBuffer} - [\text{LastByteRcvd - LastByteRead}]$$

因為應用程式讀取資料的時間點與速率是未知的,所以 $\text{rwnd}$ 是一個隨時間變化的動態值。

接收端會在每次發送給發送端的 TCP 區段標頭中,將這個 $\text{rwnd}$ 值填入接收視窗欄位,明確告訴對方自己還剩多少空間。

下圖為接收視窗($\text{rwnd}$)跟接收緩衝區($\text{RcvBuffer}$)的運作:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 278, Figure 3.38)

發送端如何限制發送量?

發送端(假設為 Host A)在本地端也會維護兩個變數:

  1. LastByteSent:最後送出的位元組編號。
  2. LastByteAcked:最後收到確認的位元組編號。

該兩個變數的差值 $\text{LastByteSent} - \text{LastByteAcked}$ 即「已送出但尚未收到確認的資料量」,發送端為了不塞爆接收端,必須嚴格遵守以下規則: $$\text{LastByteSent} − \text{LastByteAcked} \le \text{rwnd}$$

只要維持該不等式,發送端就可保證絕對不會讓接收端的緩衝區溢位。

Zero-Window Problem

在此有個非常經典的協定設計陷阱:假設接收端(Host B)的緩衝區滿了,於是送出一個 $\text{rwnd} = 0$ 的封包給發送端(Host A)。

Host A 收到後停止發送,過一陣子,Host B 的應用程式讀走了資料,緩衝區空出來了。

但是 TCP 只在有資料要送或需要發送確認(ACK)時才會產生封包,如果 Host B 剛好沒有資料要傳給 A,就永遠不會主動發送更新後的 $\text{rwnd}$ 給 A,會導致 Host A 永遠被阻擋,雙方陷入死鎖(Deadlock)。

TCP 的解法:TCP 規範強制規定,當發送端收到 $\text{rwnd} = 0$ 時,必須持續發送只有 $1$ 個資料位元組(1 data byte)的探測區段(Probe segment)給接收端。

接收端收到這個探測區段後,會回覆確認封包,這個確認封包裡面就會夾帶最新的 $\text{rwnd}$ 值,只要緩衝區開始清空,發送端就能透過這個機制得知新的 $\text{rwnd}$ 不為零,從而恢復正常傳輸。

3.5.6 TCP Connection Management(TCP 連線管理)

TCP 連線管理規範了兩個應用程式在開始傳輸資料前,如何打招呼建立連線,以及在傳輸結束後如何釋放資源。

TCP 是一個狀態導向(Stateful)的協定。

雙方在傳送資料前,必須先在各自的作業系統中分配緩衝區(Buffers)與初始化狀態變數(如序號、視窗大小等),如果沒有這個過程,TCP 就無法提供可靠的全雙工傳輸。

術語解析

  • 三次握手(three-way handshake):TCP 建立連線時,客戶端與伺服器之間必須交換三個特殊的控制封包。
  • 初始序號(Initial Sequence Number, ISN):雙方在建立連線時隨機挑選的起始位元組編號,用於防止與網路上殘留的舊連線封包搞混。
  • SYN 區段(SYN segment):TCP 標頭中 SYN 旗標位元設為 1 的特殊控制封包,用來發起連線,不攜帶應用層資料。
  • SYNACK 區段(SYNACK segment):伺服器用來回應 SYN 的封包,同時包含了確認(ACK)與伺服器自己的同步請求(SYN)。

建立連線:三次握手

當在 Python 中呼叫 clientSocket.connect((serverName, serverPort)) 時(TCP 建立連線時),作業系統底層會進行以下三個步驟:

步驟傳送方向封包名稱與標記動作與狀態變化
第一步Client → ServerSYN 區段(SYN=1, seq=client_isnClient 端隨機選擇初始序號 client_isn,發送 SYN 區段,此時客戶端進入 SYN_SENT 狀態。
第二步Server → ClientSYNACK 區段(SYN=1, seq=server_isn, ack=client_isn+1Server 端收到 SYN 後,分配 TCP 緩衝區與變數,隨機選擇自己的初始序號 server_isn,並確認 Client 端的序號,伺服器進入 SYN_RCVD 狀態。
第三步Client → ServerACK 區段(SYN=0, seq=client_isn+1, ack=server_isn+1Client 端收到 SYNACK,分配自己的緩衝區與變數,回傳確認封包。
此時封包的 Payload 已經可以攜帶應用程式資料了,雙方正式進入 ESTABLISHED(已建立)狀態。

下圖 3.39 為 TCP 建立連線的區段交換圖:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 278, Figure 3.39)

關閉連線:四次揮手

“All good things must come to an end”,意即「天下沒有不散的筵席」。

當雙方資料傳輸完畢,任何一方都可以發起關閉連線(通常由客戶端發起),此為互相確認的過程:

  1. 發起關閉:客戶端應用程式行程呼叫 close(),TCP 送出一個 FIN 區段(FIN bit = 1),並進入 FIN_WAIT_1 狀態。
  2. 伺服器確認:伺服器收到 FIN,回傳 ACK。此時伺服器進入 CLOSE_WAIT,客戶端收到 ACK 後進入 FIN_WAIT_2,此時客戶端到伺服器的方向已關閉,但伺服器如果還有資料沒傳完,仍可繼續傳送。
  3. 伺服器關閉:伺服器資料也傳完了,送出自己的 FIN 區段,進入 LAST_ACK 狀態。
  4. 最後確認與等待:客戶端收到伺服器的 FIN,回傳 ACK,並進入一個特殊的 TIME_WAIT 狀態(通常等待 30 秒到 2 分鐘)。

下圖 3.40 為 TCP 關閉連線的區段交換圖:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 278, Figure 3.40)

客戶端與伺服器通常經歷的 TCP 狀態序列

客戶端:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 278, Figure 3.41)

伺服器:

image

Image Source:Computer Networking: A Top-Down Approach (8th ed., p. 278, Figure 3.42)

SYN 滿載攻擊(The SYN Flood Attack)

在三次握手的第二步,伺服器收到 SYN 後,會立刻分配記憶體資源(建立半開連線),如果客戶端未送出 ACK 以完成三次握手的第三步,最後伺服端會終止開到一半的連線,並回收配置出去的資源,進而容易造成阻斷服務攻擊(Denial of Service, DoS)。

在 SYN 滿載攻擊中,駭客會送出大量 TCP SYN 區段,不去完成第三步握手的程序。

現代作業系統如何防禦?

工程師發明了 SYN Cookies 技術,修改了第二步的行為:

  1. 伺服器收到 SYN 時,不再預先分配記憶體與資源。
  2. 伺服器將來源/目的 IP 與 Port,加上一個只有自己知道的秘密數字(Secret number),透過密碼學的雜湊函數(Hash function)計算出一個值。
  3. 伺服器將該 Hash 值當作自己的初始序號(server_isn,即 Cookie),放進 SYNACK 傳給客戶端,然後立刻忘記這件事。
  4. 驗證:
    • 如果客戶端是合法的,會在第三步回傳 ACK,其確認號必然是 server_isn + 1
    • 伺服器此時只需重新計算一次 Hash,若加 1 後與確認號吻合,才真正分配資源並建立連線。
    • 惡意攻擊者不會回傳 ACK,伺服器自然也不會浪費任何資源。

實務應用:連接埠掃描

資訊安全工具 Nmap(Network Mapper)是款熱門、開源且功能強大的網路探測與資安稽核工具,廣泛用於主機發現、埠掃描、作業系統偵測及服務版本判斷。

Nmap 怎麼知道別台主機上有沒有跑 Web 或 FTP 服務呢?就是利用 TCP 三次握手的特性來刺探目標:

Nmap 發送目標主機的回應Nmap 的推論結果
送出 SYN 區段回傳 SYNACK 區段Open(開啟):該 Port 有應用程式正在監聽。
送出 SYN 區段回傳 RST 區段(Reset 標記為 1)Closed(關閉):封包有抵達,但該 Port 沒有應用程式。
送出 SYN 區段完全沒有回應Filtered(被阻擋):封包在半路被防火牆(Firewall)沒收了。

總整理

TCP 基本概念

TCP 是一種建立在不可靠 IP 層之上,提供可靠、全雙工、點對點的邏輯傳輸管道。

  • 主要特性:不容許資料遺失(適用網頁、檔案傳輸),但不支援群播(Multicasting)。
  • 邏輯連線:連線狀態(變數、緩衝區)只存在於通訊雙方的端點,中間的路由器對此一無所知。
  • 重要名詞:
    • MTU(Maximum Transmission Unit, 最大傳輸單位):連結層能送出的最大訊框(如乙太網路 1500 bytes)。
    • MSS(Maximum Segment Size, 最大區段大小):TCP 裝載應用層資料的最大量(受限於 MTU)。

TCP 區段結構

TCP 傳輸的基本單位,由標頭(通常 20 bytes)+ 資料兩部分組成。

  • 計算邏輯(基於 Byte Stream):TCP 的序號與確認機制是基於位元組(Byte),而非封包數量。
  • 累積式確認(Cumulative ACK):ACK 號碼代表接收端期望收到的下一個位元組編號,且表示該號碼之前的所有資料皆已收到。
  • 便車機制(Piggybacking):將 ACK 確認號與自己要發送的資料打包在同一個區段送出,節省頻寬。
  • 重要標頭旗標(Flags):
    • SYN, FIN, RST:建立、結束、重設異常連線。
    • ACK:確認號欄位有效。
    • PSH:盡快交給應用程式,不逗留緩衝區。

RTT 評估與逾時機制

TCP 需動態調整「逾時區間」,太短會浪費頻寬重傳,太長會降低傳輸效率。

TCP 不測量重傳封包的 RTT。

動態計算公式(使用指數加權移動平均 EWMA):

  • 平滑平均值:$\text{EstimatedRTT} = (1 - \alpha) \cdot \text{EstimatedRTT} + \alpha \cdot \text{SampleRTT}$
  • 變異程度:$\text{DevRTT} = (1 - \beta) \cdot \text{DevRTT} + \beta \cdot \lvert \text{SampleRTT} - \text{EstimatedRTT} \rvert$
  • 最終逾時區間:$\text{TimeoutInterval} = \text{EstimatedRTT} + 4 \cdot \text{DevRTT}$
  • 逾時懲罰(指數退讓):發生逾時後,下一次的逾時區間直接乘以 2,避免網路壅塞時火上加油。

可靠資料傳輸

TCP 結合序號、累積式確認與重傳機制來確保資料可靠性,本質是 GBN(回朔 N)與 SR(選擇性重複)的混合體。

傳送端三大事件:

  1. 收到上層資料:封裝發送並啟動計時器。
  2. 發生逾時:重傳最小未確認區段,重啟計時器。
  3. 收到 ACK:更新確認進度,若還有未確認區段則重啟計時器。

快速重傳(Fast Retransmit):

  • 接收端收到失序區段會立刻回傳重複的 ACK。
  • 傳送端只要連續收到 3 個重複的 ACK(共 4 個相同的 ACK),就會果斷在逾時前提早重傳該遺失區段。

流量控制

目的:速度匹配,防止發送端送太快,把接收端的緩衝區(Receive Buffer)塞爆。(壅塞控制是為了保護網路核心的路由器,兩者不同)

運作機制:接收端透過 TCP 標頭的 rwnd(接收視窗)欄位,誠實告知目前剩餘空間,發送端必須確保發送未確認資料量 $\le \text{rwnd}$。

Zero-Window 死鎖問題:若接收端滿了($\text{rwnd}=0$),發送端會停止發送,為了避免接收端清空後無法通知發送端(Deadlock),TCP 強制發送端定期發送 1 byte 的探測區段(Probe)來獲取最新的 $\text{rwnd}$。

TCP 連線管理

建立與關閉連線的標準流程,TCP 是具備狀態(Stateful)的協定。

三次握手(建立連線):

  1. Client 送出 SYN(包含初始序號)。
  2. Server 回傳 SYNACK(確認並附帶自己的初始序號,分配資源)。
  3. Client 回傳 ACK(此封包可開始夾帶應用層資料)。

四次揮手(關閉連線):Client 送 FIN $\to$ Server 回 ACK $\to$ Server 送 FIN $\to$ Client 回 ACK(Client 進入 TIME_WAIT 等待徹底關閉)。

SYN 滿載攻擊(SYN Flood):只做前兩次握手,耗盡伺服器資源。

防禦手段(SYN Cookies):伺服器不預先分配資源,而是將 Hash 值作為 SYNACK 序號,等 Client 第 3 步回來驗證成功後才給資源。

Nmap 連接埠掃描:利用握手特性,

  • 收到 SYNACK = Port 開啟
  • 收到 RST = Port 關閉
  • 無回應 = 被防火牆阻擋(Filtered)。