Learning Objectives
- 說出 TCP 在 IP 之上提供的保證:連線導向、可靠、依序、位元組流
- 說明序號與確認號的意思,並算出每個封包的 seq 與 ack
- 描述三向交握的三個封包,以及為什麼需要三個
- 描述四次揮手與半關閉,說出 FIN-WAIT、CLOSE-WAIT、TIME-WAIT 等狀態的意思
- 分辨連線被拒絕(RST)和完全沒有回應(逾時)兩種失敗
在不可靠的網路上做出可靠的傳輸
上一課的 UDP 寄出就好,遺失、重複、亂序都留給應用程式處理。可是大部分應用需要的是「送出去的東西,對方一定完整、依序收到」: 網頁、檔案下載、電子郵件、SSH 遠端登入都是如此。這些應用都用 TCP(Transmission Control Protocol,傳輸控制協定)。
TCP 的規格最早是 1981 年的 RFC 793,2022 年由 RFC 9293 整合後取代。它在「盡力而為」的 IP 上面提供:
| 保證 | 意思 | 靠什麼做到 |
|---|---|---|
| 連線導向 | 傳資料之前兩端先建立連線,結束時也要關閉 | 三向交握、四次揮手(本課) |
| 可靠 | 遺失的會重送,重複的會丟掉 | 序號、確認、重傳(下一課) |
| 依序 | 應用程式讀到的順序就是對方寫入的順序 | 序號,接收端重新排好 |
| 位元組流 | 沒有訊息邊界:送三次 100 位元組,對方可能一次讀到 300 | 序號以位元組為單位 |
| 流量控制、壅塞控制 | 不送得比接收端或網路能承受的快 | 視窗(下一課) |
計算機概論第 30 課用一句話帶過三向交握;這一課逐一看每個封包裡的數字。
序號、確認號與旗標
TCP 標頭至少 20 位元組,除了來源埠、目的埠,這一課最重要的是下面幾個欄位:
| 欄位 | 大小 | 意思 |
|---|---|---|
| 序號(sequence number,seq) | 32 位元 | 這個區段的資料,第一個位元組的編號 |
| 確認號(acknowledgment number,ack) | 32 位元 | 我下一個期待收到的位元組編號,也就是「這之前的我都收到了」 |
| 旗標 | 各 1 位元 | SYN(同步序號,建立連線)、ACK(確認號有效)、FIN(我送完了)、RST(重設連線)、PSH 等 |
| 視窗(window) | 16 位元 | 我的接收緩衝區還能收多少位元組(下一課) |
TCP 傳的單位叫區段(segment)。三條規則把所有數字串起來:
- 序號以位元組計:送出 100 位元組、seq=1001,下一個區段的 seq 就是 1101。
- SYN 和 FIN 各佔一個序號,即使沒有帶資料,所以它們也需要被確認。
- 確認號是累積的:ack=1101 表示 1101 之前的位元組全部收到,不必逐一確認每個區段。
每條連線兩個方向各有一套序號,起點叫初始序號(ISN)。RFC 9293 建議用時鐘加上只有本機知道的秘密值來產生初始序號, 讓外人猜不到,避免偽造的封包被當成這條連線的一部分。下面的模擬器為了好讀,固定用 1000(用戶端)與 5000(伺服器)。
三向交握
建立連線之前,伺服器要先在某個連接埠上等待(呼叫 listen(),狀態 LISTEN),用戶端呼叫 connect() 時送出三個封包:
| 順序 | 方向 | 旗標 | seq | ack | 意思 | 送出後的狀態 |
|---|---|---|---|---|---|---|
| 1 | 用戶端 → 伺服器 | SYN | 1000 | - | 我的序號從 1000 開始 | 用戶端 SYN-SENT |
| 2 | 伺服器 → 用戶端 | SYN、ACK | 5000 | 1001 | 收到你的 1000(SYN 佔一號,所以期待 1001);我的序號從 5000 開始 | 伺服器 SYN-RECEIVED |
| 3 | 用戶端 → 伺服器 | ACK | 1001 | 5001 | 收到你的 5000,期待 5001 | 兩端 ESTABLISHED |
為什麼要三個,不是兩個?兩個方向的序號都要送出並被對方確認:第 1、2 個封包讓伺服器知道用戶端的起點並回覆確認, 第 2、3 個封包讓用戶端的確認回到伺服器。少了第 3 個,伺服器無法確定用戶端真的收到了它的初始序號, 也分辨不出這個 SYN 是不是網路上延遲很久、早就作廢的舊封包。
如果伺服器那個連接埠上沒有程式在 LISTEN,它會回一個 RST,用戶端立刻知道「連線被拒絕」(Python 會丟出 ConnectionRefusedError)。
如果 SYN 在路上遺失,用戶端收不到任何回覆,等逾時之後重送 SYN;一直沒回應,最後才放棄。
傳資料時的 seq 與 ack
連線建立後,兩個方向可以同時傳資料(全雙工),每個區段都帶著 ACK 旗標與最新的確認號。延續剛才的數字:
| 方向 | 內容 | seq | ack | 說明 |
|---|---|---|---|---|
| 用戶端 → 伺服器 | 100 位元組資料 | 1001 | 5001 | 資料編號 1001~1100 |
| 伺服器 → 用戶端 | ACK | 5001 | 1101 | 1100 之前都收到,期待 1101 |
| 伺服器 → 用戶端 | 100 位元組資料 | 5001 | 1101 | 資料編號 5001~5100;確認號照樣帶著 |
| 用戶端 → 伺服器 | ACK | 1101 | 5101 | 期待 5101 |
注意兩件事:
- 沒有帶資料的 ACK 不佔序號,所以伺服器送完 ACK 之後,自己的資料仍然從 5001 開始。
- 確認號只說「到哪裡為止全部收到」。如果中間缺了一段,後面收到的再多,確認號也停在缺口的開頭。下一課會看到這件事怎麼觸發重傳。
模擬器裡接收端每收到一個區段就立刻確認;實際系統常會稍等一下,把幾個確認合併,或搭在要送出的資料上一起送(延遲確認)。
小測驗:序號與確認號
用戶端的初始序號是 3000。三向交握中,伺服器回覆的 SYN-ACK 確認號是多少?
連線建立後,用戶端連續送出兩個區段:seq=3001、len=500,以及 seq=3501、len=200。伺服器兩個都收到後的確認號是?
三向交握為什麼不能只用兩個封包(SYN、SYN-ACK)?
用戶端 connect() 之後立刻收到 RST。最可能的原因是?
四次揮手與連線狀態
關閉連線時,兩個方向要各自關閉,所以通常需要四個封包。先呼叫 close() 的一方叫主動關閉:
| 順序 | 方向 | 旗標 | 狀態變化 |
|---|---|---|---|
| 1 | 用戶端 → 伺服器 | FIN | 用戶端:ESTABLISHED → FIN-WAIT-1(我不再送資料了) |
| 2 | 伺服器 → 用戶端 | ACK | 伺服器 → CLOSE-WAIT;用戶端收到後 → FIN-WAIT-2 |
| 3 | 伺服器 → 用戶端 | FIN | 伺服器的程式也 close() 後才送;伺服器 → LAST-ACK |
| 4 | 用戶端 → 伺服器 | ACK | 用戶端 → TIME-WAIT;伺服器收到後 → CLOSED |
第 2 與第 3 個封包之間,伺服器處於 CLOSE-WAIT:它知道對方不再送資料了,但自己還可以繼續送,直到程式呼叫 close()。
這叫半關閉。如果伺服器程式忘了關閉 socket,連線會一直停在 CLOSE-WAIT。
主動關閉的一方最後要在 TIME-WAIT 等待 2×MSL(MSL 是封包在網路上最長的存活時間,RFC 9293 取 2 分鐘),原因有兩個:
- 最後一個 ACK 可能遺失。對方會重送 FIN,自己還在 TIME-WAIT 就能再確認一次。
- 讓這條連線還在網路上漂流的舊封包全部消失,不會混進之後用同樣四元組建立的新連線。
RFC 9293 定義的狀態:
| 狀態 | 意思 |
|---|---|
| CLOSED | 沒有連線(概念上的狀態) |
| LISTEN | 伺服器等待連線 |
| SYN-SENT/SYN-RECEIVED | 三向交握進行中 |
| ESTABLISHED | 連線建立,可以傳資料 |
| FIN-WAIT-1/FIN-WAIT-2 | 主動關閉方:送出 FIN,等確認、等對方的 FIN |
| CLOSE-WAIT | 被動關閉方:收到 FIN,等自己的程式關閉 |
| LAST-ACK | 被動關閉方:送出 FIN,等最後的確認 |
| CLOSING | 兩邊幾乎同時送出 FIN |
| TIME-WAIT | 主動關閉方等待 2×MSL |
在自己的電腦上可以用 netstat -an(Windows、macOS)或 ss -tan(Linux)列出每條 TCP 連線的四元組與狀態;
實際系統 TIME-WAIT 等多久依作業系統而定。另外,任何一方都可以送 RST 直接中止連線,不走四次揮手。
小測驗:關閉連線與狀態
一台伺服器上看到大量連線停在 CLOSE-WAIT,最可能的原因是?
四次揮手後,哪一方要在 TIME-WAIT 等待 2×MSL?
用戶端送出 FIN、伺服器回了 ACK,但伺服器還沒送出自己的 FIN。這段時間伺服器可以做什麼?
重點整理
- TCP 在 IP 上提供連線導向、可靠、依序的位元組流;規格是 RFC 9293。
- 序號以位元組計,SYN 與 FIN 各佔一個;確認號=下一個期待的位元組編號,而且是累積的。
- 三向交握:SYN → SYN-ACK → ACK,兩個方向的初始序號都被確認;沒有程式在聽會收到 RST。
- 四次揮手:兩個方向各自 FIN、各自確認;被動方在 CLOSE-WAIT 還能送資料(半關閉),主動方最後在 TIME-WAIT 等 2×MSL。
- 收到 RST=主機在、但拒絕;完全沒有回應=主機不在、封包被丟掉或擋掉,只能等逾時。
這一課的封包都沒有遺失(除了你刻意弄丟的 SYN)。下一課讓資料在路上遺失,看 TCP 怎麼靠確認與重傳補回來, 以及怎麼決定一次能送多少:滑動視窗、流量控制與壅塞控制。