Skip to content

TCP协议

TCP (Transmission Control Protocol,传输控制协议) 是整个互联网的基石之一。它运行在 IP 协议之上,核心使命是:在不可靠的 IP 网络上,提供面向连接的、可靠的、基于字节流的端到端传输服务。

1. TCP 的五大核心特性

  1. 面向连接:通信双方(客户端和服务端)在交换数据之前,必须先建立一条虚拟的逻辑连接。
  2. 可靠传输:TCP 保证数据能够无损坏、无丢失、无重复、按序到达接收端。
  3. 全双工通信:连接一旦建立,双方都可以同时发送和接收数据。
  4. 面向字节流:TCP 不关心应用层发来的是什么消息(不管是一张图片还是一段文本),它只把数据看成一连串无结构的字节流。
  5. 流量与拥塞控制:根据接收方的处理能力和整个网络的拥堵情况,动态调整发送速度。

2. TCP 报文段(Segment)首部解剖

Logo

TCP 首部默认 20 字节,里面藏着 TCP 实现可靠性的所有秘密:

  • 源端口 / 目的端口 (各 16 位):用于定位发送方和接收方的具体应用程序(进程)。
  • 序号 (Sequence Number, SEQ, 32 位):给发送的每一个字节打上编号,解决网络包乱序问题,保证接收端能按序重组。
  • 确认号 (Acknowledgment Number, ACK, 32 位):期望收到对方下一个报文段的第一个字节的序号。解决网络包丢失问题。
  • 6 个核心标志位 (Flags)
    • SYN (Synchronize):用于发起连接。
    • FIN (Finish):用于释放连接。
    • ACK:确认应答标志,为 1 时确认号才有效。
    • RST (Reset):强制重置连接(通常在遇到严重错误时使用)。
    • PSH (Push):催促接收方尽快将数据交付给应用层。
    • URG (Urgent):紧急指针有效。

Logo

  • 窗口大小 (Window Size, 16 位):用于流量控制,告诉对方“我还能接收多少字节的数据”。

3. 连接管理:三次握手与四次挥手

这是 TCP 面试必考的绝对核心。

3.1 三次握手 (建立连接)

目的:同步双方的初始序列号 (ISN),并确认双方的收发能力正常。

  • 第一次握手 (C -> S):客户端发送 SYN=1, seq=x。(进入 SYN_SENT 状态)
    • “你好,我想建立连接,我的初始序列号是 x。”
  • 第二次握手 (S -> C):服务端收到后,回复 SYN=1, ACK=1, seq=y, ack=x+1。(进入 SYN_RCVD 状态)
    • “收到请求,我同意连接。我的初始序列号是 y,期待你发给我的下一个包是 x+1。”
  • 第三次握手 (C -> S):客户端收到后,回复 ACK=1, seq=x+1, ack=y+1。(双方进入 ESTABLISHED 状态)
    • “收到你的确认,连接建立成功,准备发数据!”

Logo

3.2 四次挥手 (断开连接)

目的:确保双方的数据都已经完整发送和接收完毕。

  • 第一次挥手 (C -> S):客户端数据发完,发送 FIN=1, seq=x。(进入 FIN_WAIT_1 状态)
    • “我的数据发完了,请求关闭连接。”
  • 第二次挥手 (S -> C):服务端收到后,回复 ACK=1, seq=v, ack=x+1。(服务端进入 CLOSE_WAIT,客户端进入 FIN_WAIT_2
    • “收到你的关闭请求。但我可能还有数据没发完,你等我一下。”(此时处于半关闭状态)
  • 第三次挥手 (S -> C):服务端数据也发完了,发送 FIN=1, ACK=1, seq=y, ack=v+1。(进入 LAST_ACK 状态)
    • “我的数据也全发完了,可以真正关闭了。”
  • 第四次挥手 (C -> S):客户端收到后,回复 ACK=1, seq=v+1, ack=y+1。(客户端进入 TIME_WAIT 等待 2MSL,服务端收到后进入 CLOSED
    • “好的,再见。”

Logo

4. TCP 进阶:如何保证可靠性与高效性?

4.1 滑动窗口与流量控制 (Flow Control)

  • 痛点:发送方发得太快,接收方缓存满了处理不过来,导致丢包。
  • 机制:接收方在每次回复 ACK 时,通过首部的 Window Size 告诉发送方自己剩余的缓存空间。发送方会根据这个窗口大小动态调整发送量。如果窗口为 0,发送方停止发送,并开启定时器发送“窗口探测包”。

4.2 拥塞控制四大算法 (Congestion Control)

流量控制是管“点对点”的,拥塞控制是管“整个网络”的。

  • 慢启动 (Slow Start):连接刚建立时,拥塞窗口 (cwnd) 从 1 开始,每收到一个 ACK 就翻倍(指数级增长),快速探明网络上限。
  • 拥塞避免 (Congestion Avoidance):当窗口达到阈值 (ssthresh) 时,变为线性增长(每次加 1),缓慢试探网络拥堵的临界点。
  • 快速重传 (Fast Retransmit):如果接收方收到乱序包,会连续发送 3 个对上一个按序包的重复 ACK。发送方一旦收到 3 个重复 ACK,不等超时,立刻重传丢失的包。
  • 快速恢复 (Fast Recovery):触发快速重传后,TCP 认为网络只是轻微丢包,不会回到慢启动(降为 1),而是将阈值减半,进入拥塞避免阶段。

5. 终极常见问题 (FAQ) 与生产实战排查

5.1 TCP 和 UDP 的核心区别是什么?

维度TCPUDP
连接性面向连接(需握手)无连接(直接发)
可靠性极高(保证不丢包、不乱序)不可靠(尽力而为,可能丢包)
速度较慢(机制复杂,开销大)极快(机制简单,无延迟)
适用场景网页浏览、文件下载、邮件视频会议、直播、在线游戏、DNS
优点稳定、重传机制、拥塞控制机制、断开连接安全、快速、漏洞少
缺点速度慢、效率低、占用资源、容易被攻击(三次握手->DDOS攻击)不可靠、不稳定、容易丢包

5.2 为什么握手是三次,挥手却是四次?

  • 握手三次的原因:为了防止网络中滞留的“历史失效连接请求”突然到达服务端,导致服务端单方面分配资源(两次握手无法防范此问题)。
  • 挥手四次的原因:TCP 是全双工的。客户端发 FIN 只代表客户端不发数据了,但服务端可能还在发数据。所以服务端要先回 ACK,等自己的数据全发完,再发 FIN。中间这两步不能合并。

5.3 为什么客户端最后要等待 2MSL (TIME_WAIT) 才能彻底关闭?

  • 保证最后的 ACK 能送达:如果第四次挥手的 ACK 丢了,服务端会重发 FIN。客户端在 2MSL 内可以再次回复 ACK,确保服务端正常关闭。
  • 清理网络中的残留报文:让本次连接产生的所有报文在网络中消亡,防止它们去干扰下一个分配了相同端口号的新连接。

5.4 生产环境告警:服务器出现海量 TIME_WAIT 状态,怎么回事?

  • 原因TIME_WAIT 只出现在主动断开连接的一方。如果服务器承载大量 HTTP 短连接,且由服务器主动关闭连接,高并发下就会耗尽本地端口(65535 个),导致无法接收新请求。
  • 解决:开启长连接(HTTP Keep-Alive);在 Linux 内核层面调整参数开启 tcp_tw_reuse(允许复用 TIME_WAIT 端口)。

5.5 生产环境告警:服务器出现海量 CLOSE_WAIT 状态,怎么回事?

  • 原因CLOSE_WAIT 只出现在被动关闭的一方。这 100% 是业务代码的 Bug
  • 解析:服务端收到了客户端的 FIN,内核自动回了 ACK,进入 CLOSE_WAIT。但此时,服务端的业务代码没有调用 close() 函数来关闭这个 Socket(可能是死锁、异常未捕获等)。导致连接永远卡在这里,最终耗尽文件描述符 (fd) 导致服务宕机。

5.6 什么是 TCP 粘包/拆包?如何解决?

  • 现象:TCP 是无边界的字节流。应用层发了两个小包,TCP 可能合并成一个大包发(粘包);发了一个大包,TCP 可能拆成几个小包发(拆包)。接收端不知道数据的边界在哪。
  • 解决(应用层负责)
    • 固定长度消息。
    • 特殊分隔符(如 HTTP 头的 \r\n)。
    • 消息头+消息体(最常用):在头部用几个字节表明后续消息体的长度,接收端按长度读取。

5.7 如果直接拔掉网线,TCP 连接会立刻断开吗?

  • 不会立刻断开。拔网线是物理层行为,操作系统 TCP 层无法瞬间感知。
  • 如果双方正在发数据,发送方会触发超时重传,重传十几次失败后才会断开。
  • 如果双方空闲,连接会一直保持,直到 TCP Keep-Alive 机制(通常 2 小时)触发探测包,发现不通后才会断开。