TCP 连接管理经典问题

共 20 题
📑 题目列表 20 题
#
★★★

1. TCP 三次握手的完整过程与每一步的语义,为什么两次不行、四次没必要,半连接队列与 SYN Flood 的关系如何?

请描述 TCP 三次握手的完整过程及每一步的语义,解释为什么需要三次握手(两次不行、四次没必要),并说明半连接队列与 SYN Flood 攻击的关系?

  • 三次握手各报文段(SYN、SYN+ACK、ACK)的语义与作用
  • 两次握手无法保证双向可达性、四次握手为多余状态
  • 半连接队列(syn queue)与全连接队列(accept queue)的区别

三次握手过程为:客户端发送 SYN(seq=x,并携带初始序号 ISN),进入 SYN_SENT 状态;服务端收到后回复 SYN+ACK(seq=y, ack=x+1),同时为连接分配半连接队列中的条目,进入 SYN_RECV 状态;客户端收到后回复 ACK(seq=x+1, ack=y+1),服务端将连接从半连接队列移入全连接队列,连接建立,双方进入 ESTABLISHED。第一次握手确认客户端能发、服务端能收;第二次握手确认服务端能发、客户端能收,同时服务端也确认了客户端能发;第三次 ACK 让服务端确认客户端已收到自己的 SYN+ACK。两次握手存在缺陷:服务端无法确认客户端收到了自己的 SYN+ACK,若丢包将导致服务端误以为连接已建立而浪费资源,且无法正确处理旧连接延迟到达的 SYN(序列号问题)。四次握手则冗余,因为第三次 ACK 已能同时报到达和确认,无需额外一次。半连接队列保存已收到 SYN 但尚未完成握手的连接;SYN Flood 正是通过发送大量伪造源 IP 的 SYN 而不回复 ACK,使半连接队列被占满,从而拒绝正常连接。

三次握手本质是双方确认"既能发也能收"所需的最小报文数,核心是 TCP 的可靠性与双向全双工特性。TCP 序号(ISN)的随机化与半连接队列配合,防止了旧包与新连接混淆。理解半连接队列与 SYN Flood 的关系,是排查"服务端连接数异常但进程正常"类故障的关键。

#
★★★

2. TCP 四次挥手过程与状态机,为什么挥手需要四次,CLOSE_WAIT 大量堆积说明什么问题?

请描述 TCP 四次挥手的过程与各状态机变化,解释为什么挥手需要四次而不是三次,并分析 CLOSE_WAIT 大量堆积所反映的问题?

  • 四次挥手的报文与状态(FIN_WAIT_1/2、CLOSE_WAIT、LAST_ACK、TIME_WAIT)
  • 半关闭语义与挥手四次的原因
  • CLOSE_WAIT 堆积的根因与排查思路

四次挥手过程:主动关闭方发送 FIN 进入 FIN_WAIT_1;被动方收到 FIN 后回复 ACK 并进入 CLOSE_WAIT,主动方收到 ACK 进入 FIN_WAIT_2;被动方处理完数据后发送 FIN 进入 LAST_ACK;主动方收到 FIN 后回复 ACK 进入 TIME_WAIT,被动方收到 ACK 进入 CLOSED。挥手需要四次是因为 TCP 是全双工,两个方向的数据通道需要各自独立关闭:主动方发送 FIN 只关闭了自己的发送方向,被动方仍需把剩余数据发送完后才能关闭自己的发送方向,因此"FIN + ACK"两个方向共需两轮交互。CLOSE_WAIT 大量堆积说明服务端收到了对端的 FIN 并回复了 ACK,但服务端自己的应用层没有调用 close() 关闭 socket,导致服务端迟迟不发 FIN,连接停留在 CLOSE_WAIT。常见原因包括:应用未处理对端断连导致资源泄漏、线程池/连接池未正确释放连接、代码未在 finally 中关闭连接等。

挥手四次与握手三次的本质区别在于双向独立关闭所需的"半关闭"语义。CLOSE_WAIT 是服务端常见的故障指标,它不像 TIME_WAIT 是正常现象,而是应用层未能及时关闭连接的程序缺陷,通常伴随文件描述符耗尽。

#
★★★

3. TCP 与 UDP 的核心区别及典型选型场景(可靠性、有序性、拥塞控制、报文边界)?

请对比 TCP 与 UDP 在可靠性、有序性、拥塞控制、报文边界等方面的核心区别,并说明各自的典型选型场景?

  • 面向连接/无连接、可靠/不可靠、有序/无序
  • 拥塞控制、流量控制、流量传输方式的差异
  • 报文边界(字节流 vs 数据报)的本质区别

TCP 是面向连接的、可靠的、有序的字节流协议,提供确认、重传、滑动窗口流量控制、慢启动与拥塞避免等机制,保证数据不丢失、不错序、不重复;但开销大、存在队头阻塞,对实时性要求不高的场景更合适。UDP 是无连接的、尽力而为(best-effort)的数据报协议,不提供确认与重传,也不保证有序到达,开销小、延迟低,适合对时延敏感、可容忍少量丢包的应用。在报文边界上,TCP 是字节流,没有边界,应用需自行处理粘包/拆包;UDP 每个数据报保留边界,一次 send 对应一次 recv。典型选型:TCP 用于 HTTP/HTTPS、SSH、FTP、数据库连接等需要可靠传输的场景;UDP 用于 DNS 查询、音视频实时传输(RTP)、在线游戏、QUIC 底层(HTTP/3 基于 UDP)等场景。

选型的核心权衡是"可靠性"与"延迟/开销"。TCP 的可靠性以延迟和头部开销为代价,UDP 的实效性以可能的丢包错序为代价;某些应用(如音视频)宁愿丢包重渲染也不愿因重传而产生卡顿,因此选 UDP。

#
★★★

4. TIME_WAIT 存在的原因(2MSL)与大量 TIME_WAIT 的治理手段?

请解释 TIME_WAIT 状态存在的原因(2MSL 的含义),并给出大量 TIME_WAIT 时的治理手段?

  • 2MSL 的来源与含义(MSL 为报文最大生存时间)
  • TIME_WAIT 的两个作用:可靠地终止连接、让旧报文段在网络中消失
  • 大量 TIME_WAIT 的治理手段(端口复用、调整参数、连接复用)

TIME_WAIT 是主动关闭方在收到对端 FIN 并回复 ACK 后进入的状态,持续 2MSL(MSL 是报文段最大生存时间,通常约 30 秒到 2 分钟)。设置 2MSL 的原因有二:一是确保最后一次 ACK 能被对端可靠收到,若 ACK 丢失,对端会重发 FIN,处于 TIME_WAIT 的端才能重发 ACK;二是保证本连接关闭后,网络中残留的旧报文段(最迟生存 MSL、两次往返共 2MSL)彻底消失,避免与后续复用相同四元组的新连接混淆。大量 TIME_WAIT 的治理手段:开启 tcp_tw_reuse(仅允许对 TIME_WAIT 连接安全复用,主要用于客户端出向连接)、缩短/调整 tcp_fin_timeout 等参数、使用连接池复用连接减少频繁新建、调整内核参数或使用端口复用配置。需注意 tcp_tw_recycle 因存在 NAT 下的时间戳问题已被移出内核,不建议使用。

TIME_WAIT 是 TCP 可靠性设计的一部分,并非全是"问题"。当服务端主动发起大量短连接、或短连接场景下高并发时,TIME_WAIT 会占满端口/连接资源,因此需要治理;但核心治理方向是减少连接新建(连接复用)而非盲目删除 TIME_WAIT。

#
★★★

5. SYN Flood 防御,半连接队列溢出时 SYN cookies 如何工作,开启与关闭 syncookies 时的行为差异如何?

请描述 SYN Flood 攻击下半连接队列溢出的情况,解释 SYN cookies 的工作机制,并对比开启与关闭 syncookies 时的行为差异?

  • 半连接队列溢出导致 SYN 丢弃
  • SYN cookies 的无状态握手原理
  • syncookies 开启与关闭时的行为差异

SYN Flood 攻击通过发送大量伪造源 IP 且不回 ACK 的 SYN 报文,使服务端半连接队列(syn queue)被占满,后续正常 SYN 无法进入队列而被丢弃,造成合法连接被拒绝。SYN cookies 是一种无状态防御:当半连接队列溢出时,服务端不再为每个 SYN 分配队列条目,而是将对端 IP、端口、时间戳等参数经哈希计算生成一个 cookie 作为初始序列号,随 SYN+ACK 返回;客户端回复 ACK 后,服务端通过校验 ACK 中的序列号(即 cookie 回显)来验证握手合法性,而无需维护半连接状态。开启 syncookies 时,服务端在队列溢出时通过 cookie 完成校验,能够响应正常连接,但代价是放弃了一些 TCP 状态与选项(如窗口缩放、SACK 等),且计算开销增大;关闭 syncookies 时,等于放弃抵御 SYN Flood,队列一旦溢出所有新 SYN 都会被拒。syncookies 通常只在半连接队列溢出时临时启用(阈值触发),而非始终开启。

SYN cookies 的本质是把"状态"压缩到序列号中,从而在无状态的情况下完成握手校验,是一种"以 CPU 计算换内存状态"的防御策略。理解其代价(丢失部分 TCP 选项、无法支持大窗口)与启用时机(队列溢出时)是答好本题的关键。

#
★★★

6. TCP 滑动窗口与流量控制,接收窗口(rwnd)如何限制发送速率,窗口缩放(window scaling)解决了什么问题?

请解释 TCP 滑动窗口与流量控制机制,说明接收窗口(rwnd)如何限制发送速率,以及窗口缩放(window scaling)解决了什么问题?

  • 滑动窗口进行流量控制与乱序处理
  • 接收窗口 rwnd 通告与零窗口/窗口探测
  • 窗口缩放字段(window scaling)解决 16 位窗口上限的问题

TCP 的滑动窗口是发送方与接收方共同维护的一段序号范围,用于流量控制与高效的乱序容错。接收方通过 TCP 头部的窗口字段通告自己的接收窗口 rwnd——即接收缓冲区还有多少可接收的空间,发送方必须保证在途未确认数据量不超过 rwnd,从而以"接收方能力"限制发送速率,避免接收方缓冲区溢出。当 rwnd 为 0 时,发送方进入零窗口状态并定期发送窗口探测报文,等待接收方通告新的窗口。窗口缩放(window scaling)解决了 TCP 头部窗口字段仅 16 位、最大只能表示 65535 字节的问题:在握手时通过选项协商缩放因子(最大 14),使窗口字段能表示的最大值提升到约 1GB 级别(65535×2^14),从而支持高带宽长延迟链路(BDP 大)下的高效传输。若未启用缩放,在带宽延时积大的链路上窗口会成为吞吐瓶颈。

流量控制(rwnd 控制对端发送速率,防止接收方溢出)与拥塞控制(cwnd 控制网络拥塞)是两个不同维度的机制,需区分。窗口缩放是 TCP 高吞吐性能的关键选项,也解释了为何某些工具(如 tcpdump)在不同缩放因子下看到的窗口值不同。

#
★★★

7. TCP 慢启动与拥塞避免,cwnd 与 ssthresh 如何演进,慢启动门限与快速重传/快速恢复的关系如何?

请描述 TCP 慢启动与拥塞避免机制,说明 cwnd 与 ssthresh 的演进过程,以及慢启动门限与快速重传/快速恢复的关系?

  • 拥塞窗口 cwnd 与慢启动门限 ssthresh 的定义
  • 慢启动指数增长与拥塞避免线性增长的切换
  • 超时与快速重传后的不同反应(回到慢启动 vs 快速恢复)

TCP 通过拥塞窗口 cwnd 控制网络中的在途数据量(与 rwnd 取较小值作为发送窗口)。慢启动阶段 cwnd 从初始值(如 10 MSS)开始,每收到一个 ACK 大约增加一个 MSS,从而指数增长,用于快速探测可用带宽;当 cwnd 到达 ssthresh(慢启动门限)时,进入拥塞避免阶段,cwnd 每轮往返(RTT)线性增长约 1 个 MSS,增长放缓。当发生超时(超时重传)时,ssthresh 被降为当前 cwnd 的一半,cwnd 重置为初始值,回到慢启动重新探测;当触发快速重传/快速恢复(收到三个重复 ACK 判定丢包)时,ssthresh 同样降为 cwnd 的一半,但 cwnd 直接降为 ssthresh(而非回到初始值),进入快速恢复阶段,避免了"回到慢启动"带来的吞吐骤降,之后每收到一个重复 ACK 窗口适当增长,只在收到新 ACK 或超时时才退出。ssthresh 体现了"对网络拥塞的保守估计",是慢启动与拥塞避免的分界点。

慢启动(指数)、拥塞避免(线性)、快速重传、快速恢复构成 TCP 拥塞控制的经典 AIMD 框架。关键区别在丢包后的处理:超时视为严重拥塞(回到慢启动),重复 ACK 视为轻度拥塞(快速恢复),从而在可靠性与吞吐之间折中。

#
★★

8. TCP 粘包/拆包的产生原因与应用层解决方案(定长/分隔符/长度前缀)?

请解释 TCP 粘包/拆包的产生原因,并给出应用层的常见解决方案(定长、分隔符、长度前缀)?

  • TCP 字节流无边界导致粘包/拆包
  • 粘包与拆包的成因
  • 定长、分隔符、长度前缀三种方案的原理与适用

由于 TCP 是面向字节流的协议,没有报文边界,内核将应用层多次 send 的数据按接收缓冲区大小进行打包传输,应用层一次 recv 读到的数据可能包含多个报文(粘包),也可能只是一个报文的一部分(拆包)。粘包产生于多次 send 的数据被合并到一次接收;拆包产生于一次 send 的数据被拆成多次接收。应用层解决方案:其一是定长法,固定每个报文长度,不足则补齐,接收方按固定长度切分,实现简单但浪费带宽;其二是分隔符法,在报文末尾添加特殊分隔符(如 \r\n\r\n),接收方按分隔符切分,适合文本协议但需要处理转义;其三是长度前缀法(最常用),在报文前加定长字段表示报文长度,接收方先读长度再读对应字节,如 HTTP/2 的帧格式、常见编码框架(如 protobuf 流、Netty 的 LENGTH_FIELD)均采用此方案,兼顾通用性与效率。

粘包/拆包是字节流协议的本质问题,必须由应用层(或应用层协议)自己解决。长度前缀法因通用、支持二进制、避免转义复杂度而成为主流选择,是理解 HTTP/2、WebSocket 分帧等协议的基础。

#
★★

9. TCP keepalive 与应用层心跳的差异,为什么仅靠 TCP keepalive 不够?

请对比 TCP keepalive 与应用层心跳的差异,并解释为什么仅靠 TCP keepalive 不够?

  • TCP keepalive 的默认参数与触发条件
  • TCP keepalive 的局限(默认关闭/间隔长/仅探测连接)
  • 应用层心跳的优势(可携带业务数据、可判断对端应用存活)

TCP keepalive 是操作系统(内核)提供的保活机制,默认在连接空闲 2 小时(tcp_keepalive_time)后才会启动探测,每 75 秒探测一次,若多次失败才判定连接断开。它只检测"连接是否仍存在",无法感知对端应用进程是否存活、是否卡死。应用层心跳是应用在业务层定期发送的报文(如 ping/pong),间隔可自定义(如 30 秒),且能携带业务数据、可同时对端确认应用逻辑正常。仅靠 TCP keepalive 不够的原因:其一,默认间隔过长(2 小时),无法及时发现故障;其二,它只保证 TCP 栈层面的连接可用,无法判断对端应用是否响应;其三,在 NAT/代理环境下,空闲连接可能被中间设备提前回收,keepalive 即使启用也未必及时触发。因此生产环境通常用应用层心跳配合超时重连来保证连接可用性。

TCP keepalive 解决的是"传输层连接是否存活",应用层心跳解决的是"应用层是否健康",两者关注层次不同。理解默认参数与触发时机,是答好"为什么不够"的关键。

#
★★

10. TCP 拥塞控制从 Reno 到 BBR 的演进,各自适合什么网络环境?

请描述 TCP 拥塞控制从 Reno 到 BBR 的演进历程,说明各算法分别适合什么网络环境?

  • Reno、NewReno、Vegas、BIC/CUBIC、BBR 的核心思想
  • 基于丢包 vs 基于延时/带宽的探测
  • 各算法适用的网络环境

TCP 拥塞控制经历了从"基于丢包"到"基于延时/带宽"的演进。Reno 是经典 AIMD 算法,靠丢包判断拥塞,发现丢包就减半窗口,实现简单但吞吐波动大、在带宽延时积大的网络上表现不佳;NewReno 改善了多包丢失时的恢复。Vegas 引入基于 RTT 的延时探测,在拥塞尚未丢包前就调整窗口,适合低延迟、对抖动敏感的环境。BIC/CUBIC 使用三次函数形状的窗口增长,适合高带宽高速网络(常为 Linux 默认),在长时延高带宽环境下能快速恢复到丢包前水平。BBR 是 Google 提出的基于模型(model-based)的算法,不再以丢包为拥塞信号,而是通过测量带宽(BDP)与最小 RTT 来估算网络瓶颈,主动将发送速率控制在最大带宽与最小 RTT 对应的点,从而在丢包较多或缓冲区充满(bufferbloat)的网络中提升吞吐并降低延迟,适合高丢包、高带宽或在线视频等场景。总体而言,Reno 适合简单低带宽网络,CUBIC 适合现代高速网络,BBR 则在高丢包、高带宽、bufferbloat 场景更具优势。

演进主线是从"以丢包为拥塞信号"(被动响应)到"以带宽/延时为输入建模"(主动控制)。丢包式算法在缓冲区大的网络中存在 bufferbloat 问题;BBR 通过显式测量 BDP 避开了丢包信号,是其核心创新。

#
★★

11. TCP 粘包/拆包与 UDP 报文边界问题的本质区别?

请对比 TCP 粘包/拆包问题与 UDP 报文边界问题的本质区别?

  • TCP 字节流与 UDP 数据报的边界语义
  • 粘包/拆包是应用层问题,UDP 边界由内核保证
  • 两者对应用层编程的影响

两者本质区别在于传输语义不同。TCP 是面向字节流的协议,内核不保留应用层的 send 边界,将数据按缓冲区连续传输,因此应用层必须自行处理粘包(一次 recv 读到多个报文)与拆包(一次 recv 只读到半个报文),这是应用层必须解决的问题。UDP 是面向数据报的协议,内核保留每个 send 报文的边界,一次 send 对应一次 recv,不会出现"半个数据报",但 UDP 不保证报文有序到达、不丢失、不重复,且单个数据报受限于 MTU(通常需小于 64KB,实践中更小)。此外,UDP 的"边界保留"建立在以太网/IP 层不进行重组的前提下,若 IP 分片重组失败则整包丢弃。因此,TCP 应用层要解决"字节流的切分",UDP 应用层要解决"数据报的可靠与有序"(通常在应用层自行加序号、确认、重传)。

关键区别是"字节流不与报文边界对应"(TCP)与"数据报保留边界但不可靠"(UDP)。TCP 的粘包处理与 UDP 的可靠性处理,是两种协议下应用层各自的核心职责。

#
★★

12. TCP 序列号(ISN)为什么需要随机化,不随机时如何被伪造 RST 或注入攻击?

请解释 TCP 初始序列号(ISN)为什么需要随机化,并说明不随机时如何被伪造 RST 或进行数据注入攻击?

  • 初始序列号作为连接鉴权的一部分
  • 可预测 ISN 导致 RST 攻击与数据注入
  • 随机化与时间戳选项的作用

TCP 序列号兼作连接内数据的顺序标识与"鉴权信息":对端只有确认其序列号范围,才能正确地接受 ACK 和数据。若初始序列号可预测(如老系统按固定规律递增),攻击者无需见包即可猜测某个连接当前的序列号,从而伪造一个正确的 RST 报文将连接重置(RST 攻击),或伪造带正确序列号的 ACK/数据段在连接中注入数据(会话劫持)。因此现代操作系统将 ISN 随机化(如基于时间戳与随机数生成),并配合时间戳选项(RFC 7323)防止序号回绕,使攻击者无法轻易猜中序列号。此外,启用 TCP 时间戳还能抵御序号回绕(PAWS)与部分 RST 伪造。ISN 随机化是 TCP 安全的基础防线之一。

序列号是 TCP 连接合法性的关键凭证,可预测性意味着"伪造成本低"。理解 RST 攻击的本质是"猜中序号即可关闭他人连接",是理解 TCP 安全威胁的基础。

#
★★

13. TCP 半关闭(shutdown(SHUT_WR))的语义,与 close 的区别,HTTP/1.1 连接复用如何依赖它?

请解释 TCP 半关闭(shutdown(SHUT_WR))的语义,说明其与 close 的区别,以及 HTTP/1.1 连接复用如何依赖它?

  • shutdown 与 close 对 socket 生命周期的影响
  • SHUT_WR 只关闭发送方向、仍可接收
  • HTTP/1.1 连接复用与 Content-Length/分块边界的关系

TCP 半关闭指只关闭一个方向的传输。shutdown(SHUT_WR) 表示只关闭发送方向(发送 FIN),但仍可接收对端数据,适用于"告诉对端我发完了,但仍愿意接收你的数据"的场景;close 则是完全关闭 socket,既不发送也不接收,且在有引用计数时不会立即发送 FIN(需等引用计数归零)。半关闭是 TCP 四次挥手的基础:每个方向独立关闭。HTTP/1.1 连接复用(Keep-Alive)依赖工作方式为:客户端发送请求后保持连接,服务端响应完成后并不立即关闭,而是通过 Content-Length 或 chunked 编码告知响应边界,这样连接可被后续请求复用,无需为每个请求重建 TCP 连接。在响应边界明确的基础上,连接复用大大降低了连接建立开销;若服务端要主动结束连接,才会发送 FIN 完成半关闭。半关闭与连接复用共同保证了 TCP 长连接的正确关闭与复用。

shutdown 与 close 的核心区别在于"是否关闭发送方向"与"是否立即影响引用计数的 socket"。半关闭体现 TCP 全双工特性;HTTP/1.1 连接复用则需要明确的响应边界(Content-Length/chunked)才能真正复用。

#
★★

14. TIME_WAIT 的作用与端口耗尽问题?

请说明 TIME_WAIT 状态的作用,并解释其与端口耗尽问题的关系?

  • TIME_WAIT 的可靠性作用与 2MSL
  • 端口耗尽(四元组占用)的成因
  • 高并发短连接场景下的表现

TIME_WAIT 的作用是确保最后一个 ACK 可靠到达(若 ACK 丢失可重发)、并让连接方向上的旧报文段在 2MSL 内从网络中消失,防止与后续复用相同四元组的新连接混淆。端口耗尽问题产生于:一个 TCP 连接由四元组(源 IP、源端口、目的 IP、目的端口)唯一标识,客户端为每个出向连接分配一个本地端口;当服务端主动关闭大量短连接(或客户端频繁新建连接)时,TIME_WAIT 连接会占用大量本地端口,而端口数量有限(默认 65535),若在 TIME_WAIT 期间端口未被释放,新连接将因无可用端口而失败,表现为"connect: Cannot assign requested address"。尤其在高并发短连接场景(如服务端主动关闭、反向代理反复建连)下容易触发。治理手段包括:开启 tcp_tw_reuse、缩短 time_wait 时长、使用连接池复用连接减少新建、必要时调整端口范围。

TIME_WAIT 是"可靠性与资源占用"的权衡。端口耗尽源于四元组在 TIME_WAIT 期间无法重用,本质是"连接密度过高 + 连接生命周期短",因此治理重点是减少连接新建而非删除 TIME_WAIT。

#
★★

15. TCP 的保活与探测,keepalive 与连接断开检测如何实现?

请说明 TCP 的保活与探测机制,解释 keepalive 与连接断开检测的关系?

  • TCP keepalive 的触发参数与行为
  • 连接断开检测的层次(TCP 层 vs 应用层)
  • 故障场景下的表现

TCP keepalive 是由内核在连接空闲一段时间后主动发送的探测包,用于检测对端是否仍可达。默认参数:空闲 2 小时(tcp_keepalive_time)后开始探测,探测间隔 75 秒(tcp_keepalive_intvl),连续失败 9 次(tcp_keepalive_probes)后判定连接断开并通知上层。它是"连接断开检测"的一种底层手段,但局限明显:默认间隔过长、无法感知对端应用是否存活、在 NAT 环境下中间设备可能提前回收空闲连接。因此真正的连接断开检测通常由应用层心跳(定期 ping/pong)+ 超时处理完成,应用层心跳不仅能判断连接是否断开,还能判断对端应用是否健康响应,达到"更及时、更准确"的检测。两者是可配合的关系:TCP keepalive 兜底检测传输层连通性,应用层心跳负责及时、业务级的断连感知。

keepalive 关注"传输层连接是否存活",应用层心跳关注"业务层是否健康"。生产环境将两者结合:应用层心跳做及时检测,TCP keepalive 做内核级兜底。

#
★★

16. TCP 与 UDP 的选型,可靠性与延迟如何权衡?

请从可靠性与延迟的权衡角度,说明 TCP 与 UDP 的选型原则?

  • TCP 的可靠性代价与 UDP 的延迟优势
  • 应用对可靠性/延迟的敏感度
  • 折中方案(如 QUIC)

TCP 保证可靠有序,但依赖确认、重传、拥塞控制,会带来额外往返与延迟,且存在队头阻塞,在丢包/高延迟网络上延迟明显;UDP 不提供可靠保证,但延迟低、开销小、无重传与队头阻塞,适合对时效敏感、可容忍少量丢包的应用。选型原则是权衡:若应用对数据完整性、有序性要求高(如文件传输、数据库、网页、邮件),选择 TCP;若应用对实时性敏感、可容忍丢包或自带可靠机制(如音视频实时通话、在线游戏、实时监控),选择 UDP 并在应用层自行实现必要保障。折中方案是 QUIC(基于 UDP 的 HTTP/3 传输层),它在 UDP 之上实现可靠有序、拥塞控制与多路复用,既降低延迟又保留可靠性,是当前"可靠性与延迟"权衡的现代实践。

选型没有绝对优劣,本质是"应用需求"与"协议特性"的匹配。可靠性、时序、延迟、头部开销、队头阻塞是决策维度的关键;QUIC 展示了在 UDP 上叠加可靠性能力以兼顾两者的思路。

#
★★

17. TCP 快速重传与快速恢复,为什么三个重复 ACK 触发重传,快速恢复如何避免回到慢启动?

请解释 TCP 快速重传与快速恢复机制,说明为什么三个重复 ACK 触发重传,以及快速恢复如何避免回到慢启动?

  • 三个重复 ACK 作为丢包信号
  • 快速恢复的 cwnd 调整
  • 与超时重传的区别

当接收方收到乱序数据段时,会发送 ACK 通告"期望的下一个序号"(即重复 ACK);发送方收到重复 ACK 说明后续数据段可能已丢失。由于网络乱序也可能导致重复 ACK,TCP 采用"连续收到三个重复 ACK"(即第 4 个 ACK 起)才判定为丢包,触发快速重传(立即重传丢失段,无需等待超时),从而避免超时带来的长等待。快速恢复:检测到丢包后,ssthresh 降为 cwnd 的一半,cwnd 设为该新 ssthresh,进入快速恢复阶段,期间每收到一个重复 ACK,cwnd 增加 1 个 MSS(维持管道填充),当收到新数据的新 ACK 时退出快速恢复、进入拥塞避免。这样避免了回到慢启动(cwnd 重置为最小值)导致的吞吐骤降,实现了"丢包后快速恢复吞吐"。与超时重传的区别是:超时重传视为严重拥塞,cwnd 重置为初始值回慢启动;快速重传视为轻度拥塞,仅减半且进入快速恢复。

三个重复 ACK 是"保守的丢包判定",兼顾了乱序误判与及时重传。快速恢复的关键是"快速重传时不回慢启动",通过只减半并逐步恢复,减少吞吐损失。

#
★★

18. Nagle 算法与延迟 ACK 的相互作用,为什么小包场景下会出现延迟抖动,如何用 TCP_NODELAY 缓解?

请解释 Nagle 算法与延迟 ACK 的相互作用,说明为什么小包场景下会出现延迟抖动,以及如何用 TCP_NODELAY 缓解?

  • Nagle 算法(小包合并)与延迟 ACK(等待 ACK 合并)
  • 两者互相等待导致的延迟抖动
  • TCP_NODELAY 关闭 Nagle 的作用

Nagle 算法用于减少小包数量:当有未确认数据在途时,发送方将新小数据合并缓存,待收到 ACK 后再一次性发送,从而减少网络中大量小包。延迟 ACK 是接收方延迟 40ms(或到超时)才发送 ACK,以便合并多个 ACK。两者相互作用时可能出现"死锁式"延迟:发送方等待 ACK 才能发送合并数据(Nagle),接收方等待数据才发送 ACK(延迟 ACK),于是小包交互场景下每个小写事件都要等一个延迟 ACK/超时周期,造成明显的延迟抖动(如交互式应用每写一个字符都卡顿)。用 TCP_NODELAY(setsockopt 设置)关闭 Nagle 算法,使每个小数据立即发送,从而避免与延迟 ACK 的相互等待,降低交互延迟。对于要求低延迟的交互应用(如 SSH、游戏、实时协议),设置 TCP_NODELAY 是常见做法。

延迟抖动源于 Nagle 的"等 ACK"与延迟 ACK 的"等数据"相互等待。TCP_NODELAY 关闭 Nagle 以牺牲少量小包效率换取低延迟,是交互式应用的典型优化。

#
★★

19. 用 netstat/ss 诊断连接状态,SYN_SENT 堆积、SYN_RECV 溢出、CLOSE_WAIT 与 TIME_WAIT 分别指向什么问题?

请说明用 netstat/ss 诊断连接状态的方法,解释 SYN_SENT 堆积、SYN_RECV 溢出、CLOSE_WAIT 与 TIME_WAIT 分别指向什么问题?

  • netstat/ss 查看连接状态的命令
  • 各状态堆积的排障含义
  • 集群/服务故障分析

用 netstat -an 或 ss -an 可查看各连接状态,配合 ss -ant | awk 统计状态数量可快速定位问题。各状态堆积的含义:SYN_SENT 大量堆积表示客户端发出 SYN 后迟迟未收到 SYN+ACK,通常指向对端不响应、防火墙丢弃 SYN、对端服务未监听或网络往返失败;SYN_RECV 大量堆积表示服务端收到了大量 SYN 但握手未完成,常见于 SYN Flood 攻击或半连接队列接近溢出,需检查半连接队列与 syncookies;CLOSE_WAIT 大量堆积表示服务端收到对端 FIN 但应用层未调用 close() 关闭连接,是典型的应用层资源泄漏(如未释放连接);TIME_WAIT 大量堆积通常是短连接高并发或服务端主动关闭连接导致,多见于服务端主动断连的反向代理、高并发短连接场景。排障时结合状态分布与进程分析,可定位是内核、网络还是应用层问题。

连接状态是 TCP 排障的"仪表盘":SYN 类状态关注握手与网络,CLOSE_WAIT 关注应用层关闭,TIME_WAIT 关注连接复用与端口。掌握各状态含义是网络问题定位的基础。

#

20. HTTP/3 的 QUIC 如何解决队头阻塞,0-RTT 的安全性代价?

请解释 HTTP/3 的 QUIC 如何解决队头阻塞问题,并说明 0-RTT 握手的安全性代价?

  • QUIC 基于 UDP 的多路复用与流隔离
  • 解决 TCP/HTTP 层队头阻塞
  • 0-RTT 重放攻击的安全代价

队头阻塞分两层:HTTP/2 在单个 TCP 连接上多路复用多个流,但 TCP 本身是字节流、按序交付,一个包丢失会导致整个 TCP 发送窗口阻塞,所有流一起等待(TCP 层队头阻塞);TCP 慢启动与拥塞控制也会造成传输层队头阻塞。QUIC 基于 UDP 实现,将每个流(stream)独立管理,每个流有独立的序号与重传,一个流丢包只影响该流,不影响其他流,从而消除 TCP 层队头阻塞,实现真正的多路复用。QUIC 还内置 TLS 1.3 握手、连接迁移等特性。0-RTT 是 QUIC 在已有连接(携带缓存会话密钥)时,客户端可在首个数据包中直接携带应用数据,实现"零往返"建立连接,显著降低首包延迟;但其安全性代价是存在重放攻击风险:攻击者可能截获并重放 0-RTT 数据,导致服务端执行重复操作(如重复下单、重复写操作),因此 0-RTT 数据通常只用于幂等、无副作用的请求,非幂等操作仍需等待完整握手。

QUIC 以"流隔离"解决 TCP 层队头阻塞,是 HTTP/3 的核心创新。0-RTT 的代价是潜在的重复执行,需通过幂等性约束来兜底,这体现了"性能与安全"的权衡。