mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
694 字
2 分钟
TCP 连接建立过程:三次握手深度解析
2023-02-03

前言#

TCP 三次握手是网络编程的基石,也是面试必考知识点。本文深入剖析三次握手的每个细节,从协议层面理解连接建立的完整过程。

三次握手概览#

sequenceDiagram participant C as Client participant S as Server Note over C: CLOSED Note over S: LISTEN Note over C: 第一次握手 C->>S: SYN=1, seq=x Note over C: SYN_SENT Note over S: 收到 SYN Note over S: 第二次握手 S->>C: SYN=1, ACK=1, seq=y, ack=x+1 Note over S: SYN_RCVD Note over C: 收到 SYN+ACK Note over C: 第三次握手 C->>S: ACK=1, seq=x+1, ack=y+1 Note over C: ESTABLISHED Note over S: 收到 ACK Note over S: ESTABLISHED Note over C,S: 连接建立完成

一、TCP 报文结构#

1.1 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 Offset | Reserved |U|A|P|R|S|F| Window |
| | |R|C|S|S|Y|I| |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.2 关键标志位#

标志位名称含义
SYNSynchronize发起连接
ACKAcknowledge确认
FINFinish关闭连接
RSTReset重置连接
PSHPush推送数据
URGUrgent紧急数据

1.3 序列号与确认号#

// 序列号(Sequence Number)
// - 标识数据字节流的第一个字节的序号
// - SYN 和 FIN 各占用一个序列号
// 确认号(Acknowledgment Number)
// - 期望收到的下一个字节的序号
// - 表示序号之前的所有字节都已收到
// 示例
客户端发送: (SYN, (seq = 100));
服务端回复: (SYN + ACK, (seq = 200), (ack = 101)); // 确认收到 SYN
客户端发送: (ACK, (seq = 101), (ack = 201)); // 确认收到 SYN

二、三次握手详细流程#

2.1 第一次握手:客户端发送 SYN#

flowchart TB A[应用程序调用 connect] --> B[创建 TCP 控制块] B --> C[选择初始序列号 ISN] C --> D[构造 SYN 报文] D --> E[发送 SYN] E --> F[状态: SYN_SENT] F --> G{等待 SYN+ACK}

SYN 报文结构

Source Port: 54321
Destination Port: 80
Sequence Number: 1000 (ISN)
Acknowledgment Number: 0
Flags: SYN=1
Window: 65535
Options:
- MSS = 1460
- Window Scale = 7
- SACK Permitted
- Timestamp

ISN(初始序列号)选择

// Linux 内核 ISN 生成算法
// 基于时钟 + MD5 哈希,防止预测攻击
u32 secure_tcp_sequence_number(__be32 saddr, __be32 daddr,
__be16 sport, __be16 dport)
{
u32 hash[MD5_DIGEST_WORDS];
hash[0] = saddr;
hash[1] = daddr;
hash[2] = sport << 16 | dport;
hash[3] = tcp_clock_ms();
md5_transform(hash, key);
return hash[0];
}

2.2 第二次握手:服务端回复 SYN+ACK#

flowchart TB A[收到 SYN 报文] --> B[检查目标端口是否监听] B -->|是| C[创建连接请求] B -->|否| D[回复 RST] C --> E[放入 SYN 队列] E --> F[选择 ISN] F --> G[构造 SYN+ACK] G --> H[发送 SYN+ACK] H --> I[状态: SYN_RCVD]

服务端 SYN+ACK 报文

Source Port: 80
Destination Port: 54321
Sequence Number: 2000 (服务端 ISN)
Acknowledgment Number: 1001 (客户端 ISN + 1)
Flags: SYN=1, ACK=1
Window: 65535
Options:
- MSS = 1460
- Window Scale = 7
- SACK Permitted
- Timestamp

2.3 第三次握手:客户端发送 ACK#

flowchart TB A[收到 SYN+ACK] --> B[验证 ACK 序号] B --> C[构造 ACK 报文] C --> D[发送 ACK] D --> E[状态: ESTABLISHED] E --> F[可以发送数据]

ACK 报文

Source Port: 54321
Destination Port: 80
Sequence Number: 1001
Acknowledgment Number: 2001
Flags: ACK=1
Window: 65535

2.4 服务端收到 ACK 后#

flowchart TB A[收到 ACK] --> B[验证序号] B --> C[从 SYN 队列移除] C --> D[移入 Accept 队列] D --> E[状态: ESTABLISHED] E --> F[等待应用 accept] F --> G[应用调用 accept] G --> H[返回已建立连接]

三、状态机与队列#

3.1 TCP 状态转换#

stateDiagram-v2 [*] --> CLOSED CLOSED --> SYN_SENT: 主动打开/SYN CLOSED --> LISTEN: 被动打开 LISTEN --> SYN_RCVD: 收到 SYN/发送 SYN+ACK LISTEN --> CLOSED: 关闭 SYN_SENT --> ESTABLISHED: 收到 SYN+ACK/发送 ACK SYN_SENT --> CLOSED: 超时/RST SYN_RCVD --> ESTABLISHED: 收到 ACK SYN_RCVD --> LISTEN: 超时 ESTABLISHED --> FIN_WAIT_1: 主动关闭/FIN ESTABLISHED --> CLOSE_WAIT: 收到 FIN FIN_WAIT_1 --> FIN_WAIT_2: 收到 ACK FIN_WAIT_1 --> CLOSING: 收到 FIN+ACK FIN_WAIT_2 --> TIME_WAIT: 收到 FIN CLOSE_WAIT --> LAST_ACK: 发送 FIN LAST_ACK --> CLOSED: 收到 ACK CLOSING --> TIME_WAIT: 收到 ACK TIME_WAIT --> CLOSED: 2MSL 超时

3.2 半连接队列与全连接队列#

flowchart LR subgraph 服务端 A[SYN 队列<br/>半连接队列] --> B[Accept 队列<br/>全连接队列] end C[SYN] --> A A --> D[SYN+ACK] E[ACK] --> A A -.->|第三次握手后| B F[accept] --> B

队列参数

# 查看队列大小
sysctl net.ipv4.tcp_max_syn_backlog # SYN 队列大小
sysctl net.core.somaxconn # Accept 队列大小
# 查看队列状态
netstat -s | grep "listen queue"
ss -lnt # Recv-Q 显示当前 Accept 队列长度

队列溢出处理

情况处理方式
SYN 队列满丢弃 SYN,不回复(或根据 tcp_syncookies 回复)
Accept 队列满丢弃 ACK,客户端重传

3.3 SYN Cookies#

防御 SYN Flood 攻击:

// SYN Cookie 生成
// 将连接信息编码到 ISN 中
u32 syn_cookie =
MD5(saddr + daddr + sport + dport + secret)[0:24] +
(timestamp % 24) << 24;
// 验证 SYN Cookie
// 通过 ACK 的确认号反推连接信息
# 启用 SYN Cookies
sysctl -w net.ipv4.tcp_syncookies=1

四、超时与重传#

4.1 SYN 超时处理#

sequenceDiagram participant C as Client participant S as Server C->>S: SYN (第1次) Note over C: 等待 1s C->>S: SYN (第2次重传) Note over C: 等待 2s C->>S: SYN (第3次重传) Note over C: 等待 4s C->>S: SYN (第4次重传) Note over C: 等待 8s C->>S: SYN (第5次重传) Note over C: 等待 16s Note over C: 总计 31s 后放弃 Note over C: 返回 ETIMEDOUT

重传参数

# 查看 TCP 超时参数
sysctl net.ipv4.tcp_syn_retries # 主动打开 SYN 重试次数(默认 5)
sysctl net.ipv4.tcp_synack_retries # 被动打开 SYN+ACK 重试次数(默认 5)
sysctl net.ipv4.tcp_retries2 # 数据重传次数(默认 15)

4.2 指数退避算法#

// 重传时间计算(简化)
const RTO = 1; // 初始重传超时时间
const MAX_RETRIES = 5;
for (let i = 0; i < MAX_RETRIES; i++) {
const timeout = RTO * Math.pow(2, i);
console.log(`第 ${i + 1} 次重传,等待 ${timeout}s`);
}
// 输出:
// 第 1 次重传,等待 1s
// 第 2 次重传,等待 2s
// 第 3 次重传,等待 4s
// 第 4 次重传,等待 8s
// 第 5 次重传,等待 16s

五、选项协商#

5.1 TCP 选项格式#

选项格式:
Kind | Length | Data
常用选项:
+------+------+------+
| Kind | Len | Data |
+------+------+------+
| 2 | 4 | MSS | 最大段大小
+------+------+------+
| 3 | 3 | WS | 窗口缩放
+------+------+------+
| 4 | 2 | - | SACK 允许
+------+------+------+
| 8 | 10 | TS | 时间戳
+------+------+------+

5.2 MSS(最大段大小)#

MSS = MTU - IP头(20) - TCP头(20)
以太网 MTU = 1500
MSS = 1500 - 20 - 20 = 1460
flowchart LR A[应用数据 1460 字节] --> B[TCP 头 20 字节] B --> C[IP 头 20 字节] C --> D[以太网帧 1500 字节]

5.3 Window Scale#

// 窗口缩放因子
// 允许窗口大小超过 65535 字节
// 首部 Window 字段 16 位,最大 65535
// Window Scale 选项指定左移位数
// 示例:Scale = 7
// 实际窗口 = Window << 7
// 最大窗口 = 65535 << 7 = 8,388,480 字节 ≈ 8MB

5.4 SACK(选择性确认)#

sequenceDiagram participant S as 发送方 participant R as 接收方 S->>R: 数据 1-1000 S->>R: 数据 1001-2000 S->>R: 数据 2001-3000 (丢失) S->>R: 数据 3001-4000 S->>R: 数据 4001-5000 R->>S: ACK=1001 R->>S: ACK=2001 R->>S: ACK=2001 + SACK=3001-4000 R->>S: ACK=2001 + SACK=3001-5000 Note over S: 只重传 2001-3000 S->>R: 数据 2001-3000

六、连接异常处理#

6.1 RST 报文#

触发 RST 的情况:

场景原因
端口未监听目标端口没有进程监听
连接已关闭向已关闭的连接发送数据
序列号错误收到序列号不在窗口内
请求超时SYN 队列溢出(特定配置下)
主动中止应用调用 RST 关闭
sequenceDiagram participant C as Client participant S as Server C->>S: SYN → 端口 9999 Note over S: 端口未监听 S->>C: RST Note over C: 连接被拒绝 ECONNREFUSED

6.2 连接超时#

# 客户端超时
connect() → ETIMEDOUT(127s,取决于配置)
# 服务端超时
recv() → ETIMEDOUT(数据传输超时)
# Keepalive 超时
sysctl net.ipv4.tcp_keepalive_time # 空闲多久开始探测(默认 7200s)
sysctl net.ipv4.tcp_keepalive_intvl # 探测间隔(默认 75s)
sysctl net.ipv4.tcp_keepalive_probes # 探测次数(默认 9)

七、三次握手的意义#

7.1 为什么是三次?#

flowchart TB subgraph 假设只有两次握手 A1[Client → SYN] --> A2[Server → SYN+ACK] A2 --> A3[连接建立] A3 --> A4[问题: 旧的 SYN 延迟到达] A4 --> A5[Server 误建立连接] end subgraph 三次握手 B1[Client → SYN] --> B2[Server → SYN+ACK] B2 --> B3[Client → ACK] B3 --> B4[确认双方收发能力正常] end

三次握手解决的问题

  1. 防止旧的重复连接:延迟的 SYN 不会导致错误连接
  2. 同步序列号:双方都需要确认对方的 ISN
  3. 确认双向通信:验证双方的收发能力

7.2 防止 SYN Flood 攻击#

flowchart LR subgraph 攻击 A1[伪造 SYN] --> A2[SYN 队列满] A2 --> A3[正常连接被拒绝] end subgraph 防御措施 B1[SYN Cookies] --> B2[无状态验证] B3[减少 SYN+ACK 重试] --> B4[快速回收] B5[增大队列] --> B6[提高容量] end
# 防御配置
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_synack_retries=2
sysctl -w net.ipv4.tcp_max_syn_backlog=8192

八、性能优化#

8.1 快速打开(TFO)#

TCP Fast Open 跳过三次握手:

sequenceDiagram participant C as Client participant S as Server Note over C,S: 首次连接 C->>S: SYN + Cookie Request S->>C: SYN+ACK + Cookie Note over C: 保存 Cookie Note over C,S: 后续连接 C->>S: SYN + Cookie + Data Note over S: 验证 Cookie S->>C: SYN+ACK + Response Note over C,S: 0-RTT 数据传输
# 启用 TFO(Linux 3.7+)
sysctl -w net.ipv4.tcp_fastopen=3

8.2 同时打开#

两端同时发起连接:

sequenceDiagram participant A as 端点 A participant B as 端点 B A->>B: SYN, seq=x B->>A: SYN, seq=y Note over A: SYN_SENT Note over B: SYN_SENT A->>B: ACK, ack=y+1 B->>A: ACK, ack=x+1 Note over A: ESTABLISHED Note over B: ESTABLISHED

九、调试与诊断#

9.1 抓包分析#

# 使用 tcpdump 抓取三次握手
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -n
# 输出示例
IP 192.168.1.100.54321 > 192.168.1.1.80: Flags [S], seq 1000
IP 192.168.1.1.80 > 192.168.1.100.54321: Flags [S.], seq 2000, ack 1001
IP 192.168.1.100.54321 > 192.168.1.1.80: Flags [.], ack 2001

9.2 状态查看#

# 查看连接状态
netstat -nat | grep ESTABLISHED
ss -nat state established
# 查看 SYN_RECV 状态(可能有攻击)
netstat -nat | grep SYN_RECV
ss -nat state syn-recv
# 统计各状态连接数
netstat -nat | awk '{print $6}' | sort | uniq -c

9.3 内核参数调优#

/etc/sysctl.conf
# 增大 SYN 队列
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
# 减少重试次数(防御 SYN Flood)
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 3
# 启用 SYN Cookies
net.ipv4.tcp_syncookies = 1
# 快速回收 TIME_WAIT
net.ipv4.tcp_tw_reuse = 1
# 缩短 TIME_WAIT 时间
net.ipv4.tcp_fin_timeout = 30

总结#

三次握手完整流程图#

flowchart TB subgraph 客户端 C1[connect 调用] --> C2[发送 SYN] C2 --> C3[SYN_SENT] C3 --> C4[收到 SYN+ACK] C4 --> C5[发送 ACK] C5 --> C6[ESTABLISHED] end subgraph 服务端 S1[listen 调用] --> S2[LISTEN] S2 --> S3[收到 SYN] S3 --> S4[创建请求入 SYN 队列] S4 --> S5[发送 SYN+ACK] S5 --> S6[SYN_RCVD] S6 --> S7[收到 ACK] S7 --> S8[移入 Accept 队列] S8 --> S9[ESTABLISHED] S9 --> S10[accept 返回] end C2 -.->|网络| S3 S5 -.->|网络| C4 C5 -.->|网络| S7

关键要点#

  1. 三次握手:SYN → SYN+ACK → ACK
  2. 状态转换:CLOSED → SYN_SENT → ESTABLISHED
  3. 队列管理:SYN 队列 + Accept 队列
  4. 超时重传:指数退避算法
  5. 选项协商:MSS、Window Scale、SACK
  6. 安全防御:SYN Cookies、队列调优

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

TCP 连接建立过程:三次握手深度解析
https://blog.souloss.com/posts/principles/tcp-connection-establishment/
作者
Souloss
发布于
2023-02-03
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时