1. TCP 的三个核心特性
TCP 是面向连接 、可靠传输 、基于字节流的协议。
- 面向连接:通信双方必须先建立连接(虚拟的全双工信道),才能收发数据。
- 可靠传输:通过序列号(Seq)、确认应答(ACK)、超时重传、流量控制和拥塞控制来保证数据不丢、不重、不乱序。
- 基于字节流 :TCP 把应用层数据当作一个连续的字节序列,分段封装成报文段传输,不关心消息边界。这意味着应用层要自己处理粘包/拆包问题(比如 Netty 的
LengthFieldBasedFrameDecoder
)。
一个 TCP 连接在系统中由四元组唯一标识:
【源 IP, 源端口, 目的 IP, 目的端口】
内核会为每个连接维护一套状态信息,包括本端/对端序列号、发送/接收缓冲区、窗口大小等。
2. 三次握手
三次握手是 TCP 建立连接的过程:
- 第一次握手:客户端发送 SYN 报文(SYN=1,Seq=x),请求建立连接。
- 第二次握手:服务端收到 SYN,回复 SYN+ACK 报文(SYN=1,ACK=1,Ack=x+1,Seq=y),表示同意并同步序列号。
- 第三次握手 :客户端收到 SYN+ACK,回复 ACK 报文(ACK=1,Ack=y+1,Seq=x+1),连接建立。
为什么不是两次?
核心原因有三个:
-
防止历史连接干扰
如果一个延迟很久的旧 SYN 报文到达服务端,服务端建立了连接,而客户端早已关闭,会导致"幽灵连接"。第三次握手能让客户端确认对端的响应是否是自己期望的。
-
确保双方序列号同步
TCP 使用序列号来确认和重排数据包。三次握手可以保证双方初始序列号(ISN)都被确认,避免因序列号不同步导致数据错乱。
-
避免资源浪费
如果没有第三步,服务端无法确认客户端是否收到 SYN+ACK。ACK 丢失时,服务端会一直等待,造成"半开连接"占满资源。
Linux 内核细节
- ISN 生成方式:基于一个随时间递增的计数器 + 源/目的 IP + 端口等进行哈希,保证不可预测性(防止 TCP 序列号攻击)。
- 重试策略:第一次握手丢失,客户端会按指数退避(1s、2s、4s...)重发 SYN,次数由
tcp_syn_retries
控制。- SYN 攻击防御:通过增大半连接队列(
somaxconn
)、开启 SYN Cookies(tcp_syncookies=1
)、减少 SYN+ACK
重传次数(tcp_synack_retries
)等手段缓解。
3. 四次挥手:优雅断开连接
断开连接的四次挥手流程是这样的:
- 第一次挥手:主动关闭方(假设是客户端)发送 FIN 报文,表示没有数据要发了。
- 第二次挥手 :服务端收到 FIN,返回 ACK,进入
CLOSE_WAIT
状态。这时服务端可能还有数据要发送。 - 第三次挥手:服务端发送 FIN 报文,表示自己也发完了。
- 第四次挥手 :客户端返回 ACK,进入
TIME_WAIT
状态,等待 2MSL 后释放连接。
为什么需要四次?
因为 TCP 是全双工的,关闭连接需要双方分别关闭发送方向。FIN 只能单向关闭,所以需要两对 FIN+ACK。
TIME_WAIT 的意义:
- 确保最后的 ACK 能被对端收到(ACK 丢失时,对端会重发 FIN)。
- 等待网络中可能残留的旧数据包消失,避免下一个连接收到脏数据。
Linux 内核细节:
- 默认 TIME_WAIT 持续 60s(
tcp_fin_timeout
可调)。- 高并发短连接下,TIME_WAIT 会占用大量端口,可通过
tcp_tw_reuse
(复用 TIME_WAIT 连接)和tcp_tw_recycle
(已废弃)优化。
4. 数据传输中的细节
TCP 在传输阶段依赖几个关键机制:
- 滑动窗口 (Flow Control):接收方通过
Window Size
告诉发送方自己还能接收多少数据,防止溢出。 - 拥塞控制(Congestion Control):经典算法包括慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快速重传(Fast Retransmit)、快速恢复(Fast Recovery)。Linux 现在常用 CUBIC。
- 超时重传(RTO):根据 RTT(往返时延)动态计算。
- 延迟确认(Delayed ACK):减少 ACK 数量,但可能影响实时性。
- Nagle 算法:合并小包,减少包数量,但会增加延迟(Netty、游戏开发常关闭)。
5. 工程实践与常见坑
- HTTP Keep-Alive:复用 TCP 连接,减少三次握手开销,但要注意服务器连接数限制。
- 短连接高并发:TIME_WAIT 爆炸时,优化内核参数或使用连接池。
- 半关闭连接:MySQL Binlog dump 是典型场景,客户端发送完请求,保持接收端开启。
- 防御 SYN Flood:云厂商的负载均衡会在内核前就拦截,但自己写 TCP 服务要考虑 SYN Cookies。