TCP 协议

摘要:本文系统讲解 TCP 协议的核心机制,涵盖三次握手与四次挥手的完整状态转换过程。文章从 TCP 的基本特点(面向连接、可靠传输、流量控制、拥塞控制、全双工通信)入手,详细解析了 LISTEN、SYN-SENT、ESTABLISHED 等 11 种 TCP 状态及其转换条件,并配有 Mermaid 状态转换图辅助理解。最后通过 TCP 与 UDP 的对比表,帮助读者在实际开发中根据可靠性与实时性需求做出正确的协议选择。

TCP(传输控制协议)的交互过程可以分为三个核心阶段:连接建立、数据传输和连接终止。整个过程基于面向连接、可靠传输的原则。

TCP 协议的特点

  • 面向连接:需先建立连接,再传输数据
  • 可靠传输:通过序列号、确认、重传等机制保证
  • 流量控制:滑动窗口机制
  • 拥塞控制:动态调整发送速率
  • 全双工通信:双向数据流

TCP 的状态

1. 建立连接相关状态

LISTEN(监听状态)

  • 角色:服务器端
  • 触发:调用 listen() 系统调用后
  • 功能:等待客户端的 SYN 连接请求
  • 查看命令netstat -ant | grep LISTEN
  • 问题排查:如果服务器监听端口但无法连接,检查防火墙、ACL 或应用程序

SYN-SENT(同步已发送)

  • 角色:客户端
  • 触发:客户端发送 SYN 包后
  • 等待:等待服务器的 SYN+ACK 响应
  • 典型问题
    • 服务器无响应 → 状态长时间保持
    • 服务器拒绝连接 → 收到 RST,进入 CLOSED
    • 超时:通常 75 秒后放弃

SYN-RECEIVED(同步已接收)

  • 角色:服务器端
  • 触发:服务器收到 SYN,发送 SYN+ACK 后
  • 等待:等待客户端的 ACK 确认
  • 安全问题:SYN Flood 攻击的目标状态
  • 防护手段:SYN Cookies、连接限制

ESTABLISHED(已建立连接)

  • 角色:客户端和服务器
  • 触发:完成三次握手
  • 功能:正常数据传输状态
  • 特点
    • 可以双向传输数据
    • 维持滑动窗口、拥塞控制等机制
    • 保活机制可能生效(默认 2 小时)
2. 关闭连接相关状态

FIN-WAIT-1(第一次等待结束)

  • 角色:主动关闭方
  • 触发:应用调用 close(),发送 FIN 后
  • 等待:等待对端的 ACK 或 FIN
  • 可能转换
    • 收到 ACK → FIN-WAIT-2
    • 收到 FIN → CLOSING(同时关闭)
    • 收到 FIN+ACK → TIME-WAIT

FIN-WAIT-2(第二次等待结束)

  • 角色:主动关闭方
  • 触发:收到对端对 FIN 的 ACK 后
  • 等待:等待对端的 FIN
  • 特点
    • 仍可接收数据(半关闭状态)
    • 不能发送数据
    • 超时:通常 2 分钟,可配置

CLOSE-WAIT(关闭等待)

  • 角色:被动关闭方
  • 触发:收到对端 FIN
  • 含义:本端应用还未调用 close()
  • 问题状态:如果大量 CLOSE-WAIT 连接,通常表示应用程序 bug(未及时关闭连接)
  • 解决方法:检查代码中的 socket 关闭逻辑

CLOSING(正在关闭)

  • 角色:双方同时
  • 触发:双方几乎同时发送 FIN
  • 罕见状态:表示双方同时主动关闭
  • 等待:等待对方的 ACK

LAST-ACK(最后确认)

  • 角色:被动关闭方
  • 触发:本端应用调用 close(),发送 FIN 后
  • 等待:等待对端对 FIN 的 ACK
  • 转换:收到 ACK 后直接进入 CLOSED

TIME-WAIT(时间等待)

  • 角色:主动关闭方
  • 触发:发送最后一个 ACK 后
  • 等待时间:2MSL(Maximum Segment Lifetime)
    • MSL 通常 30 秒或 60 秒,因此 TIME-WAIT 通常 1–4 分钟
  • 存在的理由(RFC 要求)
    • 可靠终止:确保最后一个 ACK 能到达
    • 避免旧连接干扰:让旧连接的包在网络中消失

TCP 状态转换图

下面是 TCP 从建立连接到断开连接的完整状态转换图(横向布局),清晰展示了三次握手和四次挥手的路径及触发条件:

四次挥手路径

三次握手路径

被动打开
listen()

收到SYN
发送SYN+ACK

主动打开
connect()

收到SYN+ACK
发送ACK

收到ACK

主动关闭
发送FIN

收到FIN
发送ACK

收到ACK

收到FIN+ACK
发送ACK

收到FIN
发送ACK

收到ACK

收到FIN
发送ACK

应用关闭
发送FIN

收到ACK

2MSL超时

初始状态

CLOSED

LISTEN

SYN-RECEIVED

SYN-SENT

ESTABLISHED

FIN-WAIT-1

CLOSE-WAIT

FIN-WAIT-2

TIME-WAIT

CLOSING

LAST-ACK

CLOSED

图例说明:

  • 三次握手路径(上半部分):从 CLOSED 出发,经过 LISTEN/SYN-SENTSYN-RECEIVEDESTABLISHED
  • 四次挥手路径(下半部分):从 ESTABLISHED 出发,经过 FIN-WAIT-1/CLOSE-WAITFIN-WAIT-2/LAST-ACKTIME-WAITCLOSED
  • 箭头上的文字标注了状态转换的触发条件(如 收到SYN发送ACK发送FIN 等)
关键状态转换详解

1. LISTEN → SYN-RCVD(被动打开后的首次握手)

  • 触发条件:服务器调用 listen() 进入 LISTEN 状态后,收到客户端发来的 SYN 报文,随即回复 SYN+ACK 并进入 SYN-RCVD。
  • 典型问题排查
    • 若服务器长期停留在 LISTEN 但未收到 SYN,检查客户端是否可达、防火墙是否拦截了目标端口(telnet <ip> <port> 测试连通性)。
    • 若 SYN-RCVD 堆积过多(ss -t state syn-recv),可能是 SYN 泛洪攻击(SYN Flood),可启用 tcp_syncookies 缓解。

2. SYN-SENT → ESTABLISHED(主动打开后的三次握手完成)

  • 触发条件:客户端调用 connect() 发送 SYN 进入 SYN-SENT,收到服务器的 SYN+ACK 后回复 ACK,双方进入 ESTABLISHED。
  • 典型问题排查
    • 客户端长时间卡在 SYN-SENT(ss -t state syn-sent),说明服务器未响应 SYN,检查服务器端口是否监听、是否有丢包或路由不可达。
    • 使用 tcpdump -i any host <server_ip> and tcp port <port> 抓包确认 SYN 是否发出及 SYN+ACK 是否返回。

3. FIN-WAIT-2 → TIME-WAIT(被动关闭方完成数据发送后)

  • 触发条件:主动关闭方收到被动关闭方的 ACK 后进入 FIN-WAIT-2,等待被动关闭方发送 FIN。当被动关闭方处理完剩余数据并调用 close() 发送 FIN 后,主动关闭方回复 ACK 并进入 TIME-WAIT。
  • 典型问题排查
    • 大量连接卡在 FIN-WAIT-2(ss -t state fin-wait-2),说明被动关闭方未发送 FIN,可能是应用层未调用 close() 或存在死循环未释放 socket。
    • 检查被动关闭方(通常是服务器)的应用程序是否正确处理了连接关闭逻辑,或设置 tcp_fin_timeout 内核参数(默认 60s)强制超时回收。

4. TIME-WAIT → CLOSED(2MSL 等待后最终关闭)

  • 触发条件:主动关闭方收到 FIN 并回复 ACK 后进入 TIME-WAIT,等待 2MSL(Maximum Segment Lifetime,通常 60 秒)以确保被动关闭方收到最后的 ACK,防止旧连接报文干扰新连接。
  • 典型问题排查
    • 高并发短连接场景下 TIME-WAIT 过多(ss -t state time-waitnetstat -ant | grep TIME_WAIT | wc -l),可能耗尽端口资源。
    • 优化方案:开启 net.ipv4.tcp_tw_reuse(允许重用 TIME-WAIT 端口作为客户端)、net.ipv4.tcp_tw_recycle(已废弃,不建议使用),或调整 net.ipv4.ip_local_port_range 扩大端口范围。

5. CLOSE-WAIT → LAST-ACK(被动关闭方等待应用关闭)

  • 触发条件:被动关闭方收到 FIN 并回复 ACK 后进入 CLOSE-WAIT,等待本端应用程序调用 close() 释放 socket,随后发送 FIN 进入 LAST-ACK。
  • 典型问题排查
    • CLOSE-WAIT 堆积(ss -t state close-wait)是常见问题,通常意味着应用程序未正确调用 close()shutdown()
    • 使用 lsof -i :<port> 查看持有 socket 的进程,检查代码中是否有未关闭的 socket 或异常未捕获导致资源泄漏。

查看命令netstat -ant | grep TIME_WAITss -t state time-waitss -t state close-wait

TCP 和 UDP 对比表

对比维度 TCP(传输控制协议) UDP(用户数据报协议) 适用场景(TCP) 适用场景(UDP)
连接性 必须先建立连接(三次握手),确认双方在线后才开始传输数据
面向连接,专属通道
无需建立连接,直接发送数据,不管对方是否在线
无连接,广播式发送
- HTTP/HTTPS(网页浏览)
- FTP(文件传输)
- SMTP/IMAP(电子邮件)
- DNS(域名解析)
- DHCP(IP 地址分配)
- SNMP(网络管理)
可靠性 每个数据包都有确认应答(ACK),丢失自动重传,保证 100% 送达
可靠传输,丢包重传、超时重传
发送后不确认,不重传丢失的数据包,不保证送达
不可靠传输,只保证"我发了",不保证"你收到"
- SSH(远程登录)
- WebSocket(实时通信)
- 数据库连接(MySQL/PostgreSQL)
- VoIP(语音通话)
- 视频直播/会议
- 在线游戏(实时操作)
传输效率 报文头部复杂(20–60 字节),需等待确认和重传,延迟较高但数据完整
头部开销大,延迟较高
报文头部极小(仅 8 字节),无确认等待,速度极快
头部开销小,延迟极低
- 文件下载/上传
- 邮件收发
- API 接口调用
- 物联网传感器数据
- NTP(时间同步)
- 广播/多播通信
拥塞控制 检测到网络拥堵时自动降低发送速率(慢启动、拥塞避免),不加剧网络负担
有动态速率调整
不感知网络状态,按固定速率发送,可能加剧网络拥堵
无拥塞控制
- 视频点播(流媒体)
- 云存储同步
- CDN 内容分发
- 实时竞技游戏
- 金融行情推送
- 日志采集传输
数据顺序 通过序列号保证数据按发送顺序到达接收方,乱序自动重组
保序传输
不保证数据包到达顺序,可能乱序到达,需应用层自行处理
不保序
- 消息队列(Kafka/RabbitMQ)
- 远程过程调用(RPC)
- 实时监控数据
- 位置服务(GPS)
- 在线音视频
流量控制 滑动窗口机制动态匹配接收方处理能力,防止接收缓冲区溢出
有流量控制
无流量控制机制,发送速度不受接收方限制
无流量控制
- 大文件传输
- 数据备份/同步
- 简单状态同步
- 心跳检测
- 探针/监控

如何选择 TCP 还是 UDP?

在实际网络编程中,选择 TCP 还是 UDP 主要取决于应用对可靠性实时性的权衡:

  • 选 TCP 的场景:当数据完整性、顺序性和可靠性是首要要求时,优先选择 TCP。例如网页浏览、文件传输、电子邮件、数据库连接等场景,丢失一个数据包可能导致页面错乱或文件损坏,因此必须保证 100% 可靠送达。

  • 选 UDP 的场景:当低延迟和实时性比可靠性更重要时,选择 UDP。例如语音通话、视频直播、在线游戏等场景,偶尔丢失一帧画面或几毫秒音频,用户几乎感知不到,但若因重传导致延迟卡顿,体验会急剧下降。

  • 混合使用:许多现代应用同时使用两种协议。例如视频会议系统用 UDP 传输音视频流以保证实时性,同时用 TCP 传输控制信令和文件共享;在线游戏中用 UDP 同步玩家位置,用 TCP 处理登录认证和聊天消息。

一句话总结:TCP 保你收得全、收得对,适合对数据完整性要求高的场景;UDP 保你收得快、延迟低,适合对实时性要求高的场景。理解这一核心差异,就能在开发中做出正确的协议选择。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐