TCP连接状态详解/同时打开Simultaneous Open
概述
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在TCP连接的生命周期中,连接会经历不同的状态,这些状态反映了连接的当前状态和可执行的操作。
TCP连接状态总览
TCP连接主要有以下几种状态:
- CLOSED: 初始状态,表示连接已关闭
- LISTEN: 服务器等待连接请求
- SYN_SENT: 客户端已发送SYN包,等待服务器响应
- SYN_RECEIVED: 服务器收到SYN包,已发送SYN+ACK包
- ESTABLISHED: 连接已建立,可以传输数据
- FIN_WAIT_1: 主动关闭方发送FIN包,等待ACK
- FIN_WAIT_2: 主动关闭方收到ACK,等待对方FIN包
- CLOSE_WAIT: 被动关闭方收到FIN包,等待应用层关闭
- CLOSING: 双方同时关闭连接,这是一个相对罕见的状态
- LAST_ACK: 被动关闭方发送FIN包,等待ACK
- TIME_WAIT: 主动关闭方等待2MSL时间
连接建立过程(三次握手)
状态转换流程
CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED↑ ↓ ↓ ↓└─────────┴───────────┴───────────┘
详细过程
-
CLOSED → LISTEN
- 服务器调用
listen()
函数 - 进入LISTEN状态,等待客户端连接请求
- 服务器调用
-
LISTEN → SYN_RECEIVED
- 服务器收到客户端的SYN包
- 发送SYN+ACK包
- 进入SYN_RECEIVED状态
-
SYN_RECEIVED → ESTABLISHED
- 服务器收到客户端的ACK包
- 连接建立完成
- 进入ESTABLISHED状态
三次握手时序图
连接断开过程(四次挥手)
状态转换流程
主动关闭方:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED↓ ↓ ↓
被动关闭方:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
详细过程
主动关闭方(可以是客户端或服务器)
-
ESTABLISHED → FIN_WAIT_1
- 主动关闭方调用
close()
函数 - 发送FIN包
- 进入FIN_WAIT_1状态
- 主动关闭方调用
-
FIN_WAIT_1 → FIN_WAIT_2
- 收到对方的ACK包
- 进入FIN_WAIT_2状态
-
FIN_WAIT_2 → TIME_WAIT
- 收到对方的FIN包
- 发送ACK包
- 进入TIME_WAIT状态
-
TIME_WAIT → CLOSED
- 等待2MSL时间
- 进入CLOSED状态
被动关闭方(可以是服务器或客户端)
-
ESTABLISHED → CLOSE_WAIT
- 收到对方的FIN包
- 发送ACK包
- 进入CLOSE_WAIT状态
-
CLOSE_WAIT → LAST_ACK
- 应用层调用
close()
函数 - 发送FIN包
- 进入LAST_ACK状态
- 应用层调用
-
LAST_ACK → CLOSED
- 收到对方的ACK包
- 进入CLOSED状态
四次挥手时序图
状态转换图
重要状态说明
ESTABLISHED
- 连接已建立,可以双向传输数据
- 这是TCP连接的正常工作状态
- 可以发送和接收数据
TIME_WAIT
- 主动关闭方必须等待2MSL时间
- MSL(Maximum Segment Lifetime)是报文段在网络中的最大生存时间
- 通常为2分钟
- 目的:
- 确保最后的ACK包能够到达对方
- 让旧连接的分组在网络中消失
- 避免新连接收到旧连接的数据
CLOSE_WAIT
- 被动关闭方等待应用层关闭连接
- 此时仍可以发送数据
- 如果应用层不及时关闭,可能导致连接长时间处于此状态
CLOSING
- 出现原因:双方几乎同时调用
close()
函数,导致同时发送FIN包 - 状态描述:主动关闭方在FIN_WAIT_1状态下收到对方的FIN包
- 转换过程:FIN_WAIT_1 → CLOSING → TIME_WAIT → CLOSED
- 特点:
- 这是一个相对罕见的状态,因为需要精确的时间同步
- 双方都进入了关闭过程,但都还没有收到对方的ACK
- 当收到对方的ACK后,会转换到TIME_WAIT状态
- 实际场景:
- 网络延迟极低的环境下,双方同时发起关闭
- 某些应用协议中,客户端和服务器约定同时关闭连接
- 网络异常情况下,双方都认为需要关闭连接
协议栈支持情况
- RFC 793标准:TCP协议标准文档中明确定义了CLOSING状态
- 主流操作系统:Linux、Windows、macOS等操作系统的TCP/IP协议栈都支持CLOSING状态
- 网络设备:路由器、交换机等网络设备也支持此状态
CLOSING状态的识别特征
-
包序列特征:
- 双方几乎同时发送FIN包
- FIN包的序列号接近
- 时间间隔极短(通常<1ms)
-
状态转换特征:
- 连接从FIN_WAIT_1直接转换到CLOSING
- 跳过FIN_WAIT_2状态
- 快速转换到TIME_WAIT状态
-
网络环境特征:
- 低延迟网络环境
- 双方应用层同时调用close()
- 网络拥塞或异常情况
实际测试方法
# 创建测试脚本,模拟同时关闭
#!/bin/bash
# 客户端和服务器同时关闭连接测试
nc -l 8080 &
SERVER_PID=$!
sleep 1
nc localhost 8080 &
CLIENT_PID=$!
sleep 1
kill $SERVER_PID $CLIENT_PID# 监控TCP状态变化
watch -n 0.1 'ss -tan | grep -E "(FIN_WAIT|CLOSING|TIME_WAIT)"'
TCP同时打开(Simultaneous Open)
概念说明
TCP同时打开是指两个应用程序同时向对方发起连接请求,而不是传统的客户端-服务器模式。这是TCP协议的一个重要特性,允许两个对等方同时建立连接。
同时打开的状态转换过程
状态转换流程
CLOSED → SYN_SENT → SYN_RECEIVED → ESTABLISHED↑ ↓ ↓ ↓└─────────┴───────────┴───────────┘
详细过程
-
双方同时发送SYN包
- 两个应用程序同时调用
connect()
函数 - 双方都进入SYN_SENT状态
- 双方都发送SYN包给对方
- 两个应用程序同时调用
-
双方收到对方的SYN包
- 双方都收到对方的SYN包
- 双方都进入SYN_RECEIVED状态
- 双方都发送SYN+ACK包给对方
-
双方收到对方的SYN+ACK包
- 双方都收到对方的SYN+ACK包
- 双方都发送ACK包给对方
- 双方都进入ESTABLISHED状态
同时打开时序图
协议栈支持情况
标准支持
- RFC 793:TCP协议标准文档中明确定义了同时打开
- RFC 1122:进一步规范了同时打开的行为
- 所有主流操作系统:Linux、Windows、macOS等都支持同时打开
实现特点
- 状态机支持:TCP状态机完全支持同时打开的状态转换
- 序列号处理:正确处理双方SYN包的序列号
- 连接建立:最终建立两个独立的连接(每个方向一个)
实际应用场景
1. P2P应用
- BitTorrent:对等节点之间建立连接
- Skype:点对点通信
- 游戏网络:玩家之间的直接连接
2. 分布式系统
- 集群通信:节点之间的双向通信
- 负载均衡:服务器之间的健康检查
- 数据同步:双向数据复制
3. 网络协议
- BGP:边界网关协议中的对等连接
- OSPF:开放最短路径优先协议
- IS-IS:中间系统到中间系统协议
与普通连接的区别
普通连接(三次握手)
- 客户端发送SYN → 服务器发送SYN+ACK → 客户端发送ACK
- 明确的客户端-服务器角色
- 单向连接建立
同时打开(四次握手)
- 双方同时发送SYN → 双方同时发送SYN+ACK → 双方同时发送ACK
- 对等方角色
- 双向连接建立
注意事项
- 端口冲突:双方不能使用相同的端口
- 防火墙:需要允许双向连接
- NAT穿透:可能需要特殊处理
- 连接管理:需要正确管理两个方向的连接
常见问题与解决方案
1. 大量TIME_WAIT状态
问题: 出现大量TIME_WAIT状态的连接
原因: 主动关闭连接(可能是客户端或服务器)
解决:
- 使用
SO_REUSEADDR
选项 - 调整
tcp_tw_reuse
参数 - 让另一方主动关闭连接
2. 大量CLOSE_WAIT状态
问题: 出现大量CLOSE_WAIT状态的连接
原因: 应用层没有及时关闭连接
解决:
- 检查应用代码,确保正确关闭连接
- 设置合适的超时时间
- 使用连接池管理连接
3. 连接建立失败
问题: 无法建立连接
可能原因:
- 服务器未启动或端口未开放
- 防火墙阻止连接
- 网络配置问题
TCP/IP 常见参数确认
TCP最大报文段长度MSS
ip link show 查看其中MTU的值
- MTU = 1500字节
- IP头部 = 20字节
- TCP头部 = 20字节
- MSS = 1500 - 20 - 20 = 1460字节
PPPoE环境:
- MTU = 1492字节
- IP头部 = 20字节
- TCP头部 = 20字节
- MSS = 1492 - 20 - 20 = 1452字节
TCP的报文最大生存时间MSL
-
RFC标准定义
RFC 793:MSL建议值为2分钟
RFC 1122:MSL建议值为2分钟
实际实现:大多数系统使用60秒 -
不同操作系统的MSL值
Linux 60秒 120秒
Windows 120秒 240秒
macOS 60秒 120秒
FreeBSD 60秒 120秒 -
查看TCP相关时间参数
cat /proc/sys/net/ipv4/tcp_fin_timeout
cat /proc/sys/net/ipv4/tcp_keepalive_time
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
cat /proc/sys/net/ipv4/tcp_keepalive_probes -
查看TIME_WAIT相关参数
cat /proc/sys/net/ipv4/tcp_tw_reuse
cat /proc/sys/net/ipv4/tcp_tw_recycle
cat /proc/sys/net/ipv4/tcp_max_tw_buckets
/proc/net/tcp
/proc/net/tcp是Linux内核提供的一个虚拟文件,用于显示当前系统中所有TCP连接的状态信息。这个文件是实时更新的,反映了内核中TCP连接表的当前状态。
- 字段含义表
字段 | 含义 | 说明 |
---|---|---|
sl | socket序号 | 内核中socket的序号 |
local_address | 本地地址 | 格式:IP:端口(十六进制) |
rem_address | 远程地址 | 格式:IP:端口(十六进制) |
st | 连接状态 | TCP连接状态码 |
tx_queue | 发送队列 | 发送缓冲区中的数据量 |
rx_queue | 接收队列 | 接收缓冲区中的数据量 |
tr | 定时器 | 定时器相关信息 |
tm->when | 超时时间 | 超时时间戳 |
retrnsmt | 重传次数 | 重传计数器 |
uid | 用户ID | 创建连接的用户ID |
timeout | 超时值 | 连接超时时间 |
inode | inode号 | 对应的socket inode号 |
- TCP状态码对照表
状态码 | 状态名称 | 说明 |
---|---|---|
01 | ESTABLISHED | 连接已建立 |
02 | SYN_SENT | 客户端发送SYN包 |
03 | SYN_RECV | 服务器收到SYN包 |
04 | FIN_WAIT1 | 主动关闭方发送FIN包 |
05 | FIN_WAIT2 | 主动关闭方收到ACK包 |
06 | TIME_WAIT | 主动关闭方等待2MSL |
07 | CLOSE | 连接已关闭 |
08 | CLOSE_WAIT | 被动关闭方收到FIN包 |
09 | LAST_ACK | 被动关闭方发送FIN包 |
0A | LISTEN | 服务器监听状态 |
0B | CLOSING | 双方同时关闭连接 |
TCP交互的FIN_Wait_2时间要求是多少
FIN_WAIT_2是TCP连接关闭过程中的一个状态,表示主动关闭方已经发送了FIN包并收到了对方的ACK确认,正在等待对方发送FIN包。
标准时间要求
RFC标准:没有明确规定FIN_WAIT_2的超时时间
实际实现:大多数系统使用2分钟(120秒)
Linux默认:通常为60秒
查看TCP FIN超时时间
cat /proc/sys/net/ipv4/tcp_fin_timeout
是否支持TCP 2*MSL of RTO
tcp_rto_min:RTO的最小值,防止RTO过小
tcp_rto_max:RTO的最大值,防止RTO过大
tcp_retries2:数据包重传的最大次数
tcp_timestamps:是否启用TCP时间戳选项
主要参数说明
参数 | 路径 | 说明 | 默认值 |
---|---|---|---|
tcp_rto_min | /proc/sys/net/ipv4/tcp_rto_min | RTO最小值(毫秒) | 200 |
tcp_rto_max | /proc/sys/net/ipv4/tcp_rto_max | RTO最大值(毫秒) | 120000 |
tcp_retries2 | /proc/sys/net/ipv4/tcp_retries2 | 重传次数 | 15 |
tcp_timestamps | /proc/sys/net/ipv4/tcp_timestamps | 时间戳选项 | 1 |