在 Day 18 中,我們的網路協定棧已經能夠成功回覆 SYN-ACK,雙方建立了連線記錄並停留在 TCP_SYN_RECEIVED。
今天(Day 19),我們完成 TCP 連線建立中最關鍵、也是最後的一哩路:
收到客戶端回傳的純 ACK 封包,驗證序號,正式將狀態推進至 TCP_ESTABLISHED!

這代表我們的 TCP 協定棧不再只是「聽得見」和「說得出」,而是正式具備了最小化三向交握狀態轉換能力,這是自製 TCP/IP 協定棧中非常重要的里程碑!
tcp_find_connection(),用 4-Tuple 找到既有連線。server_seq + 1。TCP_SYN_RECEIVED 轉為 TCP_ESTABLISHED。tcp_accept() 雛形,取得已建立連線。send_tcp_handshake 完成自動化三向交握測試。在伺服器上,可能會同時有數以萬計的連線正在運作。TCP 是如何將收到的封包精準派送給對應的連線呢?
答案就是 4-tuple:
來源 IP, 目的 IP, 來源 Port, 目的 Port
在全世界的網際網路中,這四個欄位的組合對於一條 TCP 連線來說是絕對唯一的。
state == TCP_SYN_RECEIVED?若將 tcp_find_connection() 限制死在 SYN_RECEIVED:
ESTABLISHED,隨後客戶端傳送資料(HTTP 請求)或關閉連線(FIN)時,封包再次進來,查找器就會回傳 NULL,導致封包被無辜丟棄!tcp_find_connection() 只負責「認人(4-tuple)」,狀態是否正確則交由各處理階段(如 tcp_receive())來驗證。
在昨天的交握中,伺服器送出:
SEQ = 5000
Flags = SYN | ACK
雖然此封包沒有包含任何應用層 Data(Payload 長度為 0),但在 TCP 規範(RFC 793)中:
SYN與FIN控制旗標各自在邏輯上消耗 1 個 Sequence Number。
因此,客戶端收到 SEQ = 5000 的 SYN 後,必須回傳 ACK = 5001。
這代表客戶端向伺服器宣告:
「我已經完整收下了你的 SYN(序號 5000),接下來請你從 5001 號開始發送資料給我!」
如果只有兩次交握(Client 發 SYN,Server 回 SYN-ACK 即建立):
有了第三次 ACK:
ESTABLISHED。RST 終止連線,有效防止歷史連線造成的混亂與資源耗盡。include/tcp.h)新增四元組查找函式與連線受理函式宣告:
/* 連線查找與管理 */
struct tcp_socket* tcp_find_connection(uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port);
struct tcp_socket* tcp_accept(void);
tcp_accept() 實作 (src/tcp.c)struct tcp_socket* tcp_find_connection(uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port)
{
for (int i = 0; i < MAX_TCP_SOCKETS; i++) {
// 排除未使用的槽位(CLOSED)與正在迎賓的門口(LISTEN)
if (tcp_table[i].state != TCP_CLOSED && tcp_table[i].state != TCP_LISTEN) {
if (tcp_table[i].src_ip == src_ip &&
tcp_table[i].dst_ip == dst_ip &&
tcp_table[i].src_port == src_port &&
tcp_table[i].dst_port == dst_port) {
return &tcp_table[i];
}
}
}
return NULL;
}
struct tcp_socket* tcp_accept(void)
{
for (int i = 0; i < MAX_TCP_SOCKETS; i++) {
if (tcp_table[i].state == TCP_ESTABLISHED) {
return &tcp_table[i];
}
}
return NULL;
}
src/tcp.c)在 tcp_receive() 中新增處理邏輯:
if ((tcp->flags & TCP_ACK) && !(tcp->flags & TCP_SYN)) {
uint16_t src_port = ntohs(tcp->src_port);
uint16_t dst_port = ntohs(tcp->dst_port);
// 1. 透過四元組尋找連線
struct tcp_socket *conn = tcp_find_connection(ip->src_ip, ip->dst_ip, src_port, dst_port);
if (conn == NULL) {
printf("[TCP] No matching connection for ACK -> DROP\n");
return;
}
// 2. 檢查狀態是否為 SYN_RECEIVED
if (conn->state == TCP_SYN_RECEIVED) {
uint32_t ack_num = ntohl(tcp->ack);
uint32_t expected_ack = conn->seq + 1; // 5000 + 1 = 5001
// 3. 驗證對方的 ACK 號碼
if (ack_num != expected_ack) {
printf("[TCP] Invalid ACK number: %u (expected %u) -> DROP\n", ack_num, expected_ack);
return;
}
// 4. 三向交握圓滿成功!切換狀態
conn->state = TCP_ESTABLISHED;
conn->seq++; // 消耗 SYN 序號,推進至 5001
printf("\n========================================\n");
printf("[TCP] ACK Received! Handshake Complete!\n");
printf("[TCP] Connection Established: State -> ESTABLISHED\n");
printf("========================================\n\n");
tcp_dump_table();
return;
}
}
在終端機 A 執行協定棧:
sudo ./network
在終端機 B 執行自動交握測試工具:
sudo ./send_tcp_handshake
[ACCEPT]
Ethernet Frame
-------------------------
Destination : 02:00:00:00:00:01
Source : 52:54:00:12:34:56
EtherType : 0x0800
IPv4 Packet
------------------
Version : 4
Header Length: 20 bytes
TTL : 64
Protocol : 6
Checksum : 0x62e5 (OK)
Source IP : 10.0.0.1
Destination IP : 10.0.0.2
[IPv4] Local Delivery (for me)
TCP Packet
----------------------------------------
Source Port : 52144
Destination Port : 8080
SEQ : 1000
ACK : 0
Data Offset : 5 (Header Length: 20 bytes)
Flags : 0x02 [ SYN ]
SYN = 1, ACK = 0, FIN = 0, RST = 0
Window : 4096
Checksum : 0x0000
Urgent Pointer : 0
----------------------------------------
[TCP] Incoming SYN on Port 8080
=== TCP SOCKET TABLE ===
[00] State: LISTEN | Local Port: 8080 (*:* -> :8080)
[01] State: SYN_RECEIVED | 10.0.0.1:52144 -> 10.0.0.2:8080
========================
[TCP] Sent SYN-ACK: SEQ=5000, ACK=1001
Frame length: 54 bytes
[ACCEPT]
Ethernet Frame
-------------------------
Destination : 02:00:00:00:00:01
Source : 52:54:00:12:34:56
EtherType : 0x0800
IPv4 Packet
------------------
Version : 4
Header Length: 20 bytes
TTL : 64
Protocol : 6
Checksum : 0x62e4 (OK)
Source IP : 10.0.0.1
Destination IP : 10.0.0.2
[IPv4] Local Delivery (for me)
TCP Packet
----------------------------------------
Source Port : 52144
Destination Port : 8080
SEQ : 1001
ACK : 5001
Data Offset : 5 (Header Length: 20 bytes)
Flags : 0x10 [ ACK ]
SYN = 0, ACK = 1, FIN = 0, RST = 0
Window : 4096
Checksum : 0x0000
Urgent Pointer : 0
----------------------------------------
========================================
[TCP] ACK Received! Handshake Complete!
[TCP] Connection Established: State -> ESTABLISHED
========================================
=== TCP SOCKET TABLE ===
[00] State: LISTEN | Local Port: 8080 (*:* -> :8080)
[01] State: ESTABLISHED | 10.0.0.1:52144 -> 10.0.0.2:8080
========================
Frame length: 54 bytes
連線表正式由 SYN_RECEIVED 邁入 ESTABLISHED,三向交握完美成功!
TCP 連線建立的最終目的,是為了穩定可靠地傳遞應用層資料。
在 Day 20 中,我們將實作:
GET / HTTP/1.1)。conn->ack += data_len)並回傳 Data ACK,告訴客戶端我們已經確實收下資料!