文章目录
- 1 传输层概述
- 1.1 传输层的功能
- 1.2 端口号
- 2 TCP 报文段
- 2.1 TCP 报文段首部格式
- 2.2 TCP 数据传送的过程
- 3 TCP 连接管理
- 3.1 TCP 连接的建立——三次握手
- 3.1.1 客户机向服务器发送 TCP 连接请求报文段
- 3.1.2 服务器向客户机发送 TCP 连接请求确认报文段
- 3.1.3 客户机向服务器发送 TCP 确认报文段
- 3.2 TCP 连接的释放——四次挥手
- 3.2.1 客户机向服务器发送 TCP 连接释放报文段
- 3.2.2 服务器向客户机发送 TCP 确认报文段
- 3.2.3 服务器向客户机发送 TCP 连接释放报文段
- 3.2.4 客户机向服务器发送 TCP 确认报文段
- 4 TCP 流量控制和可靠传输
- 4.1 接收方对发送方的第 1 次流量控制
- 4.2 接收方对发送方的第 2 次流量控制
- 4.3 接收方对发送方的第 3 次流量控制
- 5 TCP 拥塞控制
- 5.1 慢开始和拥塞避免
- 5.2 快重传和快恢复
1 传输层概述
1.1 传输层的功能
- 端到端通信:提供应用进程间的端到端通信(逻辑通信)。因此传输层又称为端到端协议。
- 差错检测:对首部和数据部分进行检测。
- 两种协议:面向连接的 TCP、无连接的 UDP。
- 复用和分用:
概念 | 解释 |
---|---|
传输层 TCP 复用 | 发送方的部分应用进程的报文在传输层使用 TCP 协议进行封装 |
传输层 UDP 复用 | 发送方的部分应用进程的报文在传输层使用 UDP 协议进行封装 |
网际层 IP 复用 | 发送方不同协议的数据都可以封装成 IP 数据报(IP 协议字段,TCP 为 6,UDP 为 17) |
网际层 IP 分用 | 接收方的网际层在去除首部后把数据交付给上层相应的协议(TCP 或 UDP) |
传输层 TCP 分用 | 接收方的传输层使用 TCP 协议去除首部后把数据交付给目的应用进程 |
传输层 UDP 分用 | 接收方的传输层使用 UDP 协议去除首部后把数据交付给目的应用进程 |
1.2 端口号
- 端口:应用进程通过端口号进行标识。端口号长度为 16b,能表示 65535 个不同的端口号。
端口号 | 类型 | 解释 |
---|---|---|
0 ~ 1023 | 熟知端口号 | 由 IANA 分配给 TCP/IP 体系结构应用层中最重要的一些应用协议 |
1024 ~ 49151 | 登记端口号 | 为没有熟知端口号的应用程序使用。要使用这类端口号,必须在 IANA 进行登记,以防止重复 |
49152 ~ 65535 | 短暂端口号 | 仅在客户端使用,由客户进程在运行时动态选择,通信结束后会被系统收回,以便给其他客户进程使用 |
- 需记忆的熟知端口号:
FTP | SMTP | DNS | DHCP | HTTP | BGP | HTTPS | RIP |
---|---|---|---|---|---|---|---|
21/20 | 25 | 53 | 67/68 | 80 | 179 | 443 | 520 |
【注】OSPF 不使用传输层协议,所以没有对应的端口号。但 OSPF 的 IP 协议字段值为 89。
2 TCP 报文段
2.1 TCP 报文段首部格式
- 源端口和目的端口:各占 2b,端口是传输层与应用层的服务接口。
- 序号(seq):占 32b,取值范围 0 ~ 232-1。用来指出本 TCP 报文段的第一个字节的序号。
- 确认号(ack):占 32b,取值范围 0 ~ 232-1。用来指出期望收到对方下一个 TCP 报文段的第一个字节的序号,同时也是对之前收到的所有数据的确认。若确认号为 N,则表明序号 N-1 为止的所有数据都已收到。
- 数据偏移(首部长度):占 4b,表示 TCP 首部长度。该字段的取值以 4B 为单位,最大字段值为 15,因此 TCP 首部最大长度为 60B。
- 保留:占 6b,保留为今后使用,但目前应置位 0。
- 紧急位(URG):当 URG=1 时,此报文段有紧急数据,应尽快传送。
- 确认位(ACK):当 ACK=1 时,确认号字段才有效。TCP 连接建立后,ACK 必须为 1。
- 推送位(PSH):当 PSH=1 时,就尽快地交付给应用进程,而不再等到接收到足够多的数据才向上交付。
- 复位位(RST):当 RST=1 时,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接。
- 同步位(SYN):当 SYN=1 且 ACK=0 时,表明这是一个 TCP 连接请求报文段。对方若同意建立连接,则应在响应的 TCP 报文段的首部中使 SYN=1 且 ACK=1。
- 终止位(FIN):当 FIN=1 时,表明此 TCP 报文段的发送方已经将全部数据发送完毕,现在要求释放 TCP 连接。
- 窗口:占 16b,指出发送本报文段的一方的接收窗口的大小,即接收缓存的可用空间大小,用来表征接收方的接收能力。该字段的取值以 1B 为单位。
- 校验和:占 16b,用来检查整个 TCP 报文段在传输过程中是否出现了误码。
- 紧急指针:占 16b,用来指明紧急数据的长度。该字段的取值以 1B 为单位。
- 选项:长度可变,有多种选项。其中有个选项为最大报文段长度(Maximum Segment Size,MSS),表示报文段中数据部分的最大长度。
- 填充:为了使整个首部长度是 4B 的整数倍。
【注】重点关注 seq、ack、ACK、SYN、FIN 这几个位。
2.2 TCP 数据传送的过程
假设有客户机 A 和服务器 B 已经建立了 TCP 连接。
(1)现 A 向 B 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=201, ack=800, ACK=1 | 100B(seq=201~300) |
seq=201
:指 A 的数据部分是从序号 201 开始的,数据部分总长 100B,因此数据部分的最后一个序号是 300。ack=800
:指 A 期望 B 发来的下一个报文段的数据部分序号是从 800 开始的。ACK=1
:连接建立后所有传送的报文段都必须置 1。
(2)然后 B 向 A 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=800, ack=301, ACK=1 | 200B(seq=800~999) |
seq=800
:指 B 的数据部分是从序号 800 开始的,数据部分总长 200B,因此数据部分的最后一个序号是 999。ack=301
:指 B 期望 A 发来的下一个报文段的数据部分序号是从 301 开始的,同时确认 A 的报文段(seq=201~300)已收到。ACK=1
:连接建立后所有传送的报文段都必须置 1。
(3)若 A 向 B 发送 3 个 TCP 确认报文段,其中第 2 个报文段丢失:
首部 | 数据部分 |
---|---|
seq=201, ack=x, ACK=1 | 100B(seq=201~300) |
seq=301, ack=x, ACK=1 | 100B(seq=301~400)(丢失) |
seq=401, ack=x, ACK=1 | 100B(seq=401~500) |
则 B 仅正确接收第 1 个和第 3 个报文段。此时 B 向 A 发送的 TCP 报文段,应对已正确接收且按序到达的最后一个 TCP 报文段确认,这就是累积确认(更详细的解释见第 4 节内容):
首部 | 数据部分 |
---|---|
seq=x, ack=301, ACK=1 | 200B(seq=x~x+199) |
3 TCP 连接管理
TCP 连接有三个阶段:连接建立、数据传送(上面已述)、连接释放。
3.1 TCP 连接的建立——三次握手
假设有客户机 A 和服务器 B 准备建立 TCP 连接。
服务器 B 进程处于 LISTEN(监听)状态,等待客户机 A 的连接请求。
3.1.1 客户机向服务器发送 TCP 连接请求报文段
客户机 A 向服务器 B 发送 TCP 连接请求报文段:
首部 | 数据部分 |
---|---|
seq=x, ack=0, SYN=1, ACK=0 | SYN 报文段不能携带数据 |
seq=x
:随机选取一个初始序号 x,作为客户机 A 的 TCP 报文段数据部分的初始序号。ack=0
:由于 ACK=0,所以 ack 字段无效。SYN=1
:TCP 连接请求报文段中的同步标志位 SYN 的值必须设置为 1。ACK=0
:当 SYN=1,ACK=0 时,表明这是一个 TCP 连接请求报文段。
此时,客户机 A 进入 SYN-SENT(同步已发送)状态。
【注】TCP 规定同步标志位 SYN 被设置为 1 的报文段(例如 TCP 连接请求报文段和 TCP 连接请求确认报文段)不能携带数据,但要消耗掉一个序号。
3.1.2 服务器向客户机发送 TCP 连接请求确认报文段
服务器 B 收到 TCP 连接请求报文段后,向客户机 A 发送 TCP 连接请求确认报文段:
首部 | 数据部分 |
---|---|
seq=y, ack=x+1, SYN=1, ACK=1 | SYN 报文段不能携带数据 |
seq=y
:随机选取一个初始序号 y,作为服务器 B 的 TCP 报文段数据部分的初始序号。ack=x+1
:服务器 B 期望收到客户机 A 发来的下一个报文段的数据部分序号是从 x+1 开始的,同时确认客户机 A 的报文段(seq=x)已收到。SYN=1
:TCP 连接请求确认报文段中的同步标志位 SYN 的值必须设置为 1。ACK=1
:当 SYN=1,ACK=1 时,表明这是一个 TCP 连接请求确认报文段。
此时,服务器 B 进入 SYN-RCVD(同步已接收)状态。
3.1.3 客户机向服务器发送 TCP 确认报文段
客户机 A 收到 TCP 连接请求确认报文段后,向服务器 B 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=x+1, ack=y+1, SYN=0, ACK=1 | TCP 确认报文段可以携带数据 |
seq=x+1
:客户机 A 的 TCP 报文段数据部分的序号为 x+1。ack=y+1
:客户机 A 期望收到服务器 B 发来的下一个报文段的数据部分序号是从 y+1 开始的,同时确认服务器 B 的报文段(seq=y)已收到。SYN=0
:TCP 确认报文段中的同步标志位 SYN 的值必须设置为 0。ACK=1
:当 SYN=0,ACK=1 时,表明这是一个普通的 TCP 确认报文段。
此时,客户机 A 进入 ESTABLISHED(连接已建立)状态。
【注】TCP 规定普通的 TCP 确认报文段可以携带数据,但如果不携带数据,则不消耗序号。如果该报文段不携带数据,则客户机 A 要发送的下一个数据报文段的序号仍为 x+1。
服务器 B 收到 TCP 确认报文段后,也进入 ESTABLISHED(连接已建立)状态。至此 TCP 连接已建立。
【总结】TCP 建立连接的过程:
客户机:“我有话要跟你讲,不知可不可以?”
服务器:“可以,你讲吧!”
客户机:“好的!blablabla”
3.2 TCP 连接的释放——四次挥手
假设有客户机 A 和服务器 B 准备释放 TCP 连接。
客户机 A 进程和服务器 B 进程处于 ESTABLISHED(连接已建立)状态。
3.2.1 客户机向服务器发送 TCP 连接释放报文段
客户机 A 向服务器 B 发送 TCP 连接释放报文段:
首部 | 数据部分 |
---|---|
seq=u, ack=v, FIN=1, ACK=1 | FIN 报文段可携带也可不携带数据 |
seq=u
:客户机 A 的 TCP 报文段数据部分的序号为 u,它等于客户机 A 之前已传送数据部分的最后一个字节的序号加 1。ack=v
:ack 字段的值为 v,它等于客户机 A 之前已收到数据的最后一个字节的序号加 1。FIN=1
:TCP 连接释放报文段中的同步标志位 SYN 的值必须设置为 1。ACK=1
:当 FIN=1,ACK=1 时,表明这是一个 TCP 连接释放报文段。
【注】TCP 规定,终止标志位 FIN 等于 1 的 TCP 报文段即使不携带数据,也要消耗掉一个序号。
此时,客户机 A 进入 FIN-WAIT-1(终止等待 1)状态。
3.2.2 服务器向客户机发送 TCP 确认报文段
服务器 B 收到 TCP 连接释放报文段后,向客户机 A 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=v, ack=u+1, FIN=0, ACK=1 | TCP 确认报文段可以携带数据 |
seq=v
:服务器 B 的 TCP 报文段数据部分的序号为 v。ack=u+1
:服务器 B 期望收到客户机 A 发来的下一个报文段的数据部分序号是从 u+1 开始的,同时确认客户机 A 的连接释放报文段(seq=u)已收到。FIN=0
:TCP 确认报文段中的同步标志位 SYN 的值必须设置为 0。ACK=1
:当 FIN=0,ACK=1 时,表明这是一个普通的 TCP 确认报文段。
此时,服务器 B 进入 CLOSE-WAIT(关闭等待)状态。客户机 A 已经没有数据要发送了。但服务器 B 如果还有数据要发送,客户机 A 仍要接收,即从服务器 B 到客户机 A 这个方向的连接并未关闭,这称为半关闭状态,该状态可能会持续一段时间。
3.2.3 服务器向客户机发送 TCP 连接释放报文段
服务器 B 向客户机 A 发送若干个 TCP 确认报文段,直到发送最后一次报文段,即 TCP 连接释放报文段:
首部 | 数据部分 |
---|---|
seq=w, ack=u+1, FIN=1, ACK=1 | FIN 报文段可携带也可不携带数据 |
seq=w
:经过发送若干个 TCP 确认报文段,服务器 B 的 TCP 报文段数据部分的序号变为 w。ack=u+1
:服务器 B 期望收到客户机 A 发来的下一个报文段的数据部分序号是从 u+1 开始的,同时重复确认客户机 A 的连接释放报文段(seq=u)已收到。FIN=1
:TCP 连接释放报文段中的同步标志位 SYN 的值必须设置为 1。ACK=1
:当 FIN=1,ACK=1 时,表明这是一个 TCP 连接释放报文段。
此时,服务器 B 进入 LAST-ACK(最后确认)状态。
3.2.4 客户机向服务器发送 TCP 确认报文段
客户机 A 收到 TCP 连接释放报文段后,向服务器 B 发送 TCP 确认报文段:
首部 | 数据部分 |
---|---|
seq=u+1, ack=w+1, FIN=0, ACK=1 | TCP 确认报文段可以携带数据 |
seq=u+1
:客户机 A 的 TCP 报文段数据部分的序号为 u+1。ack=w+1
:客户机 A 期望收到服务器 B 发来的下一个报文段的数据部分序号是从 w+1 开始的,同时确认服务器 B 的连接释放报文段(seq=w)已收到。FIN=0
:TCP 确认报文段中的同步标志位 SYN 的值必须设置为 0。ACK=1
:当 FIN=0,ACK=1 时,表明这是一个普通的 TCP 确认报文段。
此时,客户机 A 进入 TIME-WAIT(时间等待)状态,服务器 B 收到 TCP 确认报文段后进入CLOSED(连接关闭)状态。但是 TCP 连接仍未释放,必须经过 2MSL(最长报文段寿命,Maximum Segment Lifetime)的时间后,客户机 A 才能进入 CLOSED(连接关闭)状态。
【总结】TCP 释放连接的过程:
客户机:“我准备走了。”
服务器:“等一下,我还有一些话没说完。blablabla”
服务器:“blablabla”
服务器:“blablabla,我说完了,你可以走了。”
客户机:“好的!那我走了!”
4 TCP 流量控制和可靠传输
TCP 协议提供一种基于滑动窗口协议的流量控制机制。TCP 使用了校验、序号、确认和重传等机制来达到可靠传输的目的,在下面的过程中将体现这一特点。
假设有发送方 A 和接收方 B 已建立 TCP 连接,不考虑 TCP 的拥塞控制。再假定 A 只给 B 发送数据,B 对 A 进行流量控制。
在 A 和 B 建立 TCP 连接后,B 告诉 A:“我的接收窗口 rwnd=500”,因此 A 将自己的发送窗口 swnd 也设置为 500。窗口大小是以最大报文段 MSS 为单位的,一般将 MSS 设置为 1B,所以 “swnd=500”意思是发送窗口的大小为 500B。
A 的发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | √ | √ | |||
A 已发送 | ||||||||
B 已确认 | ||||||||
B 已收到 |
A 发送数据:seq=1,seq=101,seq=201,seq=301,发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | √ | √ | |||
A 已发送 | √ | √ | √ | √ | ||||
B 已确认 | ||||||||
B 已收到 |
4.1 接收方对发送方的第 1 次流量控制
但是很不巧,seq=201 丢失了,B 只收到 seq=1,seq=101,seq=301。其中 seq=301 是失序报文段,但 B 不会丢弃它。TCP 作如下规定:每接收到一个失序报文段,就要发送一次冗余 ACK,指明下一个期待的报文数据序号。
很明显,现在 B 期望收到 seq=201 的报文,因此 seq=301 是不会被确认的,这被称为累积确认。现在 B 的接收窗口已经接受了 200B 的数据,还有 300B 未接收,于是发送 ACK=1,ack=201,rwnd=300 的冗余报文给 A。
此时发送方 A 的 swnd 调整为 300,swnd 滑动到首个未确认的数据位置。发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | |||||
A 已发送 | √ | √ | √ | √ | ||||
B 已确认 | √ | √ | ||||||
B 已收到 | √ | √ | √ |
发送方 A 继续发送 seq=401,对于接收方 B 来说,seq=401 依然是失序报文段,继续发送 ACK=1,ack=201,rwnd=300 的冗余报文给 A。此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | √ | √ | |||||
A 已发送 | √ | √ | √ | √ | √ | |||
B 已确认 | √ | √ | ||||||
B 已收到 | √ | √ | √ | √ |
4.2 接收方对发送方的第 2 次流量控制
发送方 A 也没有对 seq=201 坐视不理,实质上从发出 seq=201 开始,重传计时器也开始计数了,A 发现计时器超时但仍未收到来自 B 的确认,于是进行超时重传。
【注】其实,当发送方 A 连续收到三个冗余 ACK 后,就可以立即重新发送 seq=201,而不必等待计时器超时,这被称为快重传算法,在下一节“拥塞控制”将提到。
接收方 B 收到 seq=201 后,由于网络流量原因,接收窗口需减小到 100B,于是返回 ACK=1,ack=501,rwnd=100 的报文。此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | |||||||
A 已发送 | √ | √ | √ | √ | √ | |||
B 已确认 | √ | √ | √ | √ | √ | |||
B 已收到 | √ | √ | √ | √ | √ |
发送方 A 发送 seq=501,此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | √ | |||||||
A 已发送 | √ | √ | √ | √ | √ | √ | ||
B 已确认 | √ | √ | √ | √ | √ | |||
B 已收到 | √ | √ | √ | √ | √ |
4.3 接收方对发送方的第 3 次流量控制
接收方 B 收到 seq=501 后,返回 ACK=1,ack=601,rwnd=0 的报文,表示 B 不再接收任何数据。此时发送缓冲区情况如下:
数据 | 1~100B | 101~200B | 201~300B | 301~400B | 401~500B | 501~600B | 601~700B | 701~800B |
---|---|---|---|---|---|---|---|---|
swnd | ||||||||
A 已发送 | √ | √ | √ | √ | √ | √ | ||
B 已确认 | √ | √ | √ | √ | √ | √ | ||
B 已收到 | √ | √ | √ | √ | √ | √ |
由于发送方 A 的 swnd=0,因此 A 不能发送任何数据了。接收方 B 必须发送一个非零窗口通知,以告知发送方 A:“你可以开始发送大小为 xxx 的窗口了。”所以,A 一直在等待这个通知,只要收到通知,将恢复传输过程。
然而可能会出现这样一种情况:B 发出的通知丢失,A 只能无限等待下去。为了打破由于非零窗口通知报文段丢失而引起的双方互相等待的死锁局面,TCP 为每一个连接都设有一个持续计时器:
- 只要 TCP 连接的一方收到对方的零窗口通知,就启动持续计时器。
- 当持续计时器超时时,就发送一个零窗口探测报文段,仅携带 1 字节的数据。
- 对方在确认这个零窗口探测报文段时,给出自己现在的接收窗口值 rwnd。
- 如果接收窗口值 rwnd 仍然是 0,那么收到这个报文段的一方就重新启动持续计时器,继续以上过程。
- 如果接收窗口值 rwnd 不是 0,那么死锁的局面就可以被打破了。
【注】TCP 规定:即使接收窗口值为 0,也必须接受零窗口探测报文段、确认报文段以及携带有紧急数据的报文段。
5 TCP 拥塞控制
5.1 慢开始和拥塞避免
设置一个慢开始门限阈值 ssthresh,初始值为 16。根据发送方的拥塞窗口 cwnd 的大小执行不同的算法:
- 当 cwnd < ssthresh 时,使用慢开始算法;
- 当 cwnd > ssthresh 时,使用拥塞避免算法;
- 当 cwnd = ssthresh 时,使用拥塞避免算法。
慢开始算法:从 cwnd=1 开始,每经过一个传输轮次(即往返时延 RTT)指数规律增长,cwnd=2,cwnd=4,cwnd=8,当 cwnd = ssthresh = 16 时,改用拥塞避免算法。
拥塞避免算法:每经过一个传输轮次(即往返时延 RTT),cwnd 加 1,即线性规律增长。只要发送方判断网络出现拥塞,则令 ssthresh = cwnd / 2。然后令 cwnd=1,重新执行慢算法。
【注】传输轮次和往返时延的区别:
- 传输轮次:发送一批报文段并收到它们的确认的时间。
- 往返时延 RTT:开始发送一批报文段到开始发送下一批报文段的时间。
例如有以下传输过程:
传输轮次 | cwnd | 发送的 TCP 数据部分的序号 | 算法 | 备注 |
---|---|---|---|---|
1 | 1 | 0 号 | 慢开始 | 初始时,cwnd = 1,ssthresh = 16 |
2 | 2 | 1 ~ 2 号 | 慢开始 | |
3 | 4 | 3 ~ 6 号 | 慢开始 | |
4 | 8 | 7 ~ 14 号 | 慢开始 | |
5 | 16 | 15 ~ 30 号 | 拥塞避免 | cwnd = ssthresh = 16 |
6 | 17 | 31 ~ 47 号 | 拥塞避免 | |
7 | 18 | 48 ~ 64 号 | 拥塞避免 | |
… | … | … | ||
13 | 24 | 171 ~ 194 号 | 拥塞避免 | 重传计时器发生超时,说明网络拥塞,ssthresh = cwnd/2 = 12 |
14 | 1 | 195 号 | 慢开始 | cwnd 重新设置为 1 |
15 | 2 | 196 ~ 197 号 | 慢开始 | |
16 | 4 | 198 ~ 201 号 | 慢开始 | |
17 | 8 | 202 ~ 209 号 | 慢开始 | |
18 | 12 | 210 ~ 221 号 | 拥塞避免 | cwnd = 16 > ssthresh = 12,改用拥塞避免算法 |
19 | 13 | 222 ~ 234 号 | 拥塞避免 | |
… | … | … |
慢开始和拥塞避免算法的实现过程如下图:
【注】慢开始和拥塞避免的含义:
- “慢开始”是指一开始向网络注入的报文段少,而并不是指拥塞窗口 cwnd 的值增长速度慢。
- “拥塞避免”也并非指完全能够避免拥塞,而是指在拥塞避免阶段将 cwnd 值控制为按线性规律增长,使网络比较不容易出现拥塞。
5.2 快重传和快恢复
快重传和快恢复是对慢开始和拥塞避免算法的改进。根据发送方的拥塞窗口 cwnd 的大小执行不同的算法:
- 当 cwnd < ssthresh 时,若是首次传输,则使用慢开始算法;如果不是首次传输,则使用拥塞避免算法;
- 当 cwnd > ssthresh 时,使用拥塞避免算法;
- 当 cwnd = ssthresh 时,使用拥塞避免算法。
快重传算法:当发送方连续接收到三个冗余 ACK 报文时,直接重传对方尚未收到的报文段,而不必等待该报文段的重传计时器超时。
快恢复算法:当发送方连续接收到三个冗余 ACK 报文时,令 ssthresh = cwnd / 2,然后 cwnd 从该 ssthresh 开始线性增加。
例如有以下传输过程:
传输轮次 | cwnd | 发送的 TCP 数据部分的序号 | 算法 | 备注 |
---|---|---|---|---|
1 | 1 | 0 号 | 慢开始 | 初始时,cwnd = 1,ssthresh = 16 |
2 | 2 | 1 ~ 2 号 | 慢开始 | |
3 | 4 | 3 ~ 6 号 | 慢开始 | |
4 | 8 | 7 ~ 14 号 | 慢开始 | |
5 | 16 | 15 ~ 30 号 | 拥塞避免 | cwnd = ssthresh = 16 |
6 | 17 | 31 ~ 47 号 | 拥塞避免 | |
7 | 18 | 48 ~ 64 号 | 拥塞避免 | |
… | … | … | ||
13 | 24 | 171 ~ 194 号 | 拥塞避免 | 发送方连续收到三个冗余 ACK 报文,说明网络拥塞,ssthresh = cwnd/2 = 12 |
14 | 12 | 195 ~ 206 号 | 拥塞避免 | cwnd 设置为 ssthresh = 12 |
15 | 13 | 207 ~ 219 号 | 拥塞避免 | |
16 | 14 | 220 ~ 233 号 | 拥塞避免 | |
17 | 15 | 234 ~ 248 号 | 拥塞避免 | |
18 | 16 | 249 ~ 264 号 | 拥塞避免 | |
19 | 17 | 265 ~ 281 号 | 拥塞避免 | |
… | … | … |
快重传和快恢复算法的实现过程如下图:
需要注意,发送方的发送窗口由接收方的接收窗口和发送方的拥塞窗口两者的最小值所决定,即swnd = min(rwnd, cwnd)
。