传输层

考点定位

  • 所属大纲章节:传输层提供的服务(功能、寻址与端口、复用与分用);UDP(UDP 数据报、UDP 校验);TCP(TCP 报文段、TCP 连接管理、TCP 可靠传输、TCP 流量控制与拥塞控制)
  • 常见题型:选择题(端口号与复用分用、UDP/TCP 对比、控制位辨析、TIME_WAIT 原因);计算大题(三次握手/挥手序号推演、拥塞控制 cwnd 逐 RTT 表格推演、发送窗口与信道利用率)——TCP 拥塞控制推演是 408 计网大题最稳定的题型之一
  • 考查频率:最高档之一。传输层与网络层轮流承担计网综合大题,选择题每年必有多道

前置知识:分层与端到端通信(计算机网络体系结构);GBN/SR 滑动窗口协议(数据链路层)——TCP 的可靠传输机制本质是 GBN 与 SR 的混合体,先学链路层的窗口机制再回看 TCP 会非常顺;IP 分组转发(网络层)——本章所有报文最终都装进 IP 分组传输。

工程直觉迁移:你天天用的 netstat/ss 里的状态(ESTABLISHED、TIME_WAIT、CLOSE_WAIT)、SO_REUSEADDR、tcpdump 里的 SYN/FIN/RST、内核参数 tcp_congestion_control,全是本章内容。本章任务是把运维直觉规则化、可推演化:任给一段拥塞控制事件序列,能手写出每个 RTT 的 cwnd/ssthresh 数值表。

一、传输层的功能与端口

1. 传输层在网络中的位置

网络层(网络层)负责把分组从一台主机送到另一台主机(主机到主机);传输层在此基础上提供进程到进程的交付——报文最终要交给主机上某个具体进程(浏览器标签页、SSH 会话、数据库连接)。这是传输层区别于网络层的根本点。

传输层两大功能:

  1. 复用与分用:发送方多个应用进程共用同一个传输层向下发送数据(复用);接收方传输层按标识把数据交付给正确的进程(分用)
  2. 对上屏蔽网络细节:网络层只提供尽最大努力交付,TCP 在其上构建可靠传输、流量控制、拥塞控制(UDP 则干脆不加这些东西,把"简"进行到底)

注意:只有端系统有传输层,路由器最高只到网络层(路由器不处理 TCP/UDP 报文,除非它自身作为主机运行路由协议进程)。

2. 端口号:分用的依据

传输层用端口号(16 位,0~65535)标识进程。IP 地址 + 端口号 = 套接字(socket)地址,唯一确定一条通信端点。端口号按范围分三类(选择题高频):

类别范围说明与例子
熟知端口(well-known)0~1023分配给常用服务:FTP 21(控制)、20(数据)、TELNET 23、SMTP 25、DNS 53、TFTP 69、HTTP 80、SNMP 161、HTTPS 443
登记端口(registered)1024~49151给没有熟知端口的应用登记用,如 MySQL 3306、PostgreSQL 5432、SQL Server 1433
临时端口(ephemeral)49152~65535客户端进程临时使用,通信结束即回收

考试要点:

  • 服务器端用熟知/登记端口,客户端用临时端口。分用靠"目的端口"找服务器进程,客户端自身端口只在响应中作为源端口出现
  • 常考端口号必须记熟:上面表中 9 个熟知端口 + 3306 等常见登记端口
  • 复用分用的辨析:一台主机上两个进程同时用 80 端口是复用冲突不允许;不同主机的客户端可以用相同的临时端口互不影响(因为 IP 不同)

3. 面向连接 vs 无连接

  • UDP:无连接、尽最大努力交付、面向报文(保留报文边界)、无拥塞控制、支持一对多、首部 8 B、适合实时应用(DNS、RIP、DHCP、流媒体)
  • TCP:面向连接(先握手)、可靠有序、全双工、面向字节流(不保留报文边界)、点对点、有流量控制和拥塞控制、首部 20~60 B、适合要求可靠的应用(HTTP/HTTPS、SSH、SMTP)

"面向字节流"的含义:应用层交给 TCP 的是一串无结构的字节流,TCP 把它们切分成合适大小的报文段,发送方一次 write 100 B 与接收方分两次 read 50 B + 50 B 是等价的;而 UDP 面向报文,一次 send 对应一次 recv,应用层给多长就发多长(太长才在 IP 层分片)。

二、UDP

1. UDP 报文格式

首部仅 8 B,共 4 个字段,每个 2 B:

  • 源端口:可省(全 0)——若对方无需回话(如 SNMP trap)
  • 目的端口:分用的唯一依据
  • 长度:UDP 用户数据报总长度(首部 + 数据),最小值 8(仅有首部)
  • 校验和:可选字段,全 0 表示不校验(IPv4 中允许;IPv6 中 UDP 校验和必填,这是冷门选择题点)

2. UDP 的特点(与 TCP 对照记忆)

  1. 无连接:发送前无握手,时延小
  2. 尽最大努力交付:不保证可靠、不保证有序、不重复去重
  3. 面向报文:保留报文边界,不合并不拆分(IP 层分片不算)
  4. 无拥塞控制:网络拥塞时照发不误——适合实时应用(丢一点无所谓,堵住就完了),也正因如此 UDP 流量容易加剧拥塞
  5. 支持一对一、一对多、多对一、多对多(TCP 只能点对点)
  6. 首部开销小:8 B vs TCP 最少 20 B

3. UDP 校验与伪首部

UDP 校验和的计算是高频考点,流程如下:

  1. 加伪首部:在 UDP 报文前临时拼上 12 B 伪首部——源 IP(4 B)、目的 IP(4 B)、全 0(1 B)、协议 17(1 B)、UDP 长度(2 B)。伪首部只参与校验计算,不向下传输
  2. 若数据字节数为奇数,补一个全 0 字节(仅计算用)
  3. 按 16 位字分组,包括伪首部、UDP 首部、数据,全部做反码求和(溢出回卷加到最低位),结果取反码填入校验和字段
  4. 接收方用同样方法(校验和字段参与)计算,结果应为全 1;否则丢弃或交给上层带警告

为什么要伪首部:校验和不仅验证 UDP 自身,还验证"这个报文有没有被投递到正确的主机、正确的协议"——IP 与端口一起校验,防止 IP 层交付错误。伪首部中的 UDP 长度字段还可以防止报文被截断后校验仍然通过的漏洞。

记忆锚点:伪首部 = 源 IP + 目的 IP + 0 + 17 + UDP 长度,共 12 B;TCP 校验和同样用伪首部,只是协议字段换成 6(TCP)。

三、TCP 报文段格式

1. 首部结构总览

TCP 报文段 = 首部(20~60 B)+ 数据。固定首部 20 B,选项最多 40 B,且选项长度必须是 4 B 的整数倍(不足补填充)。

2. 逐字段精讲(选择题逐字段出题)

  • 源端口/目的端口(各 16 位):分用依据。端口 + IP 构成套接字
  • 序号 seq(32 位):本报文段数据部分第一个字节在字节流中的编号。注意三点:
    • 序号指的是数据载荷的字节编号,不是报文段编号
    • SYN 报文段虽然不携带数据,但消耗一个序号;同理 FIN 也消耗一个序号(这就是三次握手总共消耗 2 个序号、四次挥手也是 2 个的来源)
    • 纯 ACK 报文段不携带数据则不消耗序号
  • 确认号 ack(32 位):期望收到对方下一个字节的序号。隐含确认:ack = N 表示 N-1 及之前的所有字节已正确收到。注意 ack 字段本身的值只有在 ACK=1 时才有效
  • 数据偏移(4 位):即首部长度,单位是 4 B(32 位字),取值 515,对应首部 2060 B。名字叫"数据偏移"是因为它指明数据从第几个 4 B 处开始
  • 六个控制位(每位 1 bit,逐个记):
控制位全称置 1 的含义
URGUrgent紧急位,紧急指针有效,本报文段有需优先发送的数据(不排队)
ACKAcknowledgment确认位,确认号字段有效。连接建立后所有报文段 ACK 都置 1
PSHPush接收方收到后尽快向上交付给应用进程,不等缓存填满
RSTReset连接出现严重差错须释放重连,或拒绝连接、拒绝打开端口
SYNSynchronize同步位,请求/接受建立连接,配合 seq 交换初始序号
FINFinish终止位,发送方数据发完,请求释放连接
  • 窗口(16 位):本方接收窗口 rwnd,即作为接收方告知对方"我还能接收多少字节",是对方发送窗口的上限依据之一(流量控制核心字段)。指本报文段接收方期望从确认号开始能接收的字节数
  • 校验和(16 位):与 UDP 相同的反码求和算法,伪首部协议字段为 6,且 TCP 校验和必填(不可置 0 跳过)
  • 紧急指针(16 位):URG=1 时有效,指出本报文段中紧急数据的末尾在数据中的位置(相对序号的正向偏移);即使窗口为 0 也可发送紧急数据
  • 选项(长度可变):常考的有
    • MSS(最大报文段长度):数据部分最大长度(不含首部),典型值 536 或协商值(如以太网环境 1460 = 1500 − 20 IP − 20 TCP)
    • 窗口扩大选项:突破 16 位窗口 64 KB 上限
    • SACK 选项:选择确认,见第五节

3. 字段级易错点

  • 数据偏移单位是 4 B,不是 1 B;其值为 15 时首部 60 B——考题常给"数据偏移 = 8",要立刻换算出首部 32 B
  • 窗口字段是接收窗口("我能收"),不是"我要发";但它约束对方的发送窗口
  • seq 是本报文段数据第一个字节的序号;ack 是期望对方下一个报文段数据第一个字节的序号——一个"当前发",一个"未来收"

四、TCP 连接管理

TCP 是面向连接的:数据传输前必须三次握手建立连接,结束后四次挥手释放连接。连接的实质是双方各自维护的发送缓存、接收缓存、序号状态与窗口状态,不是网络中真的有一条"管道"。

1. 三次握手:逐步推演

设客户端初始序号 xx,服务器初始序号 yy(初始序号随机选取,防止与历史连接的旧报文段混淆)。四列表格推演(大题标准答案格式):

步骤方向序号 / 确认号标志位发送方状态变迁(发送前 → 发送后/接收后)
1C → Sseq = xSYN = 1客户端:CLOSED → 发 SYN → SYN_SENT
2S → Cseq = y, ack = x + 1SYN = 1, ACK = 1服务器:LISTEN → 收 SYN、回 SYN+ACK → SYN_RCVD
3C → Sseq = x + 1, ack = y + 1ACK = 1(可捎带数据)客户端:SYN_SENT → 收到步骤 2 后即 ESTABLISHED → 发 ACK;服务器:SYN_RCVD 收到 ACK → ESTABLISHED

关键推演细节:

  • SYN、FIN 各消耗一个序号,纯 ACK 不消耗。故第三次握手的 seq = x + 1(SYN 用掉了 x),ack = y + 1(确认了服务器的 SYN)
  • 第三次握手报文段可以携带数据;第一次、第二次握手的 SYN 报文段不携带数据(SYN 本身占一个序号)
  • 客户端在收到第二次握手后即进入 ESTABLISHED,可先发数据;服务器要等第三次握手才 ESTABLISHED
  • 状态序列:客户端 CLOSED → SYN_SENT → ESTABLISHED;服务器 CLOSED → LISTEN → SYN_RCVD → ESTABLISHED(待动画化:分步时序图 + 状态高亮)

为什么必须三次而不是两次:核心是防止已失效的连接请求报文段突然又传到服务器造成资源浪费。若两次握手即可建立,客户端历史遗留的一个旧 SYN 迟到抵达,服务器回 ACK 即单方面建立连接并分配缓存,而客户端根本不认——服务器空耗资源。三次握手中服务器收不到第三次 ACK 就不会真正建立连接。此外三次握手也确保"双方都能确认自己和对方的收发能力正常"。

2. 四次挥手:逐步推演

连接释放由任一方主动发起。设此时客户端数据发完,主动关闭;双方当前序号接续数据流。四列表格:

步骤方向序号 / 确认号标志位发送方状态变迁
1C → Sseq = uFIN = 1, ACK = 1客户端:ESTABLISHED → 发 FIN → FIN_WAIT_1
2S → Cseq = v, ack = u + 1ACK = 1服务器:ESTABLISHED → 收 FIN → CLOSE_WAIT(仍可发数据);客户端:FIN_WAIT_1 → 收 ACK → FIN_WAIT_2
3S → Cseq = w, ack = u + 1FIN = 1, ACK = 1服务器:LAST_ACK 之前——数据发完后发 FIN → LAST_ACK
4C → Sseq = u + 1, ack = w + 1ACK = 1客户端:FIN_WAIT_2 → 收 FIN → TIME_WAIT → 等 2MSL → CLOSED;服务器:LAST_ACK 收 ACK → CLOSED

关键推演细节:

  • 为什么四次不是三次:TCP 全双工,两个方向独立关闭。服务器收到 FIN 后可能还有数据没发完,所以 ACK(步骤 2)与 FIN(步骤 3)通常分开发,中间服务器处于 CLOSE_WAIT 继续发送剩余数据。若恰好无数据要发,步骤 2 与 3 可合并为 FIN+ACK,变成"三次挥手"
  • FIN 消耗一个序号:步骤 4 的 ack = w + 1(确认了服务器的 FIN);客户端的 seq = u + 1(自己的 FIN 用掉了 u)
  • TIME_WAIT 等待 2MSL 的原因(简答标准答案,两条缺一不可):
    1. 保证最后一个 ACK 能到达服务器:若最后的 ACK 丢失,服务器会超时重传 FIN;客户端必须留在 TIME_WAIT 才能重发 ACK。若客户端直接 CLOSED,收到重传 FIN 只能回 RST,服务器异常关闭
    2. 让本连接产生的所有报文段在网络中消逝:等待 2 倍报文段最大生存时间(MSL),防止旧连接的报文段窜入之后用相同端口建立的新连接
  • MSL(Maximum Segment Lifetime)即报文段最大生存时间;实现中 TIME_WAIT = 2MSL(如 Linux 默认 60 s)
  • 半关闭(half-close):客户端发 FIN 并收到 ACK 后(FIN_WAIT_2),客户端→服务器方向关闭,但服务器→客户端方向仍可继续传数据,直到服务器也发 FIN。TCP 提供半关闭能力;而 UDP 无连接,谈不上关闭。工程场景:客户端发完上传数据后仍要收服务器的响应/日志
  • 服务器大量 CLOSE_WAIT 堆积通常意味着应用层没有调用 close(对方已关、自己没关);大量 TIME_WAIT 出现在主动关闭方——这是运维排障常识,也可作选择题情境

3. TCP 状态变迁图

(同时打开:双方都发 SYN,各自从 SYN_SENT 直接进入 SYN_RCVD 再到 ESTABLISHED;同时关闭:双方同时发 FIN,都经过 CLOSING 汇合到 TIME_WAIT。两者属于低频考点,知道状态名即可。主动打开/主动关闭一侧走左半区(SYN_SENT、FIN_WAIT 系列),被动一侧走右半区(LISTEN、CLOSE_WAIT 系列)——这张图是选择题"某主机当前处于 X 状态,它接下来可能处于什么状态"的直接出题源。)

五、TCP 可靠传输

TCP 在不可靠的 IP 数据报服务之上构建可靠传输:不丢、不错、不乱、不重。手段是四件套:序号 + 确认 + 重传 + 校验。这套机制的本质是数据链路层中 GBN/SR 滑动窗口思想在端到端字节流上的放大版。

1. 序号与确认:累积确认

  • 发送方给字节流中每个字节编号;报文段首部 seq 指向本段第一个数据字节
  • 接收方采用累积确认(cumulative acknowledgment):ack = N 表示"序号 N-1 及之前的所有字节都已正确收到,期望下一字节序号为 N"。即使收到失序段(如先收到 200499、再收到 500999 前先收到 600~699),接收方仍只能确认连续前缀
  • 累积确认的优点:即使某个 ACK 丢失,后续携带更大确认号的 ACK 也能补上确认;缺点:不能精确告知发送方哪些失序段已收到(这正是 SACK 要解决的问题)
  • TCP 的确认机制同时具有"确认 + 重传"的闭环:发送方每发一段就启动计时器,超时未确认即重传

GBN/SR 对照(选择题爱考这种"传承关系"):

机制链路层原型TCP 的做法
累积确认GBN相同:只确认连续前缀
失序缓存SR接收方缓存失序段(GBN 丢弃),配合 SACK 可精确告知
单段重传SR超时只重传最早未确认的那一段,不像 GBN 全部重传
窗口大小链路层固定配置动态:min(rwnd, cwnd),随流量/拥塞控制调整

结论:TCP ≈ GBN 的累积确认 + SR 的失序缓存与选择性重传的混合体。

2. 超时重传与 RTO 自适应

重传的关键是重传超时时间 RTO(Retransmission Time-Out):

  • RTO 必须大于 RTT:设小了会频繁误重传;设大了丢失后空等、效率低
  • RTT 本身随网络波动,因此 RTO 必须自适应:连续测量 RTT 样本,维护加权平均

标准自适应算法(RFC 6298,教材同名公式,α=1/8\alpha = 1/8、β=1/4\beta = 1/4):

Err=SampleRTT−SRTT\text{Err} = \text{SampleRTT} - \text{SRTT}

SRTT←SRTT+α⋅ErrRTTVar←RTTVar+β⋅∣Err∣\text{SRTT} \leftarrow \text{SRTT} + \alpha \cdot \text{Err} \qquad \text{RTTVar} \leftarrow \text{RTTVar} + \beta \cdot |\text{Err}|

RTO=SRTT+4⋅RTTVar\text{RTO} = \text{SRTT} + 4 \cdot \text{RTTVar}

其中 SRTT 是平滑 RTT(加权平均往返时间,旧称 RTTs),RTTVar 度量 RTT 的波动幅度。直觉:RTO = 平均值 + 4 倍标准差余量,波动的连接留更大余量。

两个配套规则(Karn 算法):

  1. 重传的报文段不采样 RTT——无法确认 ACK 对应首发还是重传,样本有毒,直接丢弃
  2. 每发生一次重传,RTO 翻倍(指数退避),连续丢包时逐步拉大重传间隔,避免雪上加霜

3. 快重传:不等超时的重传

超时是"重度拥塞"信号,等它太慢。快重传利用冗余 ACK:

接收方收到失序段时必须立即重复发送"期待缺失段"的 ACK。发送方连续收到 3 个冗余 ACK(即同一确认号共出现 4 次)时,不等 RTO 到期,立即重传缺失的那一段。

推演示例:发送 seq=100 的段丢失,后续 101 段正常到达,接收方连续回复 ack=100 共 3 次(冗余)→ 发送方判定 100 段已丢,立即重传。

为什么是 3 个而不是 1 个:单纯的失序(后发先至)也会触发重复 ACK,3 个冗余 ACK 才比较确信"真的丢了",把误判率压低。

4. SACK:选择确认

累积确认的盲区:发送方不知道失序段中哪些其实已收到,只能重传从缺失点开始的全部段。SACK(Selective ACK)通过 TCP 选项字段携带已收到的不连续块边界(最多 4 组左右,受 40 B 选项长度限制),发送方据此只重传真正缺失的段——这就是把 SR 思想真正落到 TCP。SACK 需要双方在握手时通过选项协商启用。408 考"概念 + 作用"层面:SACK 减少不必要的重传,提高带宽利用率。

六、TCP 流量控制

1. 滑动窗口机制

流量控制的目标:匹配收发双方的处理速度——防止发送方发太快淹死接收方(接收缓存有限)。核心机制是滑动窗口,与数据链路层的 GBN/SR 窗口同源,但 TCP 的窗口单位是字节且大小动态变化。

  • 接收方在每个 ACK 中用窗口字段通告自己的接收窗口 rwnd("确认号开始我还能收这么多字节")
  • 发送方的发送窗口 = min(rwnd, cwnd):流量控制限制(对方缓存)与拥塞控制限制(网络容量)取小者。这个公式是窗口类计算题的第一行
  • 发送缓存被窗口分为四段(从左到右):①已发送已确认 ②已发送未确认 ③允许发送尚未发送(可用窗口)④不允许发送。收到新确认后窗口左沿右移(释放②),收到新通告后右沿调整——"滑动"由此得名

手推示例(大题格式):主机 A 向 B 发送数据,B 的接收窗口 400 B,A 收到的确认与窗口通告序列如下(每段 100 B,忽略确认传播等细节):

时刻B 通告(ack, rwnd)A 发送窗口解读
初始—400seq 1~400 可发
收到 ack=101, rwnd=300(101, 300)300前 100 B 已确认,B 缓存被占 100
收到 ack=201, rwnd=400(201, 400)400B 应用层已读走缓存,窗口张开
收到 ack=301, rwnd=0(301, 0)0B 缓存满,A 必须停发

2. 零窗口与坚持计时器

rwnd = 0 后发送方停止发送,进入僵局风险:

  • B 的应用进程把缓存腾空后,B 发送窗口非零通告(窗口更新)
  • 若这个通告丢失,死锁发生:B 等 A 发数据,A 等 B 的窗口更新

解药——坚持计时器(persist timer):A 收到零窗口通告即启动;超时后发送零窗口探测报文段(1 字节),B 必须响应并携带当前窗口值。若仍为 0,重置计时器再等;探测报文即使遇到拥塞也不会被丢弃(TCP 规定对探测必须响应,且其发送不受窗口为 0 的限制)。这样即使窗口更新丢失,周期性探测也能打破死锁。

考点辨析:坚持计时器对抗的是"零窗口 + 窗口更新丢失"死锁;TIME_WAIT 的 2MSL 对抗的是"最后 ACK 丢失 + 旧报文残留"——两个计时器/等待机制不要张冠李戴。

3. 糩涂窗口综合征与两个算法

糊涂窗口综合征(Silly Window Syndrome, SWS):接收方每次只腾出几字节就通告(发送方只好发几字节的迷你段),或发送方每次只产生几字节就发——段内有效载荷极小,首部 20 B+20 B 的开销占比爆炸,效率极低。两端各有一个对策:

  • 接收方对策(Clark 算法):缓存腾出的空间小于 min⁡(MSS,缓存空间的一半)\min(\text{MSS}, \text{缓存空间的一半}) 时,继续通告 rwnd = 0,攒够再开窗
  • 发送方对策(Nagle 算法):数据先攒着,满足以下任一条件才发送:
    1. 攒满一个 MSS
    2. 收到之前已发数据的确认(网络上没有本连接未确认的小段)

Nagle 与时延敏感应用冲突:交互式应用(游戏、SSH 逐键回显)宁愿小段也要低延迟,工程上用 TCP_NODELAY 关闭 Nagle。408 角度记住:Nagle 算法 = 提高网络利用率(攒大段),代价是增加时延。

七、TCP 拥塞控制

流量控制保护接收方,拥塞控制保护网络:防止过多数据注入网络把路由器队列压垮。拥塞控制是闭环控制(发送数据 → 检测反馈 → 调整速率),属于资源拥塞控制中的动态预防/缓解手段。

1. 两个前提

  • cwnd(拥塞窗口):发送方维护的状态变量,单位为 MSS(最大报文段长度)的倍数。实际发送窗口 = min⁡(rwnd,cwnd)\min(\text{rwnd}, \text{cwnd})
  • 拥塞判定信号(决定走哪个分支,大题第一步先分类事件):
    • 超时(重传计时器到期)→ 判定网络重度拥塞:响应剧烈(cwnd 打回 1,重新慢开始)
    • 收到 3 个冗余 ACK → 判定轻度拥塞(个别段丢失但后续段仍能到达):响应温和(cwnd 减半,进入拥塞避免)

2. 四个算法

  1. 慢开始(slow start):连接建立/超时后,cwnd 从 1 MSS 起步,每收到一个 ACK cwnd 加 1,效果上每经过一个 RTT cwnd 翻倍(1→2→4→8…,指数增长)。"慢"指起点低,不是增长慢
  2. 拥塞避免(congestion avoidance):当 cwnd≥ssthreshcwnd \ge ssthresh 后,每经过一个 RTT cwnd 加 1 MSS(线性增长),谨慎试探网络容量上限
  3. 快重传(fast retransmit):收到 3 个冗余 ACK 立即重传缺失段(见第五节),不启动慢开始
  4. 快恢复(fast recovery):与快重传配套。不回到慢开始,而是 ssthresh←cwnd/2ssthresh \leftarrow cwnd/2,cwnd←新 ssthreshcwnd \leftarrow \text{新 } ssthresh,直接进入拥塞避免(线性增长)

事件触发的状态切换规则(背下来,推演题就是套这个):

事件ssthresh 更新cwnd 更新之后执行的算法
cwndcwnd 达到 ssthreshssthresh(无丢包)不变不变慢开始 → 拥塞避免
超时ssthresh←cwnd/2ssthresh \leftarrow cwnd/2cwnd←1cwnd \leftarrow 1重新慢开始
3 个冗余 ACKssthresh←cwnd/2ssthresh \leftarrow cwnd/2cwnd←新 ssthreshcwnd \leftarrow \text{新 } ssthresh拥塞避免(快恢复)

注意:cwnd/2cwnd/2 出现 0.5 时按教材惯例向下取整(真题数据通常设计为整数,做题时以整数为准)。早期 RFC 的快恢复曾用 cwnd=新 ssthresh+3cwnd = \text{新 } ssthresh + 3(把 3 个冗余 ACK 离开网络的段计入),408 按教材口径 cwnd=新 ssthreshcwnd = \text{新 } ssthresh 作答。

3. 完整推演例题 A:超时分支(模拟题)

题面:TCP 连接建立时 cwnd = 1 MSS,ssthresh = 8 MSS。传输过程中依次发生:cwnd 增长到 12 时发生超时;之后 cwnd 再次达到 12 时收到 3 个冗余 ACK。以 RTT 为推演单位,给出每个 RTT 的 cwnd 与 ssthresh,直至冗余 ACK 后再增长 3 个 RTT。

推演表("本轮末"指该 RTT 内发送所用的窗口值;事件在轮末生效):

RTT本轮 cwndssthresh阶段/事件说明
118慢开始起点
228慢开始翻倍
348慢开始翻倍
488慢开始→门限达到 ssthresh,下一轮起拥塞避免
598拥塞避免+1
6108拥塞避免+1
7118拥塞避免+1
8128拥塞避免,轮末超时超时:ssthresh=12/2=6,cwnd=1
916慢开始(重启)重新指数增长
1026慢开始翻倍
1146慢开始翻倍(4 < 6 继续)
1266慢开始→门限达到 ssthresh,转拥塞避免
1376拥塞避免+1
1486拥塞避免+1
1596拥塞避免+1
16106拥塞避免+1
17116拥塞避免+1
18126拥塞避免,轮末 3 个冗余 ACKssthresh=12/2=6,cwnd=6
1966快恢复后拥塞避免直接线性,不回 1
2076拥塞避免+1
2186拥塞避免+1

关键点:第 8 轮超时后 cwnd 从 12 直接回 1(不是减半);第 18 轮冗余 ACK 后 cwnd 减半但不回 1,且新 ssthresh 仍为 6(12/2 恰好等于原值,这是命题人爱埋的巧合陷阱——按规则算,不要想当然"不变")。

4. 完整推演例题 B:三重 ACK 先行分支(模拟题)

题面:cwnd = 1,ssthresh = 16。cwnd 增长到 18 时收到 3 个冗余 ACK;此后 cwnd 增长到 10 时发生超时。推演至超时后进入拥塞避免。

RTT本轮 cwndssthresh阶段/事件说明
1~51, 2, 4, 8, 1616慢开始第 5 轮达门限,转拥塞避免
61716拥塞避免+1
71816拥塞避免,轮末 3 个冗余 ACKssthresh=18/2=9,cwnd=9
899快恢复后拥塞避免从减半值线性增长
9109拥塞避免,轮末超时ssthresh=10/2=5,cwnd=1
1015慢开始(重启)
11~132, 4, 55慢开始第 13 轮达门限
1465拥塞避免+1

注意 18/2 = 9 整除;若题给奇数如 19,则按向下取整 ssthresh = 9。

5. AIMD 原则

拥塞避免阶段的策略即 AIMD(Additive Increase, Multiplicative Decrease,加性增、乘性减):

  • 加性增:无拥塞时每个 RTT 线性 +1,温和试探容量上限
  • 乘性减:一旦检测到拥塞,窗口立即减半(超时分支甚至归 1),剧烈收缩

直觉:误判拥塞的代价(丢包重传、队列崩溃)远大于保守增长的代价,所以减要狠、增要缓。多条 TCP 流在 AIMD 下会收敛到公平共享瓶颈带宽(公平性)。

6. 流量控制 vs 拥塞控制对比(必背表)

对比维度流量控制拥塞控制
目标防止发送方淹没接收方防止发送方压垮网络
保护对象端系统(接收缓存)全网(路由器队列、链路)
控制依据接收方通告的 rwnd发送方根据超时/冗余 ACK 推断的 cwnd
信息来源对方显式告知窗口隐式拥塞信号(无显式网络通知)
生效窗口发送窗口 = min⁡(rwnd,cwnd)\min(\text{rwnd}, \text{cwnd}),取小者同时受两者约束同左
典型机制滑动窗口、零窗口探测(坚持计时器)慢开始、拥塞避免、快重传、快恢复

一句话:流量控制是端到端的私事(听对方的),拥塞控制是全网公德(看网络的)。本节窗口机制与数据链路层的滑动窗口协议一脉相承,而其必要性正源于网络层只提供尽力而为交付。

八、典型例题

例题 1(2009 年真题):主机甲与主机乙之间已建立一个 TCP 连接,主机甲向主机乙发送了两个连续的 TCP 段,分别包含 300 B 和 500 B 的载荷,第一个段的序号为 200,主机乙正确收到两个段后,发回主机甲的确认号是多少?若第一个段丢失而第二个段后到达,主机乙发回的确认号又是多少?

解析:

  • 第一个段数据字节序号 200499,第二段 500999。全部正确收到后,确认号 = 期望的下一字节序号 = 1000
  • 第一个段丢失:第二段虽到但失序,累积确认只能确认连续前缀——已收到的连续前缀止于 199,故确认号 = 200(重复确认,这正是触发快重传的冗余 ACK 场景)

例题 2(模拟题):主机甲的拥塞窗口 cwnd = 4000 B,收到接收方通告 rwnd = 3000 B,当前已发送未确认的数据为 1000 B。甲还能继续发送多少字节?

解析:生效发送窗口 = min⁡(rwnd,cwnd)=3000\min(\text{rwnd}, \text{cwnd}) = 3000 B;已占 1000 B,可用窗口 = 3000−1000=20003000 - 1000 = \mathbf{2000} B。两问一步都不能跳:先取 min 再减已占量。

例题 3(模拟题):甲乙之间 RTT = 50 ms,单个 TCP 报文段载荷 1000 B,发送窗口固定为 2000 B,发送速率 100 Mbit/s。求甲连续发送时的信道利用率。

解析:窗口 2000 B = 2 段。甲把窗口发满后必须等第一个 ACK 返回(50 ms 后)窗口才能滑动,故周期 = 发送时延 + RTT。发送时延 = 2000×8/1082000 \times 8 / 10^8 s = 0.16 ms,利用率

U=0.160.16+50≈0.32%U = \frac{0.16}{0.16 + 50} \approx \mathbf{0.32\%}

一般式 U=W/RW/R+RTTU = \dfrac{W/R}{W/R + RTT}(W 为窗口字节数、R 为发送速率)。窗口远小于带宽时延积(R×RTTR \times RTT)时利用率极低——这就是大窗口/长肥管道问题的量化直觉,也是窗口扩大选项存在的理由。

例题 4(模拟题):TCP 连接中,乙发给甲的报文段 seq = 5000、ACK = 4800、窗口 = 2000。该报文段确认了哪些字节?甲下一批最多可发送哪些序号的字节(设无其他限制)?

解析:ACK=1 时确认号有效:已正确收到 4799 及之前全部字节;窗口 2000 表示从确认号起还能接收 2000 B,故甲可发序号 4800~6799。

九、常见坑

  1. 序号消耗:SYN、FIN 各占一个序号;第三次握手可捎带数据。大题问"从连接建立到释放,甲共消耗多少序号"时要把 SYN 与 FIN 计入
  2. 确认号方向:ack 是"我期望你的下一个字节",确认的是对方的数据;别把自己发的 seq 与期待对方的 ack 混为一谈
  3. 窗口字段立场:TCP 首部窗口字段是接收窗口(我能收),不是发送窗口;真正生效的发送窗口 = min(rwnd, cwnd)
  4. 超时 vs 三重 ACK 的响应差异:都把 ssthresh 减半,但超时 cwnd 回 1(慢开始),冗余 ACK 后 cwnd = 新 ssthresh 直接拥塞避免——推演题最大失分点
  5. 慢开始的"慢":指起点低而非增长慢,实际是指数增长;拥塞避免才是线性增长
  6. TIME_WAIT 在主动关闭方,CLOSE_WAIT 在被动关闭方;"服务器大量 CLOSE_WAIT"是应用没调 close,不是网络问题
  7. UDP 伪首部不传输、校验和可为 0(IPv4 中);TCP 校验和必填。伪首部协议字段 UDP=17、TCP=6
  8. 数据偏移单位是 4 B:值为 5 对应 20 B 首部,值为 8 对应 32 B;既不是 1 B 也不是 8 b
  9. MSS 指载荷上限(不含首部),以太网典型 1460 B = 1500 − 20 − 20;窗口/长度计算先确认题目给的是载荷还是总长

十、自测

  1. (概念)UDP 伪首部包含哪些字段?它参与校验计算,为何却不出现在实际传输的报文中?
  2. (推演)TCP 连接建立,客户初始序号 x = 1000,服务器初始序号 y = 3000。写出三次握手每一步的方向、seq、ack、标志位及双方状态。
  3. (计算)cwnd = 1,ssthresh = 4。按慢开始/拥塞避免推进,当 cwnd = 6 时发生超时。写出超时后 5 个 RTT 的 cwnd 与 ssthresh。
  4. (计算)接收方通告 rwnd = 0,之后缓存腾出 2 KB 并发出窗口更新,但该报文丢失。发送方如何打破死锁?涉及哪个计时器?
  5. (概念)快恢复为什么不把 cwnd 置回 1?它依据的拥塞信号与超时有何本质区别?

自测答案与简析

  1. 伪首部 = 源 IP(4 B)+ 目的 IP(4 B)+ 全 0(1 B)+ 协议 17(1 B)+ UDP 长度(2 B),共 12 B。它仅用于收发两端的校验和计算(顺带验证 IP 是否把报文交给了正确主机与协议),是临时拼接的辅助结构,不向下交给 IP 层,故不出现在实际传输的报文中
  2. ① C→S:SYN=1, seq=1000;客户 CLOSED→SYN_SENT,服务器 LISTEN→SYN_RCVD。② S→C:SYN=1, ACK=1, seq=3000, ack=1001;客户收到后 SYN_SENT→ESTABLISHED。③ C→S:ACK=1, seq=1001, ack=3001(可捎带数据);服务器 SYN_RCVD→ESTABLISHED。SYN 消耗一个序号,故第三次握手 seq = x+1
  3. 超时:ssthresh = 6/2 = 3,cwnd = 1。之后 5 个 RTT:cwnd = 1→2→3(达门限转拥塞避免)→4→5,ssthresh 恒为 3
  4. 发送方的坚持计时器超时后周期性发送 1 字节零窗口探测报文段,接收方必须响应并携带当前窗口值(本题通告约 2 KB),从而打破"窗口更新丢失"造成的死锁
  5. 3 个冗余 ACK 说明后续报文段仍能到达,网络仅轻度拥塞(个别段丢失),无需激进收缩;超时意味着报文与 ACK 双双无回音,属重度拥塞,必须回 1 重新慢开始。信号强度不同,响应强度不同(AIMD 精神)

配套测验见传输层章节测;下一章应用层将把本章的端口、TCP/UDP 特性与 DNS、HTTP、FTP、SMTP 等协议实例结合。