跳到主要内容
约 11 分钟6 张卡

TCP 连接

TCP 通过三次握手同步连接状态与初始序列号。全双工连接的两个方向分别关闭后,连接才会释放。本文负责握手、关闭、Linux 监听队列和 TIME_WAIT 处理。

1. 面试核心​

先记住这几条,再读正文

2. TCP 连接状态​

TCP 向应用提供可靠、有序的字节流。通信双方需要维护序列号、确认号、接收窗口和连接状态。有了这些信息,后续数据才能被确认、重传并按顺序交付。

建立连接时需要完成两件事:

  1. 确认客户端到服务端、服务端到客户端的通信路径可用。
  2. 双方交换初始序列号。这能避免新连接把网络中迟到的旧报文当成当前数据。

连接是全双工的。两个方向可以独立发送数据,也可以独立结束发送。这一点决定了关闭过程需要分别处理两个方向。

3. 三次握手与初始序列号​

假设客户端初始序列号为 x,服务端初始序列号为 y:

  1. 客户端发送 SYN,序列号为 x,进入 SYN-SENT。
  2. 服务端回复 SYN + ACK,序列号为 y,确认号为 x + 1,进入 SYN-RECEIVED。
  3. 客户端回复 ACK,确认号为 y + 1,客户端进入 ESTABLISHED;服务端收到有效 ACK 后才进入 ESTABLISHED。

SYN 会占用一个序列号,因此确认号在对方序列号基础上加一。不带数据的 ACK 不占序列号。第三个报文可以同时携带应用数据。服务端收到该 ACK 后,才能确认自己的 SYN 和初始序列号已被接收。

4. 第三次握手的作用​

如果握手只有客户端的 SYN 和服务端的 SYN + ACK,双方确认会不完整。客户端能确认服务端收到了自己的请求。服务端却不知道自己的回复是否到达客户端。服务端也就无法判断对方是否接受了本次连接参数。

第三次 ACK 完成服务端方向的确认。它还帮助服务端区分有效的新连接与迟到的旧 SYN。这样可以避免历史请求让服务端误入已建立状态。服务端也就不会为该请求分配完整连接资源。

握手建立当前连接状态。后续传输继续依赖序列号、确认、超时重传和拥塞控制。

5. 四次挥手与双向关闭​

以客户端主动关闭为例:

  1. 客户端发送 FIN,表示客户端已经没有更多数据发送,进入 FIN-WAIT-1。
  2. 服务端确认该 FIN,进入 CLOSE-WAIT;客户端收到 ACK 后进入 FIN-WAIT-2。此时服务端仍可以继续发送剩余数据。
  3. 服务端应用完成发送并关闭连接后,服务端发送自己的 FIN,进入 LAST-ACK。
  4. 客户端确认服务端的 FIN,进入 TIME-WAIT;服务端收到最后的 ACK 后关闭连接。

第二步的 ACK 与第三步的 FIN 可以在同一个报文中发送。因此,抓包时不一定总能看到四个独立报文。“四次挥手”描述双向关闭的逻辑过程。两个方向各自发送 FIN,并获得确认。

6. TIME_WAIT 与 CLOSE_WAIT​

常规单方主动关闭时,主动关闭者需等待自己的 FIN 被确认。它还需收到对端 FIN 并回复 ACK。随后,它进入 TIME-WAIT,等待 2MSL。MSL 是报文最大生存时间,不是一次往返的 RTT;具体计时值与实现有关。最后 ACK 丢失时,对方会重传 FIN。等待期间,主动关闭者可以再次回复。这段时间也让旧连接的迟到报文从网络中消失。这样就能避免影响随后复用相同四元组的连接。

被动关闭的一方收到 FIN 后进入 CLOSE-WAIT。这个状态表示对端已经停止发送,本地应用还没有关闭自己的方向。少量、短暂的 CLOSE-WAIT 属于正常过渡;持续堆积说明应用关闭路径尚未完成。此时应检查 socket 释放和相关线程阻塞。

双方也可以同时发送 FIN。如果各自在自身 FIN 被确认前收到对端 FIN,就会进入 CLOSING。随后,双方进入 TIME-WAIT。客户端与服务端都能承担主动关闭角色。

TIME_WAIT 数量随短连接关闭速率和等待时长增长。处理时应看端口与内存等实际资源压力。

6.1 TIME_WAIT 的资源压力与连接复用​

TIME_WAIT 多时,先减少连接创建与关闭频率。HTTP keep-alive、HTTP/2 多路复用和数据库连接池都能复用连接。多个请求因此可以共享连接。用 ss -tan state time-wait 确认主动关闭端和目标分布。再检查临时端口耗尽、内存占用与连接失败。仅数量高且没有资源压力时,保留正常等待即可。

Linux 的 net.ipv4.tcp_tw_reuse 可为新连接复用 TIME_WAIT socket。复用需满足协议安全条件,主要适用于主动发起连接的一端。连接还需协商 TCP timestamps。内核会检查时间戳和等待时间。复用效果需在目标内核与实际网络路径下验证。

tcp_tw_recycle 的旧实现会快速回收 TIME_WAIT。它假设同一对端地址的时间戳单调递增。这会与 NAT 后的多个主机及时间戳偏移冲突。处理 TIME_WAIT 应考虑连接复用和容量调整。也可以根据实际情况评估 tcp_tw_reuse。tcp_fin_timeout 管理孤儿 FIN_WAIT_2 等待。它与 TIME_WAIT 计时分属不同机制。

7. 握手丢包与半连接​

丢失位置典型状态与恢复排查关注点
初始 SYN客户端仍在 SYN-SENT,等待重传或超时网络可达性、监听端口、防火墙
SYN+ACK客户端还未收到应答,双方按各自定时器重传 SYN 或 SYN+ACK返回路径、半连接积压
第三次 ACK客户端已进入 ESTABLISHED,服务端仍在 SYN-RECEIVED;服务端重传 SYN+ACK,客户端再次确认分别观察两端状态与服务端队列

纯 ACK 没有独立的重传定时器。对方重传 SYN+ACK 后,客户端会再次确认。后续携带有效确认的数据也能恢复握手。重试次数、退避和最终超时取决于协议实现及配置。面试时应结合具体系统说明这些值。

SYN flood 会让服务端不断处理难以完成的连接请求。SYN cookies、限制速率和合理配置队列可降低状态分配压力;带宽耗尽还需要上游流量治理。半连接先等待握手完成,之后才等待应用 accept。这是两个阶段,排障时要结合操作系统队列和指标。

7.1 SYN 队列与 accept 队列​

Linux 监听 socket 有两个阶段的排队。SYN 半连接队列保存尚未完成握手的请求。accept 全连接队列保存已完成握手的连接。这些连接等待应用通过 accept() 取走。

队列等待条件相关配置
SYN 半连接队列服务端已发 SYN+ACK,等待有效 ACKnet.ipv4.tcp_max_syn_backlog;SYN cookies 启用并触发时改变状态保存方式
accept 全连接队列握手完成,等待应用 acceptlisten(fd, backlog),上限受 net.core.somaxconn 截断

Linux 自 2.2 起,listen() 的 backlog 指完成握手后的等待队列长度。例如应用传 backlog=1024,而 somaxconn=4096,请求容量就是 1024。应用传 8192 时受 4096 上限约束。它管理的是待接收连接。已经被 accept 取走的活跃连接,由应用和系统资源限制。

SYN 队列溢出等情况下,SYN cookies 将握手信息编码进 SYN+ACK。这能降低保存半连接状态的压力。accept 队列消费缓慢仍需改善应用接收速度。诊断时先用 ss -lnt 查看监听队列。排查时还要结合 TcpExtListenOverflows、TcpExtListenDrops 等指标。通过队列和指标,可以分别判断握手积压和应用处理不足。

8. 半关闭、复位与业务完成​

shutdown(SHUT_WR) 可以结束本端发送方向,仍保留接收方向;对端读完此前数据后读取到 EOF,但仍可返回结果。FIN 表示有序结束这一方向,RST 则用于复位连接。接收应用按连接错误处理 RST 终止。

TCP ACK 确认协议栈收到对应字节,FIN 标记发送方向结束;业务执行与持久化结果由应用层响应确认。需要端到端成功语义时,协议必须定义业务响应。它还需定义超时、请求标识和重试规则。

9. 常见问题​

9.1 握手与挥手的报文数量​

建立连接时,服务端可以合并 ACK 和自己的 SYN。这里的 ACK 用于确认客户端的 SYN。因此,三个报文能够完成双方初始序列号确认。关闭时,收到 FIN 的一方先确认该方向结束。剩余数据发完后,它再发送自己的 FIN。分开发送时,关闭过程表现为四个报文。

9.2 FIN 与应用处理进度​

10 · 面试题卡片​

本页题目与参考回答

10.1 为什么 TCP 建立连接需要三次握手,TIME_WAIT 有什么作用​

考察重点

  • 理解双向可达、初始序列号与连接状态。
  • 理解全双工关闭、半关闭和 ACK/FIN 合并。
  • 理解TIME_WAIT、CLOSE_WAIT 与线上连接问题。

相关内容:三次握手与初始序列号、第三次握手的作用、TIME_WAIT 与 CLOSE_WAIT。

参考回答

三次握手让双方确认初始序列号和连接状态;TIME_WAIT 则用于处理关闭时的 FIN 重传,并等待旧报文消失。

  • 客户端发 SYN,服务端回 SYN+ACK,客户端再回 ACK。第三次报文让服务端确认自己的 SYN 已被收到,也降低旧 SYN 误建连接的风险。
  • TCP 的两个方向分别用 FIN 关闭。单方主动关闭时,主动关闭者确认对端 FIN 后进入 TIME_WAIT,等待 2MSL;这时仍能响应对端重传的 FIN。

CLOSE_WAIT 表示已收到对端 FIN,但应用尚未关闭本地发送方向。它持续堆积时应检查 socket 关闭路径和阻塞线程。

10.2 第三次握手的 ACK 丢失,客户端和服务端分别处于什么状态?​

考察重点

  • 理解两端状态变化时机。
  • 理解SYN+ACK 重传与纯 ACK。

相关内容:三次握手与初始序列号、握手丢包与半连接。

参考回答

客户端已进入 ESTABLISHED,服务端仍停在 SYN-RECEIVED。

服务端未收到确认会重传 SYN+ACK,客户端再回复 ACK;后续携带有效 ACK 的数据也可完成确认。对方重传报文等事件会触发再次发送纯 ACK。服务端应用还需要 accept 才能取得连接。

10.3 收到 FIN 后还能发送数据吗,RST 又有什么不同?​

考察重点

  • 理解全双工与半关闭。
  • 理解协议确认和业务完成。

相关内容:四次挥手与双向关闭、半关闭、复位与业务完成。

参考回答

可以。

FIN 结束对端发送方向。本端在应用尚未关闭时仍可以发送剩余数据,之后才发自己的 FIN。RST 复位连接,属于异常终止语义。业务提交结果依赖应用协议响应与幂等核对。

10.4 线上 TIME_WAIT 或 CLOSE_WAIT 大量堆积,各应该先查什么?​

考察重点

  • 理解主动关闭与本地关闭责任。
  • 理解连接复用、资源压力和调用栈。

相关内容:TIME_WAIT 与 CLOSE_WAIT。

参考回答

TIME_WAIT 多时,先看谁主动关闭、是否有大量短连接。

再检查四元组与端口资源是否真正受限。据此考虑连接复用和容量。CLOSE_WAIT 持续存在说明本地已收到对端 FIN。但应用尚未完成关闭,应查关闭路径、异常分支和阻塞线程。两种状态分别对应关闭后的等待和应用尚未关闭的连接。

10.5 SYN 半连接队列、accept 全连接队列与 backlog 有什么关系?​

考察重点:握手阶段、应用接收与 Linux 队列容量。

相关内容:SYN 队列与 accept 队列。

参考回答

SYN 队列等待完成握手,accept 队列等待应用取走已建立连接;Linux 的 listen(backlog) 管理后者,并受 somaxconn 限制。

backlog=8192、somaxconn=4096 时被截到 4096;半连接容量还涉及 tcp_max_syn_backlog 与 SYN cookies。处理 accept 积压需要提升应用接收速度,SYN cookies 主要降低半连接状态压力。

10.6 TIME_WAIT 过多怎样处理,tcp_tw_reuse 和 tcp_tw_recycle 有什么区别?​

考察重点:连接复用、主动连接端口压力与时间戳安全检查。

相关内容:TIME_WAIT 的资源压力与连接复用。

参考回答

优先通过 keep-alive、HTTP/2 或连接池减少短连接。

再确认主动关闭端的端口和内存压力。tcp_tw_reuse 在协议安全条件下复用 TIME_WAIT socket。连接需协商 TCP timestamps,并通过内核等待检查。tcp_tw_recycle 的旧机制依赖对端时间戳单调递增。NAT 后多个主机会破坏这个假设。

11. 参考资料​