计算机网络 之 【TCP协议】(确认应答、超时重传、流量控制、三次握手、四次挥手、滑动窗口、快重传、延迟应答、Nagle算法、捎带应答、拥塞控制)

目录

1.TCP可靠性保证机制

1.1确认应答机制

1.2超时重传机制

1.3流量控制机制

1.4连接管理机制

1.4.1三次握手

1.4.2四次挥手

1.4.3队列机制与listen参数

1.4.4TIME_WAIT状态

重启解决方案:setsockopt

2.TCP效率提升机制

2.1滑动窗口机制

(8)序号与滑动窗口的关系

2.2快重传机制

(3)快重传VS超时重传

2.3延迟应答机制

(4)开发者的影响

2.4Nagle算法

(2)Nagle算法VS延迟应答

2.5捎带应答机制

3.TCP拥塞控制机制

(3)三个"窗口"概念对比

(4)拥塞控制的两个阶段

阶段1:慢启动

阶段2:拥塞避免


机制是指为了解决一个特定问题,而设计的一套有明确步骤、规则和触发条件的做事方法

1.TCP可靠性保证机制

可靠性的核心定义可总结为:确保数据从发送端A到接收端B的传输过程中,实现不丢(完整)、不乱(按序)、不差(无差错)、不重(无重复)
TCP通过序号+确认应答 实现字节级别的可靠性,滑动窗口 支持批量发送与累积确认以提高效率,超时重传 弥补未确认部分的不可靠性,校验和 确保数据包在传输过程中未发生损坏。但理论上最后一条已发送数据永远无法被确认,因此TCP保证的是已确认数据的可靠,而不是整个会话的100%无丢包

1.1确认应答机制

  1. **原理:**发送方每发一个报文,接收方必须回复一个ACK(确认序号)
  2. 作用 :发送方通过ACK 明确知道对方收到了哪些字节
  3. 单向性:数据从A→B可靠由B回复的ACK保证;B→A可靠由A回复的ACK保证,两者独立

已确认的数据:可靠;发送但未确认的数据:暂时不可靠

1.2超时重传机制

  1. 超时重传是TCP保障可靠性的兜底机制:当发送方在动态计算的超时时间(默认500ms整数倍) 内未收到ACK,则判定数据丢失(可能原因:数据丢失、网络阻塞、ACK丢失)
  2. 超时时间与网络状况强相关 ,且存在特定重传次数限制(超过则断开连接)
  3. 接收方可能收到因重传导致的重复报文 ,依靠序号机制去重,保证数据唯一性
  4. 整个过程在传输层自动完成,应用层无感知,属于TCP内部可靠性细节

TCP通过动态测量网络RTT(往返时间)计算自适应超时时间(RTO),并默认以500ms为单位进行控制;若超时未收到ACK则触发重传,每次重传后将超时时间翻倍 (1×RTO、2×RTO、4×RTO...),避免网络拥堵;当重传累计达上限(如15次)或总超时超阈值,则判定连接异常并强制关闭。这一机制在传输效率与网络稳定性之间取得动态平衡,既避免因超时过短导致频繁误重传,又防止超时过长降低丢包恢复速度

1.3流量控制机制

(1)核心目的: 保证发送方的发送数据量不超过接收方的接收能力,避免因接收方缓冲区满而导致丢包;既提升可靠性(防止正常丢包 ),也提升效率(减少不必要的重传

(2)建立连接时的协商(三次握手):在三次握手过程中 ,双方通过SYN报文和SYN+ACK报文中的窗口字段,告知并协商彼此的接收能力 ,为后续发送数据量设定合理基础;第三次握手可以携带数据(捎带应答),因为此时客户端认为连接已建立,发送数据量大小也可预测

(3)传输过程中的动态调整:当接收方窗口变为0时,发送方会发送窗口探测报文 ,主动获取窗口是否已恢复;接收方一旦有可用缓冲区,会发送窗口更新报文通知发送方,避免死锁

(4)窗口扩大因子: TCP选项中可以包含窗口扩大因子(M)

实际窗口大小 = 声明窗口值 << M(左移,相当于乘以2的M次方);扩大因子受操作系统和硬件限制,用于支持高带宽延迟网络中的大窗口

1.4连接管理机制

TCP通信基于连接,通过三次握手 建立连接,通过四次挥手 断开连接。连接管理由双方操作系统自动完成,上层应用(如connectaccept)只负责触发和获取结果

对比项 三次握手 四次挥手
目的 建立连接,同步状态 断开连接,释放资源
主动性 客户端主动 双方均可主动
次数原因 最小可靠验证全双工 可靠保证双方无发送意愿
状态 SYN_SENTSYN_RCVDESTABLISHED FIN_WAITTIME_WAITCLOSE_WAIT
关键机制 队列管理(半连接/全连接) 半关闭、TIME_WAIT
特性 三次握手 四次挥手
保障的重点 协商起始序号,确认双方通信能力正常 优雅地关闭,防止数据丢失,支持半关闭

1.4.1三次握手

(1)握手过程

步骤 发送方 报文 状态变化
1 客户端 → 服务器 SYN 客户端进入 SYN_SENT
2 服务器 → 客户端 SYN + ACK 服务器进入 SYN_RCVD
3 客户端 → 服务器 ACK 双方进入 ESTABLISHED

(2)连接建立完成的判定

  • 客户端:发送完ACK后即认为连接建立完成
  • 服务器:收到ACK后认为连接建立完成
  • 本质:客户端在"赌"ACK能被服务器收到;若服务器未收到ACK,后续收到客户端数据时会回复RST报文

(3)相关函数

  • connect:发起建立连接请求,阻塞等待,不参与握手细节
  • accept:不参与 连接建立,只是从已完成连接队列中提取结果(阻塞等待)

(4)三次握手的原因

  • 最小可靠握手次数: 验证全双工通路通畅,将连接失败的成本转移到客户端

第1次 (客户端→服务端):服务端收到SYN → 证明 客户端→服务端 发送通 + 服务端接收通
第2次 (服务端→客户端):客户端收到SYN+ACK → 证明 服务端→客户端 发送通 + 客户端接收通 ,同时确认了第1次ACK的回复
第3次 (客户端→服务端):服务端收到ACK → 证明 客户端→服务端 发送通(再次确认) + 更关键的是:让服务端确信客户端活着的、能收、能回

  • 资源消耗对比
阶段 客户端资源 服务端资源
收到SYN后 无状态 分配TCB(传输控制块),进入SYN_RCVD
收到ACK后 进入ESTABLISHED 进入ESTABLISHED

关键 :服务端在收到第三个ACK之前 就已经分配了资源(TCB)。如果只做2次握手,服务端在收到第一个SYN后就要分配资源并进入ESTABLISHED,恶意客户端可以发送大量伪造IP的SYN,让服务端为不存在的连接消耗海量内存

  • 成本转移

    正常流程:
    SYN → 服务端分配资源(SYN_RCVD)→ 等待ACK → 若ACK不到,超时释放

    SYN洪水攻击(2次握手):
    攻击者发送海量SYN(伪造源IP)→ 服务端为每个SYN分配资源 → 永远等不到ACK → 资源耗尽

    SYN洪水攻击(3次握手,有SYN Cookie保护):
    攻击者发送海量SYN → 服务端不分配资源,只计算Cookie → 只有收到合法ACK(携带正确的Cookie)才分配资源

2次握手:服务端在收到SYN时就承担了资源成本(容易被攻击)

3次握手:服务端把验证客户端的责任推迟到收到ACK之后,客户端必须证明自己能收到服务端的SYN+ACK(即能正确回复ACK),服务端才分配完整资源

结果 :攻击者要消耗服务端资源,必须先耗费自己的资源去接收并回复SYN+ACK(即必须有一个真实的IP和网络通路)。成本从服务端转移到了攻击者

  • 两次握手:无法验证服务器→客户端的发送能力,连接失败的成本在服务端

    客户端 ---SYN---> 服务端
    客户端 <---ACK--- 服务端(不发送SYN)

两次握手无法验证服务端→客户端的发送能力 :两次握手只包含客户端发送SYN和服务端回复ACK,其中ACK仅是被动响应,无法证明服务端具备主动向客户端发送数据的能力(考验完整的本地发送路径:应用 → 协议栈 → 网卡 → 网络)。如果服务端→客户端的发送通路存在问题,客户端会误以为连接已建立,后续服务端发送的数据客户端完全收不到,双方却都认为连接正常,导致单向断连且无法及时察觉

连接失败成本在服务端(假设有两次握手):两次握手中,服务端收到SYN后立即分配资源并进入ESTABLISHED状态(随后才发送ACK)。如果客户端没有收到ACK,客户端会超时重传并最终放弃,认为连接未建立;而服务端已经白白占用了资源,并向上层应用误报连接成功,这就是"连接失败的成本完全压在服务端"

  • 一次握手:无法验证全双工通路通畅

    客户端 ---SYN---> 服务端(直接认为连接建立)

客户端无法知道服务端是否存活,服务端无法知道客户端是否收到任何东西

这根本不是握手,只是单向通知。UDP就是这样工作的,但TCP要求可靠性,所以不可接受

  • 细节补充

三次握手本质可以看作四次:服务端的 SYN 和 ACK 是合并发送的(SYN+ACK 一个包)

握手期间会协商双方的接收能力

1.4.2四次挥手

建立连接是一方主动,断开连接需要双方都要同意

断开连接通过四次挥手完成

(1)挥手过程

步骤 发送方向 说明
初始 --- 正常数据传输
1 主动方 → 被动方 主动方调用close,发送FIN
2 被动方 → 主动方 被动方收到FIN,回复ACK;应用层收到EOF
3 被动方 → 主动方 被动方调用close,发送FIN
4 主动方 → 被动方 主动方回复ACK;被动方收到后关闭

四次挥手本质是两个独立的"FIN-ACK"可靠传输闭环

分别关闭两个方向的数据流,确保双方都明确知道对方已无数据发送

(2)核心原则

  • 断开连接的本质:双方都没有数据要发送了
  • 四次挥手可靠保证:双方都没有发送消息的意愿
  • 允许半关闭:一方发FIN后仍可接收数据,发送管理报文

(3)挥手次数的变化

  • 四次挥手可能变成三次(当被动方的FIN和ACK合并发送时)

1.4.3队列机制与listen参数

(1)连接队列

队列 存放内容 管理方式
全连接队列 已完成三次握手的连接 listen的第二个参数 + 1 = 队列最大长度
半连接队列 处于SYN_RCVD状态的连接 由操作系统决定长度,超时自动清除

accept的本质是从 TCP 全连接队列中取出第一个节点

并返回一个新的 socket 文件描述符供应用层使用
全连接队列满后,Linux 默认 直接丢弃客户端发来的第三次握手 ACK,导致连接无法从半连接队列移入全连接队列,仍停留在半连接队列的 SYN_RCVD 状态;服务端会超时重传 SYN+ACK,若客户端无响应,该半连接约 60-120 秒后超时释放(自动消失)

(2)listen的第二个参数backlog

  • 底层全连接队列最大长度 = min(backlog, somaxconn(系统级上限数量)) + 1
  • **为0:**为0可能导致服务器空闲,效率低(实际长度为 1)
  • **太大:**浪费内核内存,应根据硬件和场景调整

(3)SYN洪水攻击

  • 攻击者大量发送SYN,填满半连接队列
  • 导致正常用户的半连接请求被丢弃,无法正常通信

1.4.4TIME_WAIT状态

  • 产生条件及特点

主动断开连接的一方在四次挥手完成后进入TIME_WAIT状态

等待2倍MSL(最大报文存活时间)后自动释放

(源IP, 源端口, 目标IP, 目标端口) 在 TIME_WAIT 期间(通常为 2MSL,约 60 秒)保持唯一

属性
全称 Maximum Segment Lifetime
含义 TCP报文段在网络中存在的最长时间
本质 工程假设值,并非物理上限
作用 作为 TIME_WAIT 状态时长的计算基准
  • 存在原因

让历史报文消散: 防止后续新连接的报文与旧连接的残留报文冲突(起始序号随机可辅助规避)
**容错性:**如果最后一个ACK丢了,被动方会重发FIN,主动方在TIME_WAIT期间可以重发ACK

  • TIME_WAIT 会导致"无法立即重启"

例如:服务端程序崩溃或正常重启。它尝试再次 bind() 到 (0.0.0.0:8080)时,内核会发现上一个连接的四元组 (客户端IP, 客户端端口, 服务端IP, 8080) 仍然处于 TIME_WAIT 状态。结果bind() 失败,报错 Address already in use。服务无法启动
这是为了防止旧连接延迟的包误入新连接,造成数据错乱

  • 客户端"不易处于 TIME_WAIT"

主动关闭方:谁先发起 FIN,谁进入 TIME_WAIT

服务端:通常主动关闭(如停止服务、重启),因此成为 TIME_WAIT 的受害者

客户端:通常由服务端先关闭连接,客户端被动关闭,进入 CLOSE_WAIT 后快速释放。即使客户端主动关闭,它使用的是随机端口,下次启动时源端口变了,四元组自然不同

重启解决方案:setsockopt
复制代码
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, 
               const void *optval, socklen_t optlen);
参数 说明
sockfd socket 文件描述符
level 选项所在的协议层
optname 选项名称
optval 指向选项值的指针
optlen 选项值的长度
返回值 成功返回 0,失败返回 -1

(1)常用level级别

level 说明
SOL_SOCKET Socket 通用层(与协议无关)
IPPROTO_TCP TCP 协议层
IPPROTO_IP IP 协议层
IPPROTO_IPV6 IPv6 协议层

(2)SOL_SOCKET 层常用选项

1. SO_REUSEADDR ------ 重用地址

复制代码
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  • 作用 :允许重用处于 TIME_WAIT 状态的地址和端口
  • 典型场景 :服务器崩溃/重启后,立即绑定同一个端口;解决 Address already in use 错误
场景 SO_REUSEADDR SO_REUSEADDR
TIME_WAIT 存在时 bind() 失败 成功
安全性 防止旧包混入新连接 需自行确保无旧包干扰

2. SO_REUSEPORT ------ 重用端口

复制代码
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

作用 :允许多个 socket 绑定到完全相同的 IP 和端口。

典型场景:多进程/多线程服务器,每个进程独立监听同一端口;内核级负载均衡,新连接分发到不同进程

特性 SO_REUSEADDR SO_REUSEPORT
忽略 TIME_WAIT
多进程绑定同端口
内核负载均衡
兼容性 所有平台 Linux 3.9+

3.快速参考表

选项 级别 作用 典型值
SO_REUSEADDR SOL_SOCKET 快速重用 TIME_WAIT 端口 1
SO_REUSEPORT SOL_SOCKET 多进程绑定同端口 1
SO_KEEPALIVE SOL_SOCKET TCP 保活探测 1
SO_RCVBUF SOL_SOCKET 接收缓冲区大小 262144
SO_SNDBUF SOL_SOCKET 发送缓冲区大小 262144
SO_LINGER SOL_SOCKET 控制 close() 行为 {1,0}
TCP_NODELAY IPPROTO_TCP 禁用 Nagle 算法 1
TCP_QUICKACK IPPROTO_TCP 立即发送 ACK 1

1.5TCP拥塞控制机制

(1)为什么需要拥塞控制

  • 之前讨论的机制都假设**问题出在对端主机:**对方处理慢(接收窗口小);对方ACK回得慢
  • 但网络是共享的公共资源 。真正的丢包往往不是对方主机问题,而是网络路径上的某个路由器缓存满了 (网络拥塞)。如果发送方在拥塞时仍然超时重传 ,只会往已经满的管道里再塞一份数据,让拥塞更严重

核心思想 :TCP不仅是两个主机的事,更是网络公共资源的使用者。检测到拥塞时,必须自我限速
TCP将网络视为公共资源,通过拥塞控制机制(如慢启动、拥塞避免、快速重传/恢复)让各主机自主感知拥塞(如检测丢包、RTT增大)并主动限速 ,从而在不依赖中央协调的情况下实现分布式共识:集体牺牲发送速率以消散网络中的历史积压数据,再逐步恢复,以此保障整个网络的稳定与公平。但这种协作是**"自适应"**的,不同主机可能因探测时机、算法实现或RTT差异而对拥塞做出不同强度的反应

(2)拥塞控制的关键:引入一个新窗口

之前的发送窗口只考虑对方接收能力:发送窗口 = 接收方通告窗口 (rwnd),现在引入拥塞窗口 (cwnd, congestion window) ,反映网络路径的承载能力

最终发送窗口大小 = min(rwnd, 拥塞窗口 cwnd)

  • rwnd:对方能收多少(接收端能力)
  • cwnd:网络能撑多少(网络路径能力)

真正的瓶颈取最小值:

  • 对方处理慢 → 被 rwnd 限制
  • 网络拥塞 → 被 cwnd 限制

(3)三个"窗口"概念对比

窗口名称 位置/归属 作用 动态性
16位窗口大小 TCP报头中的一个字段 接收方告诉发送方"我还能收多少"(即 rwnd) 由接收方应用层读取数据的速度决定
滑动窗口大小 发送方维护 实际能发送的数据量 = min(rwnd, cwnd) 随 rwnd 和 cwnd 变化
拥塞窗口 cwnd 发送方维护 判断网络是否健康的指标,超过会引发拥塞 根据网络拥塞情况动态调整

接收方通过 16位窗口字段 告诉发送方自己的接收能力(rwnd)(可结合选项扩展),发送方结合 拥塞窗口 cwnd (网络能力),取最小值作为 滑动窗口 的实际大小

角度 含义
最终发送窗口 是一个数值 (大小)= min(rwnd, cwnd)
滑动窗口 是一个区间 (范围)= [snd_una, snd_una + 最终发送窗口]

(4)拥塞控制的两个阶段

RTT全称Round-Trip Time,即往返时间

MSS 全称 Maximum Segment Size,即最大报文段长度

阶段1:慢启动

特点:初始慢,但增长速度快(指数增长)

  • 初始 cwnd = 1~10 个 MSS(现代TCP通常初始为10)
  • 每收到 1 个 ACK,cwnd 加 1;经过 1 个 RTT,cwnd 翻倍

设当前 cwnd = n(单位是 MSS)

  • 一个 RTT 内,发送方能发 n 个包
  • n 个包会触发接收方发回 n 个 ACK(假设无延迟应答影响)
  • 每个 ACK 让 cwnd +1
  • 一个 RTT 结束后,cwnd_new = n + n = 2n

为什么叫"慢启动"?

  • 相比直接发一个大窗口,初始值确实很慢(从1开始)
  • 但增长速度是快的(指数级),这个名字确实容易误解

谨慎启动,快速爬升

错误理解 正确理解
收到 1 个 ACK,cwnd 就 ×2 收到 1 个 ACK,cwnd 只 +1
翻倍是瞬间发生的 翻倍是一个 RTT 周期结束后的结果
所有阶段都翻倍 只有慢启动阶段,且达到慢启动阈值后停止翻倍
阶段2:拥塞避免

触发条件: cwnd 达到 慢启动阈值 (ssthresh)

  • ssthresh初始值:初始 ssthresh 通常设为一个较大值

  • ssthresh更新规则:每次检测到网络拥塞(丢包)时, ssthresh = 当前cwnd 的一半

  • 作用:当 cwnd 达到 ssthresh 后,从指数增长切换为线性增长(每个 RTT 拥塞窗口增加 1 MSS)

    超时丢包(严重拥塞)
    ssthresh = cwnd / 2
    cwnd = 1
    重新进入慢启动

    收到3个重复ACK(轻度拥塞,快重传+快恢复)
    ssthresh = cwnd / 2
    cwnd = ssthresh + 3
    直接进入拥塞避免(不重新慢启动)

  • 超时 → 网络可能完全不通 → 严重 → 从1重新慢慢爬

  • 3个重复ACK → 网络还能传后续包 → 轻度 → 窗口减半直接线性增长

  • +3 是为了利用已被确认、离开网络的 3 个报文段所腾出的空间

为什么需要线性增长?

  • 指数增长太快,很容易一下就撞破网络承载极限
  • 线性增长更温和,逐步探测网络的上限

2.TCP效率提升机制

TCP的可靠性依赖确认应答,但一发一确认效率太低。于是,

  • 滑动窗口:允许批量发送,不需要等ACK就能发多个报文
  • **累积确认:**用一个ACK确认多个报文,减少ACK数量
  • 捎带应答:将确认信息附着在数据报文中,避免发送纯ACK

三者结合,在保证可靠性的前提下,实现了"高效传输"

2.1滑动窗口机制

(1)什么是滑动窗口

  • 滑动窗口是发送缓冲区的一部分
  • 用于保存已经发出去但暂时没有收到应答的报文
  • 窗口大小不能超过对方接收缓冲区剩余空间(即应答报文中的窗口大小)

(2)为什么需要滑动窗口

  • 有了滑动窗口区域,发送方可以一次性发送多个报文,无需等待每个ACK
  • 既保证可靠性(保存未确认报文以便重传 ),又提高效率(批量发送
  • 滑动窗口是流量控制、超时重传、快重传的基础
机制 与滑动窗口的关系
流量控制 发送方根据接收方报文中的16位窗口大小调整滑动窗口大小进而直接实现流量控制
超时重传 基于滑动窗口的已发送未确认区间判断丢包,超时后重传窗口内未被确认的报文
快重传 基于滑动窗口的重复 ACK 检测------收到 3 个重复 ACK 说明窗口内某个报文可能丢失,立即重传

(3)发送缓冲区的分区

复制代码
发送缓冲区(环形)
|<-- 已确认 -->|<-- 已发未确认(窗口内) -->|<-- 待发送 -->|<-- 空 -->|
0            a                           b            c          size
              ↑                           ↑
            ack指针                      nxt指针

- 已确认:[0, a)      - 可覆盖,内存可回收
- 已发未确认:[a, b)   - 滑动窗口区域,需暂存等待ACK
- 待发送:[b, c)       - 应用层已写入但尚未发送
- 空闲:[c, size)      - 可用缓冲区空间

发送缓冲区按发送与确认的性质划分为以下区域:

区域 说明
已确认 已收到ACK,可被覆盖
已发送但未收到应答 滑动窗口区域,需暂存
待发送 尚未发送的数据
空闲缓冲区
  • 区域划分通过首尾字节下标作为双指针实现
  • 窗口滑动的本质就是指针右移
  • TCP采用环状算法(模运算),保证滑动窗口不越界

(4)窗口的滑动机制

  • 窗口不会向左移动(已确认的数据不会回退),只会向右移动(窗口整体前移)
  • 窗口大小可以动态变化:变大、变小、不变,甚至可以变为0
变化类型 左边界 右边界 窗口大小 触发条件
右移(滑动) 右移 右移 不变 收到 ACK,已确认数据被移除
扩大 不变 右移 变大 接收方通告更大的 rwnd
缩小 不变 左移 变小 接收方通告更小的 rwnd
窗口关闭 不变 左移至边界 变为 0 接收方缓冲区满,通告 rwnd=0

(5)窗口边界的计算

复制代码
窗口边界
start = SND.UNA(已确认序号)
window_size = min(rwnd, cwnd)
end = start + window_size

实际发送
//可用窗口 表示 当前这一瞬间还能发多少
可用窗口 = end - SND.NXT(下一个要发送的序号)
//end 会在收到 ACK 或 rwnd/cwnd 变化时重新计算
待发数据量 = 有效数据量
本次可发 = min(可用窗口, 待发数据量)

发送方的行为就是在这个动态滑动的窗口内,尽最大努力发送待发数据

(6)窗口滑动的依据

  • 确认序号的定义: 确认序号为x,表示x之前的所有报文都已收到(累积确认
  • 如果前面的报文丢了,即使后面的收到了,ACK确认序号只能是前面的那个
  • 这保证了确认序号是线性的,不会跳跃,进而使滑动窗口连续滑动

(7)窗口大小影响因素

窗口大小 = min(接收方扛得住, 网络撑得住, 硬件发得出)

滑动窗口的实际大小受多个因素共同限制:

  • 对方接收窗口大小(流量控制)
  • 拥塞窗口大小(拥塞控制)
  • 硬件限制(窗口内部还会分块发送)

原则:窗口越大,网络吞吐量越高

rwnd = cwnd = 1000 时,窗口大小固定为 1000;有效数据只有 100,则实际只发送 100,但窗口能力仍是 1000,不因数据量而缩小

(8)序号与滑动窗口的关系

新连接双方各自独立随机选择起始序号;随机目的是防止旧报文干扰新连接

复制代码
初始序号(ISN) = 随机值
发送序号(Seq) = 初始序号(ISN) + 已发送字节数
确认序号(ACK) = 期望收到的下一个字节 = 收到的最后一个连续字节 + 1

滑动窗口用序号标记左右边界,确认序号使左边界单调递增,从而驱动窗口线性、连续地向右滑动

2.2快重传机制

(1)为什么需要快重传

在TCP协议中,超时重传是基础机制,但存在明显缺陷:

  • RTO(重传超时时间)通常较长(至少200ms,常见200ms~1s以上)
  • 如果丢包后被动等待超时,网络利用率低,延迟大
  • 尤其对于短连接或实时性要求高的场景,影响显著

快重传就是为了提前发现丢包、立即重传,不依赖超时

(2)快重传的核心触发条件

收到 3 个重复的 ACK**(注意** :必须是 3 个

  • 1 个重复 ACK → 可能是网络乱序(包可能晚到)
  • 2 个重复 ACK → 也可能是乱序
  • 3 个重复 ACK → 大概率是丢包

这个阈值(3)是一个工程权衡的结果,兼顾避免误判乱序与快速恢复

(3)快重传VS超时重传

特性 快重传 超时重传
触发条件 收到 3 个重复 ACK RTO 超时
响应速度 快(几十ms内) 慢(通常 ≥ 200ms)
是否依赖 RTO 估计
是否判定为丢包 高置信度 最终兜底
是否会引发拥塞控制 是(进入快恢复) 是(进入慢启动)
作用 主要丢包检测手段 兜底机制
  • 快重传收到3个重复ACK时判定丢包但网络尚可,将拥塞窗口减半并进入快恢复以温和调整;
  • 超时重传判定网络可能严重阻塞,将拥塞窗口降为1并进入慢启动,代价更大、恢复更慢

2.3延迟应答机制

(1)为什么需要延迟应答

假设:

  • 接收方内核缓冲区剩余空间 = 1KB
  • 收到1KB数据后立即ACK → 通告窗口 = 0KB(满)
  • 发送方被迫停止发送
  • 上层应用还没来得及read数据

结果:网络空闲,效率低下

(2)延迟应答的核心思想

收到数据后不立即ACK,等一会儿,给上层应用时间把数据读走,这样ACK时就能通告一个更大的窗口, 本质是博概率

  • 延迟一小段时间 → 上层可能正好把数据读走 → 缓冲区多出空间 → 通告窗口变大
  • 发送方看到大窗口 → 一次发更多数据 → 网络吞吐量提升

(3) 延迟应答的两个限制(防止死等)

限制类型 典型值 目的
时间限制 最大200ms 避免ACK拖太久触发发送方超时重传
数量限制 每2个报文应答一次 确保接收方不会无限延迟
  • 200ms:这是一个经验值,小于TCP的RTO(重传超时时间,通常200ms以上),防止因ACK延迟导致发送方误判丢包而重传
  • 2个报文:如果每次都等2个报文才ACK,相当于把ACK频率减半,减少了网络中的确认包数量,也给了应用两次数据的间隔时间来读取

实际策略(常见实现):

  • 收到2个报文 → 必须ACK
  • 或等待200ms → 必须ACK
  • 哪个条件先满足就触发

(4)开发者的影响

推荐做法:

  • 尽快调用 read() / recv() 把数据从内核缓冲区取走
  • 不要在内核层积压数据
  • 这样可以保证下次ACK时通告的窗口是真实可用的

不推荐:

  • 应用层处理慢,数据在内核堆积 → 窗口变小 → 发送方降速

2.4Nagle算法

组成部分 在Nagle算法里的体现
要解决的问题 网络中被大量小数据包(如1字节)浪费带宽,效率极低。
核心思想 "合并发送" ------ 将多个小数据积攒成一个大数据块再发送。
规则和步骤 1. 判断发送缓冲区是否有"未确认"的数据。 2. 有未确认数据 → 新数据放入缓冲区,暂不发送。 3. 无未确认数据 → 新数据立即发送。
触发条件 1. 累积到一个MSS 。 2. 或发送缓冲区已满。 3. 或收到上次数据的ACK。
执行动作 发送一个"合并后"的大数据包。

(2)Nagle算法VS延迟应答

带宽 = 网络链路的最大传输速率,单位时间能传输的最大数据量

对比维度 Nagle算法 延迟应答
谁执行 发送方 接收方
核心目的 减少小数据包发送,节省带宽 通告更大窗口,提升吞吐量
核心操作 攒小包,合并发 延迟ACK,等应用读数据
副作用 可能增加延迟(因为要等ACK) 可能让发送方误以为丢包
冲突点 两者都引入额外延迟,叠加效果可能导致明显卡顿

(3)经典问题:Nagle + 延迟应答 = 死等?

场景:

  • 发送方发了一个小包,进入"未确认"状态(Nagle把它卡住)。
  • 接收方收到包,但因为延迟应答,不立即回ACK。
  • 发送方等ACK才能发下一个包,接收方等时间才回ACK。
  • 双方都在等,造成几十毫秒的延迟。

解决方案:

  • 对实时性要求高的应用(如游戏、远程桌面),用 TCP_NODELAY 选项禁用Nagle算法。
  • Linux下:setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

2.5捎带应答机制

捎带应答 是指,接收方在向发送方返回确认(ACK)时,如果自己恰好也有数据要发送给对方,就将ACK确认信息数据放在同一个TCP报文段中发送,而不是单独发送一个只包含ACK的空报文

(1)为什么需要捎带应答

  • 在TCP协议中,一个ACK报文段通常只包含一个20字节的IP头和20字节的TCP头,共40字节,而数据部分为0
  • 没有捎带应答: 为了确认一个数据包,接收方需要专门发送一个40字节的"空头"ACK包。在网络中,这些小包会挤占带宽 ,并增加路由器/交换机处理大量微小数据包的负担,降低整体效率
  • 有捎带应答: 接收方把自己要发送的数据(比如一个HTTP响应)和ACK合并成一个更大的报文。发送方收到后,既拿到了数据,又完成了确认。这减少了网络中数据包的总数,提高了传输效率
相关推荐
小小小陆7 小时前
同一台电脑两个WinForm程序TCP通信
网络·网络协议·tcp/ip
攻城狮在此8 小时前
华三网络设备Console口登录交换机配置
网络
一个有温度的技术博主8 小时前
计算机网络基础三:从定义到分类,构建你的知识框架
计算机网络
liulilittle8 小时前
OPENPPP2 1.0.0.26145 正式版发布:内核态 SYSNAT 性能飞跃 + Windows 平台避坑指南
开发语言·网络·c++·windows·通信·vrrp
贺小涛8 小时前
VictoriaMetrics深度解析
java·网络·数据库
消失的旧时光-19438 小时前
C++ 网络服务端主线:从线程池到 Reactor 的完整路线图
开发语言·网络·c++·线程池·并发
zl_dfq9 小时前
计算机网络 之 【TCP协议】(面向字节流、TCP异常情况、保活机制、文件与Socket的关系、网络协议栈的本质)
网络·计算机网络·tcp
多年小白9 小时前
2026年AI智能体“三国杀“:腾讯龙虾矩阵、阿里千问生态与字节豆包的技术架构全解析
网络·人工智能·科技·矩阵·notepad++
wanhengidc9 小时前
云手机 性能不受限 数据安全
服务器·网络·安全·游戏·智能手机