TCP(Transmission Control Protocol)是互联网最核心的协议之一。它以面向连接、可靠传输、字节流、全双工的特性,支撑了绝大多数网络应用。深入理解 TCP,是成为优秀后端工程师的必经之路。
一、TCP 头部结构
1.1 报文头格式
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 | Flags |U|A|P|R|S|F| | |
| Offset| Reser |R|C|S|S|Y|I| Window Size | |
| | ved |G|K|H|T|N|N| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (Variable) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Flags:URG ACK PSH RST SYN FIN
↓ ↓ ↓ ↓ ↓ ↓
紧急 确认 推送 重置 同步 结束
1.2 关键字段说明
| 字段 | 大小 | 说明 |
|---|
| Source/Dest Port | 各 16 bits | 标识发送/接收应用 |
| Sequence Number | 32 bits | 本报文段数据的第一个字节序号 |
| Ack Number | 32 bits | 期望收到的下一个字节序号 |
| Window Size | 16 bits | 接收窗口大小(流量控制) |
| Data Offset | 4 bits | 头部长度(以 4 字节为单位) |
| Flags | 6 bits | 控制位(SYN、ACK、FIN 等) |
二、三次握手与四次挥手
2.1 三次握手(建立连接)
客户端(状态) 服务端(状态)
CLOSED LISTEN
│ │
│ ─────── SYN=1, seq=x ────────────→ │
│ SYN_SENT │ SYN_RCVD
│ │
│ ←──── SYN=1, ACK=1, seq=y, ack=x+1 │
│ ESTABLISHED │
│ │
│ ───── ACK=1, seq=x+1, ack=y+1 ───→ │
│ │ ESTABLISHED
│ │
为什么是三次而不是两次?
● 防止历史重复连接的初始化造成混乱
● 同步双方的初始序列号
● 确认双方的收发能力正常
2.2 四次挥手(关闭连接)
客户端 服务端
ESTABLISHED ESTABLISHED
│ │
│ ───── FIN=1, seq=u ─────────────→ │
│ FIN_WAIT_1 │ CLOSE_WAIT
│ │
│ ←──── ACK=1, seq=v, ack=u+1 ──────│
│ FIN_WAIT_2 │
│ │
│ ←──── FIN=1, seq=w, ack=u+1 ──────│
│ TIME_WAIT │ LAST_ACK
│ │
│ ───── ACK=1, seq=u+1, ack=w+1 ───→ │
│ │ CLOSED
│ (等待 2MSL 后关闭) │
│ CLOSED │
TIME_WAIT 状态存在的意义:
● 确保最后一个 ACK 能到达对方(可重传)
● 等待 2MSL 让网络中残留的报文段全部消失
● 防止旧的连接报文段被新连接误接收
2.3 Java Socket 观察状态
// 获取 Socket 连接状态
Socket socket = new Socket("example.com", 80);
System.out.println("本地地址: " + socket.getLocalAddress());
System.out.println("本地端口: " + socket.getLocalPort());
System.out.println("远程地址: " + socket.getInetAddress());
System.out.println("远程端口: " + socket.getPort());
System.out.println("是否连接: " + socket.isConnected());
System.out.println("是否关闭: " + socket.isClosed());
// 查看系统 TCP 连接状态(Linux)
// cat /proc/net/tcp
// ss -tan | awk '{print $1}' | sort | uniq -c
三、状态机
+--------+
主动打开 | CLOSED | 被动打开
┌────────→│ │←────────┐
│ +--------+ │
│ ↑ │
↓ │ ↓
+----------+ 超时/关闭 +----------+
│ SYN_SENT │←────────────→│ LISTEN │
+----------+ +----------+
│ SYN+ACK │
收到 SYN+ACK 收到 SYN │
│──────────────────────────→│
↓ ↓
+──────────+ +──────────+
│ESTABLISHED│←──────────→│SYN_RCVD │
│ │ 三次握手完成 │ │
+──────────+ +──────────+
│ │
收到 FIN(主动关闭) ACK(被动关闭方确认)
│ │
↓ ↓
+──────────+ +──────────+
│FIN_WAIT_1│ │CLOSE_WAIT│
+──────────+ +──────────+
│ │
收到 ACK │
│ 应用层 close()
↓ ↓
+──────────+ +──────────+
│FIN_WAIT_2│ │ LAST_ACK │
+──────────+ +──────────+
│收到 FIN │收到 ACK
│ │
↓ ↓
+──────────+ +────────+
│ TIME_WAIT│ │ CLOSED |
+──────────+ +────────+
│
2MSL 超时
│
↓
+--------+
│ CLOSED |
+--------+
四、可靠传输机制
4.1 序列号与确认应答
发送方 接收方
│ ── seq=1, data="Hel" ─────────→ │
│ ── seq=4, data="lo " ─────────→ │
│ ── seq=8, data="Wor" ─────────→ │
│ │
│ ←──── ack=12 ────────────────── │ 期望收到 seq=12
│ │
│ ── seq=12, data="ld!" ────────→ │
4.2 超时重传(RTO)
发送方 接收方
│ ── seq=1, data="A" ───────────→ │
│ [等待 RTO 超时] │
│ [未收到 ACK] │
│ ── seq=1, data="A" ───────────→ │ 重传
│ │
│ ←──── ack=2 ─────────────────── │
RTO 计算(Jacobson / Karn 算法):
SRTT = α * SRTT + (1-α) * RTT_sample // 平滑 RTT
RTTVAR = β * RTTVAR + (1-β) * |SRTT - RTT_sample|
RTO = SRTT + 4 * RTTVAR
4.3 快速重传
发送方 接收方
│ ── seq=1 ─────────────────────→ │
│ ── seq=2(丢失)────────────────→ │
│ ── seq=3 ─────────────────────→ │
│ ── seq=4 ─────────────────────→ │
│ │
│ ←──── ack=2 ─────────────────── │ (期望 seq=2)
│ ── seq=5 ─────────────────────→ │
│ │
│ ←──── ack=2 ─────────────────── │ (重复 ACK #1)
│ ── seq=6 ─────────────────────→ │
│ │
│ ←──── ack=2 ─────────────────── │ (重复 ACK #2)
│ │
│ ── seq=2 ─────────────────────→ │ (快速重传,不等 RTO)
│ │
│ ←──── ack=7 ─────────────────── │ (累积确认)
五、流量控制:滑动窗口
5.1 原理
发送方缓冲区:
| 已发送已确认 | 已发送未确认 | 可发送未发送 | 不可发送 |
| <-1 | 2-5 | 6-9 | 10+ │
└────────────┴───────────────┴──────────────┴───────────┘
↑ ↑ ↑
SND.UNA SND.NXT SND.UNA + SND.WND
接收方缓冲区:
| 已接收已确认 | 期望接收 | 未准备接收 |
| <-1 │ 2-6 │ 7+ │
└──────────────┴──────────────┴───────────┘
↑ ↑
RCV.NXT RCV.NXT + RCV.WND
5.2 零窗口与窗口探测
接收方缓冲区满时,发送 Window=0 的 ACK:
发送方 接收方
│ ── seq=100, data ─────────────→ │
│ │
│ ←──── ack=101, win=0 ────────── │ 缓冲区满!
│ │
│ ── seq=101, 1 byte (探测) ─────→ │ 窗口探测
│ │
│ ←──── ack=101, win=0 ────────── │ 仍然满
│ │
│ ... 持续探测 ... │
│ │
│ ←──── ack=101, win=4096 ─────── │ 有空间了!
│ │
│ ── seq=101, data ─────────────→ │ 恢复发送
六、拥塞控制
6.1 四个核心算法
| 算法 | 阶段 | 机制 |
|---|
| 慢启动 | 连接初始 | 拥塞窗口(cwnd)指数增长 |
| 拥塞避免 | 慢启动阈值后 | cwnd 线性增长 |
| 快速重传 | 收到 3 个 dup ACK | 立即重传,不等待 RTO |
| 快速恢复 | 快速重传后 | cwnd 减半 + 3,避免慢启动 |
6.2 状态变化图
连接建立
│
▼
┌─────────┐ cwnd < ssthresh ┌─────────┐
│ 慢启动 │ ◄─────────────────────│ 快速恢复 │
│(指数增长)│ │ │
└────┬────┘ └────┬────┘
│ cwnd >= ssthresh │ 拥塞事件
▼ │
┌─────────┐ ┌────────┐ │
│拥塞避免 │────→│ 拥塞 │────────────┘
│(线性增长)│ │ 超时 │
└─────────┘ └────────┘
│
▼
ssthresh = cwnd / 2
cwnd = 1
6.3 现代拥塞控制算法
| 算法 | 特点 | 适用场景 |
|---|
| Reno | 经典算法,快速恢复 | 通用场景 |
| CUBIC | Linux 默认,三次函数增长 | 高 BDP 网络 |
| BBR | Google 开发,基于带宽和 RTT 估算 | 高丢包率网络 |
| Vegas | 基于延时检测拥塞 | 低延迟网络 |
# Linux 查看当前拥塞控制算法
cat /sys/module/tcp_congestion_control/parameters/available
cat /proc/sys/net/ipv4/tcp_congestion_control
# 临时修改为 BBR
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
七、TCP 调优参数
# /etc/sysctl.conf
# 连接队列大小
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 端口复用与快速回收
net.ipv4.tcp_tw_reuse = 1
# WARNING: tcp_tw_recycle 已在 Linux 4.12 移除
# TCP Keepalive
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
# 窗口缩放
net.ipv4.tcp_window_scaling = 1
# 缓冲区大小
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# BBR 拥塞控制
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
# 生效
sudo sysctl -p
// Java Socket 调优
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.setReceiveBufferSize(128 * 1024);
Socket socket = serverSocket.accept();
socket.setTcpNoDelay(true); // 禁用 Nagle 算法
socket.setSoTimeout(30000); // 读超时
socket.setSendBufferSize(64 * 1024);
socket.setReceiveBufferSize(64 * 1024);
socket.setKeepAlive(true); // TCP Keepalive
socket.setOOBInline(true); // 接收紧急数据
八、常见问题与排查
| 现象 | 可能原因 | 排查方法 |
|---|
| 大量 TIME_WAIT | 短连接频繁 | `netstat -tan |
| 大量 CLOSE_WAIT | 服务端未 close | 检查代码中 socket 关闭逻辑 |
| SYN Flood 攻击 | 恶意连接 | 启用 SYN Cookies |
| 连接耗时高 | DNS 解析慢/网络延迟 | tcpdump / Wireshark 抓包分析 |
| 吞吐量低 | 窗口太小/丢包 | 检查 ss -tin 看 cwnd、ssthresh |
九、总结
| 机制 | 目的 | 实现方式 |
|---|
| 三次握手 | 可靠建立连接 | SYN → SYN+ACK → ACK |
| 四次挥手 | 优雅关闭连接 | FIN → ACK → FIN → ACK |
| 序列号 | 有序交付、去重 | 每字节编号 |
| 确认应答 | 可靠传输 | 累积 ACK |
| 超时重传 | 丢包恢复 | RTO 计算、快速重传 |
| 滑动窗口 | 流量控制 | 接收窗口通告 |
| 拥塞控制 | 网络保护 | 慢启动、拥塞避免、BBR |
TCP 通过精心设计的机制,在不可靠的 IP 层之上构建了可靠的传输通道。理解这些机制,不仅是网络编程的基础,更是排查线上问题的利器。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。