TCP 协议
2026/1/15大约 3 分钟
TCP 协议
TCP vs UDP
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠 |
| 顺序 | 保证顺序 | 不保证 |
| 速度 | 较慢 | 较快 |
| 头部 | 20-60 字节 | 8 字节 |
| 应用 | HTTP、FTP | DNS、视频流 |
TCP 头部结构
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+标志位
| 标志 | 说明 |
|---|---|
| SYN | 同步,建立连接 |
| ACK | 确认 |
| FIN | 结束,关闭连接 |
| RST | 重置连接 |
| PSH | 推送,立即传输 |
| URG | 紧急数据 |
三次握手
为什么是三次握手?
确认双方收发能力
- 第一次:服务端确认客户端发送能力
- 第二次:客户端确认服务端收发能力
- 第三次:服务端确认客户端接收能力
防止历史连接
- 两次握手无法防止历史 SYN 报文建立错误连接
同步序列号
- 双方交换初始序列号
SYN 洪泛攻击
攻击者发送大量 SYN 请求,不完成握手,耗尽服务器资源。
防御措施:
- SYN Cookie
- 限制半连接数
- 缩短 SYN_RCVD 超时时间
四次挥手
为什么是四次挥手?
TCP 是全双工,需要双方各自关闭发送通道。
- 第一次:客户端关闭发送
- 第二次:服务端确认
- 第三次:服务端关闭发送
- 第四次:客户端确认
为什么要 TIME_WAIT?
确保最后的 ACK 到达
- 如果 ACK 丢失,服务端会重发 FIN
让旧连接的报文消失
- 防止旧报文影响新连接
TIME_WAIT 过多怎么办?
# 开启 TIME_WAIT 重用
net.ipv4.tcp_tw_reuse = 1
# 开启快速回收(不推荐)
net.ipv4.tcp_tw_recycle = 1
# 减少 TIME_WAIT 时间
net.ipv4.tcp_fin_timeout = 30流量控制
通过滑动窗口控制发送速率,防止接收方缓冲区溢出。
滑动窗口
发送窗口:
+---+---+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10|
+---+---+---+---+---+---+---+---+---+---+
|已发送已确认|已发送未确认| 可发送 |不可发送|零窗口
接收方窗口为 0 时,发送方停止发送。
- 发送方定时发送窗口探测报文
- 接收方窗口恢复后通知发送方
拥塞控制
防止网络拥塞,控制发送速率。
拥塞控制算法
慢启动
- 初始 cwnd = 1 MSS
- 每收到一个 ACK,cwnd 翻倍
- 达到 ssthresh 后进入拥塞避免
拥塞避免
- cwnd 线性增长
- 每个 RTT,cwnd += 1
快速重传
收到 3 个重复 ACK,立即重传丢失的报文。
快速恢复
- ssthresh = cwnd / 2
- cwnd = ssthresh + 3
- 进入拥塞避免
TCP 粘包
什么是粘包?
多个小包合并成一个大包,或一个大包拆分成多个小包。
解决方案
- 固定长度:每个包固定长度
- 分隔符:用特殊字符分隔
- 长度字段:包头包含长度信息
// Netty 解决方案
// 固定长度
new FixedLengthFrameDecoder(100);
// 分隔符
new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter());
// 长度字段
new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);TCP 保活
Keep-Alive
# 保活时间(默认 2 小时)
net.ipv4.tcp_keepalive_time = 7200
# 探测间隔
net.ipv4.tcp_keepalive_intvl = 75
# 探测次数
net.ipv4.tcp_keepalive_probes = 9应用层心跳
更灵活,可以携带业务数据。