小村軟體有限公司
第 2 章

連線 — TCP 的三向交握與斷線

連線的真面目是兩端合意的號碼起算點。追蹤 SYN → SYN/ACK → ACK 為什麼需要 3 次,以及 FIN 與 TIME_WAIT 守護的是什麼。

「建立連線」的真面目是什麼

平常只要 connect() 有回來就當作連上了,不會有什麼困擾。但故障時,如果無法區分「連不上」的內容 — 是被拒絕、沒有回應,還是建立之後才斷掉 — 就無法前進。為此,需要知道「建立連線」在物理上到底是什麼。

答案出乎意料地樸素。路徑上不會保留專用線路,途中的路由器什麼都不記得。所謂連線,只不過是兩端 2 台機器各自在記憶體中持有的「與這個四元組的對象,號碼互通到哪裡了」這對帳本。所以建立連線的儀式要做的也只有一件事 — 互相傳達彼此的「起算號碼」,並互相確認確實聽到了

這個起算號碼稱為初始序號(ISN)。不從 1 開始數的理由,與第 5 題的 TIME_WAIT 也是相通的 — 為了不和不久前還在用同一個四元組通訊的舊封包混在一起,每次都從難以預測的值開始。

用戶端與伺服器之間往返 SYN、SYN/ACK、ACK 這 3 個封包,雙方的初始序號獲得確認後進入 ESTABLISHED 的循序圖

三向交握。範例中用戶端的 ISN 是 5000、伺服器是 9000。請確認 ack 號碼永遠是「對方的號碼 + 1 = 下一個想要的號碼」。

把 3 個封包一個一個讀

以用戶端 ISN 為 5000、伺服器 ISN 為 9000 的例子來追蹤。

1. SYN(用戶端 → 伺服器)
seq = 5000。「我想連線。我要從 5000 開始數」。SYN 不攜帶資料,但會消耗 1 個號碼
2. SYN/ACK(伺服器 → 用戶端)
seq = 9000, ack = 5001。「聽到 5001 之前了(= 收到了你的 SYN)。我要從 9000 開始數」— 確認與宣告的共乘
3. ACK(用戶端 → 伺服器)
seq = 5001, ack = 9001。「你的起算點我也聽到了」。此時雙方都進入 ESTABLISHED,可以傳送資料

為什麼是 3 次?TCP 持有雙向各自獨立的 2 條位元組串流,所以每個方向都需要「起算點的宣告(SYN)」與「聽到了的確認(ACK)」。角色合計 4 個,但中間的 2 個(伺服器的 ACK 與伺服器的 SYN)可以共乘在 1 個封包裡,所以最少 3 個封包。2 次的話沒有人確認過「伺服器的宣告送達了」,4 次則多出 1 個浪費,就是這麼回事。

練習 2-1 — 逐一封包看三向交握

確認 3 個封包各自「在說什麼」。號碼的題目請寫在紙上計算。

Q1. 三向交握的第 2 個封包(伺服器回傳的 SYN/ACK)在說的事情,正確的是哪一個?

Q2. 用戶端以初始序號 ISN = 5000 送出 SYN。伺服器回傳的 SYN/ACK 的 ack 號碼是多少?(SYN 不攜帶資料,但會消耗 1 個序號)

Q3. 交握只有 2 次(SYN → SYN/ACK 就結束)無法成立的理由,正確的是哪一個?

斷線是 4 個角色 — 一個方向一個方向地打烊

斷線比建立多 1 手,是因為不一定能共乘。「已經沒有東西要送了」的宣告 FIN,只關閉從自己到對方的方向。對方可能還有東西沒送完,所以送出 FIN 之後仍可繼續接收(半關閉)。雙向各需要 FIN 與 ACK,所以基本形是 4 個封包。

而先送出 FIN 的一方,在一切結束之後仍會以 TIME_WAIT 狀態留存一段時間(多數實作為 30 秒〜數分鐘)。理由有 2 個。

最後一個 ACK 的保險
自己回覆的最後一個 ACK 若遺失,對方會重送 FIN。如果沒有留下負責回應的人,對方就無法正確關閉
替舊區段排毒
等到仍漂流在網路上的這條連線的區段因壽命耗盡而消失之前,擋住同一四元組的重複使用。防止舊資料混入新連線的事故

還有一種不客氣的結束方式,就是 RST(重置)。這是「我不知道那條連線/現在立刻毀掉」的單方面通告,會回應給送往沒有等待程式的連接埠的 SYN、送給已當掉行程的資料等。FIN 是合意的打烊,RST 是不由分說的廢棄 — 光是能在封包擷取中分辨這兩者,故障的風景就會大不相同。

練習 2-2 — 斷線與 TIME_WAIT,還有 RST

TCP 的結尾比開頭更深奧。實務上遇到的頻率也是斷線這邊比較高。

Q4. TCP 的斷線需要雙向各 1 次 FIN(合計 2 次,各自附帶 ACK)的理由是哪一個?

Q5. 先送出 FIN 斷線的一方不會立刻消失,而是在 TIME_WAIT 狀態等待一段時間。這段等待時間的目的,正確的是哪一個?

Q6. 對沒有任何程式在等待的連接埠送出 SYN,與防火牆默默丟棄封包這兩種情況,從用戶端看到的差異,正確的是哪一個?

本章帶走的重點

  • 連線的真面目是兩端持有的帳本對。所謂建立,就是「起算號碼(ISN)」的合意
  • 交握是把雙向份的 SYN + ACK 共 4 個角色共乘進 3 個封包的最小形。ack 永遠是「下一個想要的號碼」
  • 斷線是 FIN 一次關一個方向。TIME_WAIT 不是故障,而是最後一個 ACK 的保險與舊區段的排毒
  • 立刻回 RST = 有送達但被拒絕,無回應逾時 = 根本沒有回應。切分的第一步

下一章將以手算追蹤建立之後的主角 — 序號與 ACK 號碼在資料傳輸中如何運作。