所讀版本:機械工業出版社
這一篇在做什麼
前幾篇已經在程式層面開始使用 Socket,這一篇往下補的是 TCP 本身的工作機制。目標不是把協議規格背完,而是理解幾個會直接影響實作判斷的問題:
- TCP 在整個網絡模型中的位置
- 連接建立、數據傳輸與連接關閉的基本流程
- 幾個常見 Socket 參數的用途
- 為什麼實戰裡常常還要加上心跳機制
先把 4 層網絡模型放回腦中
在日常開發裡,最常接觸的是 Socket.Send、Socket.Receive 這類 API,但它們其實只是整個網絡模型中的一小段。
這裡可以把常見的 4 層模型簡單整理成:
應用層
最接近遊戲或業務邏輯的一層。程式在這裡決定要發什麼內容,再把資料轉成字節流交給下面的傳輸層。
傳輸層
這一層就是 TCP 所在的位置。它不是單純把資料往下丟,而是會處理:
- 分段
- 順序控制
- 確認回應
- 重傳
- 流量控制
也就是說,TCP 的價值在於它把「可靠傳輸」這件事包進了協議本身。
網絡層
網絡層主要負責把資料送到正確的地址,也就是 IP 處理的範圍。它會在資料上附加來源與目的地地址資訊。
網絡接口層
這一層才真正和實際的物理傳輸介質打交道。

TCP 連接建立
TCP 是面向連接的協議,所以在真正傳數據之前,雙方必須先建立連接。這就是常說的三次握手。
三次握手在做什麼
它不只是「打個招呼」,而是在確認幾件事情:
- 雙方都在線
- 雙方都能收也能發
- 初始序列號與確認號已同步
- 一些窗口大小等控制信息已交換
三次握手流程
- 連接方呼叫
Connect,送出SYN。 - 監聽方收到後,回送
SYN/ACK。 - 連接方收到後,再送出
ACK,雙方把狀態切到已建立連接。
如果 Connect 很久不返回,通常代表底層還在重試 SYN 或等待 SYN/ACK,直到成功或超出重試上限。

TCP 傳輸數據時在做什麼
連接建立之後,TCP 的傳輸過程也不是「送了就算」,而是持續做可靠性控制。
流量控制
發送方不會無限制地把數據灌給接收方。它會考慮對方接收緩衝區的容量,如果對端快滿了,就會暫停或放慢發送,避免對方溢出。
確認與重傳
發送方送出一段數據後,不會直接假設成功,而是會等待接收方確認。如果太久收不到確認,就會觸發重傳。
擁塞控制
TCP 還會根據回應時間與丟包狀況判斷當前網絡是否擁堵,必要時自動減慢傳輸速度。
這也是為什麼 TCP 在可靠性上好用,但在即時性上有時不如 UDP。
TCP 連接終止
關閉連接時,TCP 用的是四次揮手,而不是三次握手那種簡化流程。
為什麼是四次
因為 TCP 是全雙工的。也就是說,一方說「我這邊不再發了」,不代表另一方也同時沒有資料要送,所以雙方的關閉必須分開確認。
四次揮手流程
- 主機 1 發送
FIN,表示自己沒資料要發了。 - 主機 2 回送
ACK,確認收到這個關閉請求。 - 主機 2 把剩餘資料發完後,再送一個自己的
FIN。 - 主機 1 回送
ACK,然後進入TIME_WAIT,等待一段時間再真正釋放。
TIME_WAIT 的作用,是確保最後一個確認包如果丟失,對方重發 FIN 時仍有機會被正確處理。

幾個常見 TCP / Socket 參數
ReceiveBufferSize
指定作業系統接收緩衝區大小,默認通常是 8192 字節。它影響的是本地一次可暫存多少收到但尚未被應用層取走的數據。
SendBufferSize
指定作業系統發送緩衝區大小,也通常默認是 8192 字節。它會影響本地可先排隊多少待發送數據。
NoDelay
這個參數和 Nagle 演算法有關。
如果啟用 Nagle,系統會傾向先把很多小包累積起來,再合併成較大的數據包送出,優點是降低封包碎片與總開銷;缺點則是增加延遲。
所以:
- 想減少封包數量,可以接受延遲時,Nagle 有價值
- 很在意即時性的互動,例如某些遊戲同步場景,常會關掉它
TTL
TTL 是 IP 數據包的存活跳數。每經過一個路由器都會減少,歸零後就被丟棄。
它的主要用途是避免一個錯誤路由的數據包在網絡中無限循環。

ReuseAddress
ReuseAddress 允許端口複用。這個參數在服務端重啟場景裡很常見,因為程序退出和 OS 真正釋放端口並不是完全同步的。
如果不處理,常見情況就是:
- 程序剛關掉
- 你立刻重啟
- 結果
Bind失敗,因為端口還沒完全釋放
LingerState
LingerState 用來控制 Socket 在關閉時要不要等,以及等多久。
它實際上對應的是「關閉前是否還要保證把剩餘資料處理完」這個問題。
簡化理解:
- 時間為
0:傾向一直等到處理完成 - 時間大於
0:只等指定時間,之後強制結束
為什麼還需要心跳機制
理論上,TCP 有自己的保活檢測能力,但在實際遊戲服務中,通常還是會再做應用層心跳。
原因很簡單:如果某一方異常斷線,卻沒有機會正常送出 FIN,另一端可能還會暫時誤以為連接有效。
TCP KeepAlive
系統層面可以打開 KeepAlive:
Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true)
它的作用是:長時間無通信時,底層會主動探測對端是否還活著;如果多次探測都失敗,才關閉連接。
但這個機制往往:
- 觸發週期較長
- 粒度不一定符合遊戲需求
應用層心跳的常見做法
所以很多遊戲會自己再做一層:
- 客戶端定時送
Ping - 服務端收到後回
Pong - 服務端記錄最近一次收到
Ping的時間 - 超過一定時間沒再收到,就判定該連接失效並主動清理資源
這一套做法比單純依賴 TCP KeepAlive 更可控,也更適合實時服務。
這一篇先記住的重點
如果只收斂成最關鍵的幾點,可以記成:
- TCP 提供的是可靠傳輸,不只是單純發包
- 三次握手負責建立連接,四次揮手負責安全關閉
- 傳輸中會同時受到流量控制、重傳與擁塞控制影響
NoDelay、BufferSize、ReuseAddress、LingerState都會直接影響實際行為- 遊戲實戰裡通常還要在 TCP 之上再補一層應用層心跳