P2P 應用(檔案傳輸、視訊會議、即時通訊)常面臨核心難題:雙方皆位於 NAT 後方,無法直接建立 TCP 連線。UDP 穿透 NAT 技術相對成熟(STUN、TURN、ICE),但 TCP 因連線導向、狀態機複雜,實作難度較高。本文詳細解析 TCP Hole Punching(TCP 打洞) 技術原理、NAT 分類影響、完整交握流程,並提供可編譯執行的 C++ 原始碼(含 Server/Client 端)。
依據 RFC 3489 / RFC 4787,NAT 分四類,前三屬 Cone 型,最後一類為 Symmetric:
| 類型 | 映射行為 | 外部主機可回傳條件 | TCP 打洞可行性 |
|---|---|---|---|
| Full Cone | 內部 IP:Port → 固定外部 IP:Port | 任意外部主機 | ✅ 容易 |
| Restricted Cone | 同上 | 僅內部曾主動連過的外部 IP | ✅ 可行 |
| Port Restricted Cone | 同上 | 僅內部曾主動連過的外部 IP:Port | ✅ 可行(需精確 Port) |
| Symmetric NAT | 每次新會話分配新 Port | 極嚴格,需預測 Port | ❌ 極難/不可靠 |
本文實作假設雙方 NAT 均為 Cone 型(含 Port Restricted),此為家用/企業路由器主流類型。
Main Port (4000) 維持長連線;Hole Port (8000) 處理打洞協商。ReqConnClientPkt(含雙方 ID),同時在本地相同 Port 監聽(需 SO_REUSEADDR)。connect(A公網IP, A公網Port)。多數 Cone NAT 會丟棄此 SYN,但NAT-A 會記錄此連入嘗試,建立映射期待後續回包 — 此即「打洞」動作。同時 B 在相同本地 Port 監聽。connect(B公網IP, B公網Port)。因步驟 6 NAT-A 已記錄 B 的 IP:Port,視為合法回應,允許通過。**TCP 直連建立成功。**步驟 3 與 6 均需在「發起連線」與「監聽回連」使用相同本地 Port。預設 TCP 不允許同 Port 重複綁定,必須設定:
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
步驟 8 A 直連 B 時,若 B 尚未完成 accept,連線會暫時失敗(WSAEWOULDBLOCK)。採用非阻塞模式輪詢:
u_long mode = 1;
ioctlsocket(sock, FIONBIO, &mode); // 非阻塞
for (int i=0; i<100; ++i) {
if (connect(sock, ...) == 0) break; // 成功
if (WSAGetLastError() == WSAEWOULDBLOCK) Sleep(100);
else break;
}
#pragma pack(push, 1)
struct T_WelcomePkt { DWORD dwID; char szClientIP[16]; UINT nClientPort; };
struct T_NewUserLoginPkt { DWORD dwID; char szClientIP[16]; UINT nClientPort; };
struct T_ReqConnClientPkt { DWORD dwInviterID; DWORD dwInvitedID; };
struct T_SrvReqMakeHolePkt{ DWORD dwInviterID; DWORD dwInviterHoleID; DWORD dwInvitedID; char szClientHoleIP[16]; UINT nClientHolePort; };
struct T_ReqSrvDisconnectPkt { DWORD dwInviterID; DWORD dwInviterHoleID; DWORD dwInvitedID; };
struct T_SrvReqDirectConnectPkt { DWORD dwInvitedID; char szInvitedIP[16]; UINT nInvitedPort; };
struct T_HoleListenReadyPkt { DWORD dwInvitedID; DWORD dwInviterHoleID; };
#pragma pack(pop)
accept 對方直連。WaitForMultipleObjects 同步多執行緒完成事件。TCP Hole Punching 利用 Cone NAT 的「記錄連入嘗試」特性,透過公網 Server 協雙方交換公網端點並同步打洞時機,成功建立雙向 TCP 直連。本文提供的完整原始碼(Server/Client A/Client B)可直接編譯測試,是理解 NAT 穿透機制的優良教材。實務部署時建議整合 ICE 框架,自動偵測 NAT 類型並選擇最佳策略(UDP 優先、TCP 兜底、Relay 最後手段)。