计网五层教学模型中传输层的UDP与TCP
本文仅简单介绍教学用五层模型中传输层的UDP协议与TCP协议
计网五层教学模型中传输层的UDP与TCP
一、关于传输层
- 在教学用五层模型中,传输层负责实现端到端(进程到进程)的数据段的以端口号为标识的传输,其依赖下层网络层实现的主机到主机的IP分组的以IP地址为标识的传输(通过IP地址与端口号,就能精准实现主机间特定进程间的数据传输),此外该层还主要实现
- 复用和分用
- 复用(从上到下):发送数据时,同主机的多个进程可使用同一种传输层协议
- 分用(从下到上):接收数据时,传输层能把数据准确交付给使用同协议的多个进程中正确的目标进程
- 差错检测
- TCP:检测出错后直接丢弃数据,并通知发送方重传
- UDP:检测出错后直接丢弃数据,但不通知发送方
- 复用和分用
- 传输层向应用层提供两种服务
- 面向连接、可靠的端到端传输服务(TCP),确保数据完整且正确,但速度慢开销大,适用于文件传输等场景
- 无连接、不可靠的端到端传输服务(UDP),数据可能丢失或出错,但速度快开销小,适用于视频直播等场景
- 关于进程、端口号、传输层协议间的关系如下
- 不同主机可以自由分配其内不同进程的端口号(如下图主机A和B都拥有
777
、113
端口号,二者并无关系且也不存在冲突) - 同一主机内的不同传输协议也可以自由分配依赖于该协议的不同进程的端口号(如下图主机B中的进程5和进程8都是用
886
端口号,二者分别使用TCP和UDP协议,故使用相同端口号也不冲突)
- 不同主机可以自由分配其内不同进程的端口号(如下图主机A和B都拥有
- 所以当不同主机的进程间进行通信时
- 发送方进程需指定使用何种传输层协议(不然无法区分类似上图主机B两种协议相同的
886
端口号)、本进程的端口号、接收方进程的IP地址与端口号 - 使用C++进行网络编程时会定义套接字(Socket)数据结构来简化上述执行流程,例如主机A的进程1想与主机B的进程5进行通信,就需声明一个TCP套接字(指向通信的另一方的特定进程端口),而无需手动一一指明上述的那些繁杂信息
- 发送方进程需指定使用何种传输层协议(不然无法区分类似上图主机B两种协议相同的
- 端口号由$16$比特编码表示,其范围为$[0,65535]$,一般被分为以下两大类(该分类标准只是建议而非强制)
- 服务端(被动接收通信的一方)使用$[0,49151]$范围内的端口号
- 熟知端口号$[0,1023]$(通常只被用于被熟知的重要应用程序)
- 登记端口号$[1023,49151]$
- 客户端(主动发起通信的一方)使用$[49152,65535]$范围内的端口号
- 服务端(被动接收通信的一方)使用$[0,49151]$范围内的端口号
- 在实际开发时一般不使用熟知端口号,而其它端口号则无严格的使用限制,只要是本机未使用的端口号都可被使用
二、UDP协议
2.1 UDP数据段格式
- 应用层的报文流入传输层时,传输层协议会为其添加协议首部,包含源端口号、目的端口号等信息以实现端到端传输
- UDP协议
- UDP首部较短
- 不支持应用层报文拆分重装(故报文长度受限)
- 不支持拥塞控制
- 支持一对一、一对多传输
- TCP协议
- TCP首部较长
- 支持应用层报文拆分重装
- 支持拥塞控制
- 仅支持一对一传输(因为需建立连接、释放连接)
- UDP协议
- UDP数据段的具体格式如下,主要分为首部和数据两部分
- 首部
- 源端口号
- 目的端口号
- UDP数据段长度
- UDP检验和
- 数据
- 有最大长度限制
- 首部
- 以下是一个$96$字节的报文经UDP的传输示例
2.2 UDP数据段检验
2.3.1 差错检验方法
- 以UDP协议封装的数据段传给接受方后,接收方一般会进行检验,若有差错则直接丢弃且不会通知发送方重传,此环节中采用的差错检验方法不同于链路层,如下图所示
- 该差错检验方法生成检验和的过程中需对原始二进制编码的按位做加法运算,其可以对更多的原始数据进行检验,如下图所示
- 若遇到最高位产生进位的情况,需将进位回卷加到最低位上(对传输进行检验时,将原始数据相加时同样需使用回卷,然后再将结果与检验和相加,得到全
1
才说明无误)
- 对于一个UDP数据段,发送时需使用前述方法为其生成检验和,用于计算检验和的原始编码材料为临时添加的伪首部(仅用于计算检验和,用完即删,不装入IP分组实际传输)、首部中除了检验和的部分、数据部分三个部分
- 而对于接收方来说,其从接收到的UDP数据段中取出首部内的检验和,然后为首部前方添加相同的伪首部用于检验计算,检验无差错则删除伪首部(将该数据段然后按照目的端口号交付给对应进程),有差错则丢弃数据段
2.3.3 对比IP分组检验
- 先前网络层讲到IP分组的首部内有首部检验和部分的存在,其计算方式和UDP检验和的计算方式完全一致,区别在于其用于计算检验和的原材料不同
- IP分组首部检验和不引入伪首部进行计算
- IP分组首部检验和不引入数据部分进行计算,而只使用首部中非检验和的部分编码
三、TCP协议
3.1 TCP传输流程
- 传输层依靠TCP协议传输数据时,需用到TCP段/TCP报文段,主要分为三大阶段
- 建立连接,需收发双方相互传输共三个TCP段(三次握手)
- 传输数据,将应用层报文数据封装为若干TCP段进行传输
- 释放连接,需收发双方相互传输共四个TCP段(四次挥手)
- 建立连接后的数据传输阶段,双方均可传输多个应用层报文
- 每个报文一般会被拆分封装为多个TCP段以便进行传输,每个TCP段的最大长度受到最大段长(MSS, Maximum Segment Size)限制(并不要求每个TCP段都满载,只要不超过最大长度限制即可)
- TCP首部会包含关于顺序的信息,保证可能乱序到达接收方的TCP段能够按照发送时的顺序重组复原,以正确交付给目的进程(而对比之下UDP就是直接将一个完整的应用层报文封装为UDP数据段发送出去,不同UDP数据端的到达顺序也就无所谓了)
3.2 TCP段的格式
3.2.1 收发进程端口的标识
- 源端口($16$比特):发送方进程的端口号
- 目的端口($16$比特):接收方进程的端口号
3.2.2 用于还原报文的标识
seq
序号:标记该TCP段在被拆分前在报文中的原始位置(即封装后的TCP段的数据部分在原始应用层报文中的起始序号,不一定从0
开始,假设是X
,那么下一相邻TCP段的seq
序号在当前基础上偏移Y
字节得到X+Y
,Y
即当前TCP段数据部分的字节长度)ack
确认号:表示接收方希望接收到发送方的下一个TCP段的第一个数据部分字节的序号,同时也相当于向发送方传达,在确认号前的所有字节均已被正确接收(类似累积确认)- ACK位($1$比特):标记当前TCP报文段中的确认号是否有效,除了三次握手中首次握手对应的TCP段ACK位为
0
外,其余TCP段的ACK位均应为1
3.2.3 TCP段相关长度的标识
- 数据偏移($4$比特):取值范围$[0,2^4-1]$,以$4$字节为单位表示TCP首部的长度,故TCP首部总长度须为$4$字节的整数倍(IP首部中包含IP分组总长度、IP首部长度的信息,结合TCP首部长度,即可通过相减得出TCP段数据部分长度,故后者无需特别标识)
- 填充:填充二进制编码在TCP首部末尾,以为了满足上述整数倍长度的要求
3.2.4 除ACK位之外的标志位
- 若干个$1$比特的标志位
- URG位($1$比特):紧急位(Urgent)
- 该位为
1
时表示紧急指针(表示该紧急TCP段对应的紧急序号,这是一套不同于普通TCP段序号的另一套序号,用于拼接紧急报文)有效 - 用于让紧急数据插队传输(如微信传输大文件时,聊天消息就属于应插队于文件传输前的紧急消息,否则等待大文件传输完成后才可发消息就很难受)
- 该位为
- PSH位($1$比特):推送位(Push)
- 该位为
1
时表示希望接收方尽快回复关于该TCP段的信息,用于交互式通信(例如游戏中客户端进程按下技能按键的操作被封装为TCP段发给服务端进程,我们希望尽快得知该技能是否造成伤害,就需要尽快能收到服务端反馈)
- 该位为
- RST位($1$比特):复位位(Reset)
- 该位为
1
时表示出现严重差错或遇到非法TCP报文段,让双方尽快释放连接
- 该位为
- URG位($1$比特):紧急位(Urgent)
- 用于管理连接的标志位
- SYN位($1$比特):同步位(Synchronize)
- 用于请求建立连接
- 只有三次握手中的前两次握手的TCP段的SYN位为
1
,其余TCP段该位均为0
- FIN位($1$比特):终止位(Finish)
- 用于请求释放连接,要求释放一方进程的传输连接
- 只有四次挥手中的第一、三次挥手的TCP段(分别结束了从一方进程到另一方进程的单向通信)的FIN位为
1
,其余TCP段该位均为0
- SYN位($1$比特):同步位(Synchronize)
- 对于任意一个标志位XXX位,若其为值$1$,则其对应的TCP段可被称做XXX段
3.2.5 用于流量控制的字段
- 窗口:用rwnd或rcvwnd表示(来自Receive和Window两单词),表示接接收窗口大小(接收方回传给发送方的TCP段中指出自己还能塞下多少数据,让发送方心里有个数),用于实现流量控制
3.2.6 用于差错检验的字段
- 检验和:被纳入计算的原始编码(包含伪首部、TCP首部、数据部分,共三部分)与计算方法都和UDP的差错检验原理一致,区别在于伪首部的编码构成不同
3.2.7 用于协商MSS的字段
- 选项:用于在建立TCP连接时设置MSS的大小(通常不会太大,以避免TCP报在网络层封装为IP分组时被分片),以便接收方预留合理大小的接收缓冲区
3.3 TCP连接管理
3.3.1 建立连接的流程
- 在TCP协议的全过程中,三次握手用于建立连接,关于三次握手对应的TCP段的各组成部分的取值,需注意以下几点
- SYN位:只有前两次握手的SYN标志位为
1
,表示二者的TCP段仅用于建立连接,而第三次握手目的是发送方告诉接收方自己即将开始发送真正携带数据部分的TCP段 - ACK位:只有后两次握手的ACK标志位为
1
,因为第一次握手发送的TCP段是TCP过程的首个TCP段,还未接收过对方的TCP段,自然不知道对方的seq
序号,自然无法也无需定义ack
序号(但发送方告诉对方的自己的起始序号seq
不能为空) seq
与ack
序号:接收方(可以是下图的客户端,也可以是服务端)收到对方的TCP段后,会根据该TCP段的ack
序号(假设是$x$)及其数据部分的长度(假设是$n$),计算接收方期望收到的下一个TCP段的seq
序号为$x+n$,将该序号值作为自己回复给发送方的握手TCP段的ack
字段值- 例如服务端接收到第一次握手的TCP段的
seq
为666
后,回传响应第二次握手的TCP段的ack
为666+1=667
(前文提到前两次握手的TCP段数据部分为空,那为何此处要加一?因为TCP协议规定它们的数据部分要占用$1$字节的序号) - 同理客户端接收到第二次握手的TCP端的
seq
为50
后,发送第三次握手的TCP段的ack
为50+1=51
,而seq
的667
也符合对方期望
- 例如服务端接收到第一次握手的TCP段的
- TCP段的数据携带:前两次握手的TCP段仅包含TCP首部,数据部分为空,第三次握手的TCP段可选择携带或不携带数据
- TCP段的序号占用:综上所述,第一次和第二次握手的TCP段占用$1$字节的序号,第三次握手的TCP段携带$n$字节数据时占用$n$字节的序号,否则不占用
- SYN位:只有前两次握手的SYN标志位为
- 第三次握手的数据部分可以选择携带或不携带数据
- 不携带数据时该TCP段无需像前两次握手一样必须占用$1$字节的序号,即对方回传的下一个TCP段的
ack
为667
而无需强制添一位到668
- 发送方携带$n$比特数据时,则接收方回传的TCP段的
ack
会在接收的TCP段的seq
基础上正常添加$n$比特序号
- 不携带数据时该TCP段无需像前两次握手一样必须占用$1$字节的序号,即对方回传的下一个TCP段的
- 在上述过程中,发送方与接收方的进程状况的时序表示如下
- 发送方发送第一次握手前,双方都处于未连接状态
- 发送方收到第二次握手时,发送方进入已连接状态
- 接收方收到第三次握手时,接收方进入已连接状态
3.3.2 释放连接的流程
- 在TCP协议的全过程中,四次挥手用于释放连接,关于三次握手对应的TCP段的各组成部分的取值,需注意以下几点
- FIN位:只有在第一次挥手(已建立连接的一方A发送给另一方B)和第三次挥手(B发送给A)时为
1
,分别用于中断B、A的已连接状态,其余所有TCP段的FIN位均为0
- TCP段的数据携带:第二次挥手可以携带数据,第四次挥手不可携带数据(因为第三次挥手后双方均已处于断开连接状态,已经无法进行通信),第一和第三次挥手理论上可以携带数据(但现实中一般不会让其携带)
- TCP段的序号占用:第一和第三次挥手即使不携带数据,也需占用$1$字节的序号(三次握手中SYN为
1
位的、四次握手中FIN位为1
的就会这样),第四次挥手占用为$0$,第二次挥手若携带数据则下一个TCP段正常增加序号即可,若不携带数据则占用为$0$ - SYN位与ACK位:四次挥手均分别为
0
和1
,三次握手时讲过,此处不赘述 seq
与ack
序号:根据TCP段的序号占用,类似三次握手时的分析,此处不赘述
- FIN位:只有在第一次挥手(已建立连接的一方A发送给另一方B)和第三次挥手(B发送给A)时为
- 在上述过程中,发送方与接收方的进程状况的时序表示如下
- 发送方
- 发送第一次挥手前,双方都处于已连接状态
- 发送第一次挥手后,进入终止等待1状态
- 收到第二次挥手后,进入终止等待2状态并立刻发送第四次挥手
- 收到第三次挥手后,等待两倍最长报文段寿命后关闭连接
- 最长报文段寿命(MSL, Maximum Segment Lifetime)是TCP规定的固定时长
- 为何不在收到第三次挥手后立刻关闭?因为若第四次挥手意外丢失,则对方会以为己方未收到第三次挥手而重复发送
- 若接收方重复收到第三次挥手,则需重置等待2MSL的计时器
- 接收方
- 收到第一次挥手后,进入关闭等待状态并立刻发送第二次挥手
- 发送第二次挥手后,在极短时间间隔内发送第三次挥手
- 收到第四次挥手后,立刻关闭连接
3.4 TCP流量控制与可靠传输
3.4.1 缓冲区与窗口
3.4.1.1 窗口大小
- 每个TCP段的
rwnd
字段指明了发送方的接收窗口大小
- 发送方的发送缓冲区和接收方的接收缓冲区大小可能不同,发送方收到对方的
rwnd
即接收窗口大小($\leq$对方的接收缓冲区大小)后,会限制自己的发送窗口大小($\leq$自己的发送缓冲区大小)不能超过对方的rwnd
大小(收发窗口的大小是可以动态变化的)
- 同一主机上的同一进程对应的端口,可通过声明不同的Socket对象分别与不同的其它进程端口建立TCP连接,以达成通过同一端口和不同进程进行数据通信的目的
3.4.1.2 滑动窗口
- 在TCP三次握手规定双方收发窗口后,其数据传输过程中也通过滑动窗口传输TCP段(和链路层传输数据帧的滑动窗口同理),我们作以下假设来模拟滑动窗口过程
- 接收方接收缓冲区$8B$,初始为空,初始接收窗口为$8B$(握手时告知发送方)
- 发送方发送缓冲区$10B$,初始为空,初始发送窗口为$8B$(握手时获知对方窗口)
1
2
3
4
5
6
7
发送方窗口,<>代表窗口范围,[]内表示数据
<[] [] [] [] [] [] [] []> [] []
----
接收方窗口
<[] [] [] [] [] [] [] []>
- 三次握手后的数据发送阶段,发送方向接收方发送单个携带$3B$数据的TCP段,该轮传输所触发的行为流程如下
- 一:接收方接收到发送方发送的单个TCP段后,将数据填入缓冲区
- 通过检验和进行差错检验,无误窗口向右滑动,则回传TCP段
- TCP段的
ack
告知发送方数据无误,并可准备下一趟数据 - TCP段的
rwnd
告知发送方自己接收缓冲区内的所剩窗口大小
- 二:发送方收到回传的ACK段后
- 更新发送缓冲区内的数据,符合对方
ack
序号的期待 - 更新窗口大小,迎合对方
rwnd
的接收窗口大小限制
- 更新发送缓冲区内的数据,符合对方
- 一:接收方接收到发送方发送的单个TCP段后,将数据填入缓冲区
1
2
3
4
5
6
7
发送方窗口,缓存了待发送数据
<[d1] [d2] [d3] [d4] [d5] [d6] [d7] [d8]> [d9] [d10]
--TCP段携带d1~d3数据-->
接收方窗口
<[] [] [] [] [] [] [] []>
1
2
3
4
5
6
7
发送方窗口,丢弃已发送的d1~d3,并根据对方的新窗口大小更新发送窗口
<[d4] [d5] [d6] [d7] [d8]> [d9] [d10] [d11] [d12] [d13]
<--TCP段更新接收窗口为5B--
接收方窗口,滑动到右侧,回传只包含首部的TCP段通过rwnd更新窗口大小为5B
[d1] [d2] [d3] <[] [] [] [] []>
3.4.2 确认机制
3.4.2.1 累积确认机制
1
2
3
4
5
6
7
发送方窗口
<[d4] [d5] [d6] [d7] [d8]> [d9] [d10] [d11] [d12] [d13]
----
接收方窗口
[d1] [d2] [d3] <[] [] [] [] []>
- 发送方在前文基础上继续发送数据,此次快速连续发送两个TCP段,接收方可以只回传一个ACK段进行累积确认,这涉及ACK段的返回时机
- 接收方收到一个TCP段后,最多推迟等待$0.5$秒(推迟确认),期间收到的多个TCP段可以被累积确认
- 若连续收到两个长度为最大段长MSS的报文段,应立即返回ACK而不继续等待(因为太长了,发生错误导致重传的代价太大)
1
2
3
4
5
6
7
8
发送方窗口
<[d4] [d5] [d6] [d7] [d8]> [d9] [d10] [d11] [d12] [d13]
--第一个TCP段携带d4~d5数据-->
--第二个TCP段携带d6~d7数据-->
接收方窗口
[d1] [d2] [d3] <[] [] [] [] []>
1
2
3
4
5
6
7
发送方窗口,丢弃已发送数据,更新发送窗口大小
<[d8]> [d9] [d10] [d11] [d12] [d13] [d14] [d15] [d16] [d17]
<--TCP段累积确认前两个接收到的TCP段,更新窗口为1B--
接收方窗口
[d1] [d2] [d3] [d4] [d5] [d6] [d7] <[]>
3.4.2.2 缓冲区的清理
- 当接收窗口满了时,接收方应尽快交付缓冲区的数据,以便后续的数据接收
1
2
3
4
5
6
7
发送方窗口
<[d8]> [d9] [d10] [d11] [d12] [d13] [d14] [d15] [d16] [d17]
--TCP段携带d8数据-->
接收方窗口
[d1] [d2] [d3] [d4] [d5] [d6] [d7] <[]>
1
2
3
4
5
6
7
发送方窗口,丢弃已发送数据,更新发送窗口大小
<[d9] [d10] [d11] [d12] [d13] [d14] [d15] [d16]> [d17] [d18]
<--TCP段更新窗口为8B--
接收方窗口,收到了d8后交付数据,并清空缓冲区
<[] [] [] [] [] [] [] []>
3.4.2.3 捎带确认机制
1
2
3
4
5
6
7
8
9
10
11
发送方发送窗口
<[d9] [d10] [d11] [d12] [d13] [d14] [d15] [d16]> [d17] [d18]
发送方接收窗口
<[] [] [] [] [] [] [] [] [] []>
----
接收方接收窗口
<[] [] [] [] [] [] [] []>
接收方发送窗口
<[x1] [x2] [x3] [x4] [x5] [x6] [x7] [x8]>
- 在前文基础上
- 发送方再发送一个携带$2B$数据的TCP段
- 接收方回传的TCP段不仅可以起到ACK确认作用,还能携带数据(即捎带确认),此处捎带$5B$数据,这种情况下双方互为发送和接收方(TCP允许双向通信)
1
2
3
4
5
6
7
8
9
10
11
发送方发送窗口
<[d9] [d10] [d11] [d12] [d13] [d14] [d15] [d16]> [d17] [d18]
发送方接收窗口
<[] [] [] [] [] [] [] [] [] []>
--TCP段发送d9~d10数据-->
接收方接收窗口
<[] [] [] [] [] [] [] []>
接收方发送窗口
<[x1] [x2] [x3] [x4] [x5] [x6] [x7] [x8]>
1
2
3
4
5
6
7
8
9
10
11
发送方发送窗口
<[d11] [d12] [d13] [d14] [d15] [d16]> [d17] [d18] [d19] [d20]
发送方接收窗口
<[] [] [] [] [] [] [] [] [] []>
<--TCP段回传更新接收窗口,并捎带x1~x5数据,即捎带确认--
接收方接收窗口
[d9] [d10] <[] [] [] [] [] []>
接收方发送窗口
<[x1] [x2] [x3] [x4] [x5] [x6] [x7] [x8]>
1
2
3
4
5
6
7
8
9
10
11
发送方发送窗口
<[d11] [d12] [d13] [d14] [d15] [d16]> [d17] [d18] [d19] [d20]
发送方接收窗口
<[x1] [x2] [x3] [x4] [x5] [] [] [] [] []>
----
接收方接收窗口
[d9] [d10] <[] [] [] [] [] []>
接收方发送窗口,此时对方刚收到数据,还未回传,所以窗口暂时不变
<[x1] [x2] [x3] [x4] [x5] [x6] [x7] [x8]>
- 上述的流程即下图中的前两条TCP段的传输模拟
- 接下来左侧发送方继续向右侧发送$6B$数据,并捎带确认先前接收的右侧发送的数据
1
2
3
4
5
6
7
8
9
10
11
发送方发送窗口
<[d11] [d12] [d13] [d14] [d15] [d16]> [d17] [d18] [d19] [d20]
发送方接收窗口
<[x1] [x2] [x3] [x4] [x5] [] [] [] [] []>
--TCP段捎带d11~d16数据,并捎带确认先前接收的x1~x5数据-->
接收方接收窗口
[d9] [d10] <[] [] [] [] [] []>
接收方发送窗口
<[x1] [x2] [x3] [x4] [x5] [x6] [x7] [x8]>
1
2
3
4
5
6
7
8
9
10
11
发送方发送窗口
<[d11] [d12] [d13] [d14] [d15] [d16]> [d17] [d18] [d19] [d20]
发送方接收窗口
<[x1] [x2] [x3] [x4] [x5] [] [] [] [] []>
----
接收方接收窗口,已满
[d9] [d10] [d11] [d12] [d13] [d14] [d15] [d16] <>
接收方发送窗口,清除已发送数据,更新发送窗口大小为5B
<[x6] [x7] [x8] [x9] [x10]> [x11] [x12] [x13]
1
2
3
4
5
6
7
8
9
10
11
发送方发送窗口,清除已发送数据,更新发送窗口为8B
<[d17] [d18] [d19] [d20] [d21] [d22] [d23] [d24]> [d25] [d26]
发送方接收窗口
<[x1] [x2] [x3] [x4] [x5] [] [] [] [] []>
<--仅确认并更新窗口,不捎带数据--
接收方接收窗口,交付数据并清空缓冲区
<[] [] [] [] [] [] [] []>
接收方发送窗口
<[x6] [x7] [x8] [x9] [x10]> [x11] [x12] [x13]
- 上述流程对应下图的后续两个TCP段的传输
- 继续给右侧发送数据,即便右侧收到后缓冲区未满,只要数据有序,也可直接交付数据清空缓冲区(所谓数据无序指的是,接收窗口
[][][]
提前收到[][data][]
处的数据,其前方数据仍未到达而暂时空缺,此时就不可直接交付)
3.4.2.4 立即确认机制
- 立即确认机制与前文的推迟确认机制不同,详见后文快重传机制
3.4.3 重传机制
3.4.3.1 超时重传机制
- 以下是保障可靠传输的超时重传机制示意,类似链路层数据帧的超时重传,此处不赘述
3.4.3.2 快重传机制
- 前文使用超时重传机制+推迟确认机制时,能尽可能发挥累积确认的作用,但存在缺点
- 即连续发送多个TCP段时,若中间某个TCP段丢失或出错,那就需要等待包括此TCP段在内的后方所有TCP段都超时后全部重传
- 即使后方TCP段可能已经传输成功而没必要重传,这会导致很浪费时间(类似链路层的后退$N$帧协议的弊端)
- 为此可引入快重传机制+立即确认机制的方式,让可能出错的报文段尽可能早地重传,而不是非要等到超时才重传,避免上述的弊端
- 立刻确认:接收方收到的每个TCP段,都要立刻返回ACK段进行确认,若多个TCP段中途出现一个异常,则其后续所有TCP段回传的ACK段的
ack
序号都相同(ack
序号均指向出现异常的那个TCP段的数据部分首部) - 快重传:发送方连续收到$1+3$个相同的ACK段时(第一个ACK段是正常的,而后面三次则属于冗余ACK段),就立即重发对应的TCP段
- 立刻确认:接收方收到的每个TCP段,都要立刻返回ACK段进行确认,若多个TCP段中途出现一个异常,则其后续所有TCP段回传的ACK段的
3.5 TCP拥塞控制
慢开始算法用于在网络严重拥塞时迅速缩小拥塞窗口;拥塞避免算法用于在网络不拥塞时扩大拥塞窗口;快恢复算法用于在网络轻微拥塞时适当缩小拥塞窗口
3.5.1 区别于流量控制
- TCP流量控制针对两台主机间端到端的局部,目的是使得发送速度匹配接收速度
- TCP拥塞控制针对网络的全局,目的是尽可能降低路由器负载(负载过重会导致丢包)
- 检测到拥塞时应缩小拥塞窗口以减少数据发送量,本质是限制发送窗口大小
- 发送窗口大小由
rwnd
大小(接收窗口,用于流量控制)和cwnd
大小(拥塞窗口,用于拥塞控制)综合控制,所以在后文的讨论中我们认为rwnd
是足够大的(这使得我们只需关注拥塞控制问题,而无需关心流量控制问题) \(发送方发送窗口\leq min(rwnd,cwnd)\)
3.5.2 慢开始与拥塞避免算法
- 在最开始时,拥塞窗口
cwnd
大小使用慢开始算法快速递增- 当
cwnd
的大小超过慢开始门限后转而使用拥塞避免算法缓慢递增 - 在
cwnd
大小为$n$时若发生超时重传(代表严重拥塞),则立刻恢复使用慢开始算法,并将cwnd
置为$1$、将慢开始门限更新为$\frac{n}{2}$,往后依次类推
- 当
3.5.3 快恢复算法
- 快恢复算法基于前文提到的快重传机制,在最开始时
cwnd
同样使用慢开始算法递增- 当
cwnd
的大小超过慢开始门限后转而使用拥塞避免算法缓慢递增 - 在
cwnd
大小为$n$时若发生了快重传(代表轻微拥塞),则将cwnd
大小和新门限的值均更新为$\frac{n}{2}$(适当缩小cwnd
大小,而不是直接将cwnd
降为$1$),二者相等使得立刻进入拥塞避免算法
- 当
本文由作者按照 CC BY-NC-SA 4.0 进行授权