35传输层
5.1 传输层基本概念
传输层传输基本单位为报文段/段,segment
- UDP不允许拆分,整个报文就是一个报文段
- TCP可以拆分,原始TCP报文会拆分成若干个报文段
传输层封装来自应用层的数据
- 应用层的数据可能存在首部也可能不存在首部,由应用程序来决定
- 不论来自应用层的PDU是否包含首部,传输层都会对其进行封装,并增加传输层的首部
传输层主要传输两种数据段:UDP和TCP
- UDP和TCP的首部不相同
5.1.1 端口号
传输层根据端口号来区分数据来自/发往哪个进程

- 两台主机的端口号时相互独立的
- TCP、UDP协议的端口号也是相互独立的,即一台主机中的两个进程可以拥有端口号相同的TCP、UDP服务
- 当两个进程之间想要通信时,需要指明:
- 使用哪种传输层协议,UDP or TCP
- 本进程绑定的端口号
- 对方的IP地址和端口号
- 套接字,Socket:
- 格式为<IP地址:端口号>,是一种数据结构,相当于一个"网络指针"。用于指明网络中一台主机的一个特定进程,该指明唯一
- 同样的,也分为TCP套接字和UDP套接字
- 假设进程1正在用TCP通信,进程2、6正在用TCP通信,进程3、7正在用UDP通信,进程4、8正在用UDP通信
端口号分类
端口号共16bit,分别为0 ~ 65535,主要分为两类:
- 服务器使用的端口号
- 熟知端口号:0 ~ 1023
- 登记端口号:1024 ~ 49151
- 客户端使用的端口号
- 短暂端口号:49152 ~ 65535
0~1023为熟知端口号
- 保留给各种系统服务进程或被熟知的重要应用程序使用,开发使用一般避免使用这些端口号
- 使用这些熟知端口号的程序,一般系统会附带进去
- 其余端口号实际开发时没有特别严格限制,只要本机没有其他京城占用该端口都可以使用
- 端口号分类只是"建议标准",而不是"强制标准"
| 应用程序 | 服务 | 熟知端口号 |
|---|---|---|
| FTP | FTP服务 | 21 |
| TELNET | TELNET服务 | 23 |
| SMTP | SMTP服务 | 25 |
| DNS | DNS服务 | 53 |
| TFTP | TFTP服务 | 69 |
| HTTP | HTTP服务 | 80 |
| SNMP | SNMP服务 | 161 |
5.1.2 传输层功能
传输层主要实现端到端(进程到进程)的通信
复用和分用:
- 复用:(从上到下)
- 在发送数据时,同一台主机的多个进程可以使用同一个传输层协议
- 分用:(从下到上)
- 在接收数据时,传输层可以把数据正确交付给目的进程
差错检测:
- TCP检测出错后会直接丢弃数据,并通知发送方重传
- UDP检测出错后直接丢弃数据,且不通知发送方
向上层应用层提供服务:
- 面向连接的、可靠的端到端传输服务TCP:
- 确保数据正确/完整,但开销大、实时性较差
- 例如,文件传输
- 面向无连接的、不可靠的端到端传输服务UDP:
- 数据可能出错/丢失,但速度快、开销小
- 例如,视频通话,直播
有连接与无连接传输

有连接传输:
- 传输前先打招呼,先确认对方已经准备好接收数据
- 传输结束时也要告知对方已结束
无连接传输:
- 不打招呼,直接把数据传给对方
可靠与不可靠传输

可靠传输:
- 接收方使用“确认机制”让发送方知道哪些数据已被正确接收
不可靠传输:
- 接收方无论收没收到数据、数据是否正确,都不给发送方反馈
5.2 UDP

- UDP每次传输一个完整的报文,不支持报文自动拆分和重装
- 如果传输层接收到来自应用层超出UDP协议长度上限的报文,会直接丢弃该报文
- 如果一定要传输很大的UDP报文,只能在应用程序中自行编写拆分、重装功能代码以实现该功能
- UDP是无连接的、不可靠的,不支持拥塞控制
- UDP虽然是不可靠的,但是可靠性可以交给上层应用层处理
- UDP支持一对一、一对多传输
- 一对一传输,通过封装成单播IP数据报
- 一对多传输,通过封装成广播/多播IP数据报。可用于直播
5.2.1 UDP数据报格式

- UDP首部很小,只占8B
- UDP长度字段占2B,以字节为单位,因此UDP数据部分长度理论最大长度为65535B
- 但是由于UDP封装到IP数据报作为PDU需要同时满足IP数据报的最大数据长度
- IP数据报数据部分理论最大长度为65515B,因此UDP长度最大限制在65515B。UDP数据部分长度限制在65507B
UDP传输示例

5.2.2 UDP检验
校验和:
- 假设要发送的数据为32 bit,将原始数据按照16bit为一组,进行按位相加,结果进行逐位取反,取反16 bit结果作为检验和
- 进行数据传输时,将原始数据和检验和一同发送
- 当接收方收到这48 bit的数据后,也以16bit为一组,按位相加,如果得到的结果为全1,则说明传输无误;反之存在位错
- 回卷:
- 在进行按位相加的计算时,如果最高位发生了进位,则需要将该进位加到结果的最低位
UDP校验过程
发送方:

- 发送方封装完UDP数据报准备向下传递前,先计算校验和
- 计算校验和之前,先在该UDP数据报首部之前添加伪首部信息
- 伪首部共12B,包括各4B的源IP地址、目的IP地址,1字节的0,1字节的无符号数17,2B的UDP长度
- 伪首部的源IP、目的IP地址、UDP长度和UDP数据报的首部信息相同,UDP长度不包括伪首部
- 将伪首部、首部、数据部分按照16bit为一组(不包括首部中的校验和字段),进行二进制加法(最高位进位需要回卷),结果逐位取反得到16bit校验和,填入UDP首部
- 去掉伪首部,将UDP数据报交给下层网络层,封装成IP数据报
接收方:

- 接收方收到层网络层传递过来的UDP数据报,处理UDP数据报前先进行校验
- 校验同样先添加伪首部,从UDP数据报首部中复制信息
- 将伪首部、首部、数据部分按照16bit为一组(其中也包括首部中的校验和字段),进行二进制加法(最高位进位需要回卷)
- 如果加法结果为全1说明没有出错,接收方传输层接收该UDP数据报,并根据目的端口号,向应用层提交报文
- 如果加法签过不全为1说明存在位错,传输层直接丢弃该UDP数据报
IP数据报的首部检验和计算也是和UDP检验和一样,对IP数据报首部以16bit为一组进行
IP数据报首部长度为4B = 32bit,按照16bit为一组的划分完全不存在问题
5.3 TCP

- TCP支持报文自动拆分、重装,类同IP数据报的拆分和重组,因此可以直接传输长报文
- TCP是有连接、可靠的,支持拥塞控制
- TCP仅支持一对一传输,通信双方的传输层必循先建立连接
TCP协议三大阶段:
- 建立连接,通过三个TCP段进行三次握手
- 数据传输
- 释放连接,通过四个TCP段进行四次挥手

- 三次握手过程一定是客户端先向服务器发起,但四次挥手不强制要求客户端发起
- 假如服务器已经传输完所有数据,可以先向客户端发起四次挥手
注:连接了一次TCP连接,可以连续传输多个报文(且支持全双工),不需要传输一个报文建立一次连接
- 假设建立TCP连接时,双方协商MSS=1000B,Maximum Segment Size,最大段长
- TCP并不会强制要求每个TCP段均满载传输MSS大小的数据,只要求不超过MSS长度
- 由于MSS的限制,需要传输很大的TCP报文会也会被拆分成多个TCP报文段进行传输
- 不同于UDP面向报文,TCP是面向字节流的
- TCP可以将原本多个报文的信息放入同一个TCP报文段中,只要不超过MSS即可
- 无论一个TCP报文段中包含多少个报文,在TCP协议看累都是一连串字节流
- TCP拆分后的分段可能乱序到达接收方,接收方的TCP协议会按序交给上层应用层对应的进程
5.3.1 TCP段格式
考研不需要记忆每个字段的位置,考试中会给出示意图

- TCP首部比UDP更大,占20~60B
- 源端口、目的端口字段各占16bit,范围为0 ~ 65535
- 序号字段seq:
- 标记了数据部分第一个字节在原始字节流中的位置,即拆分前在字节流的位置;重组过程利用该字段还原字节流的先后顺序
- 一个TCP连接建立后进行数据传输,"起始序号"是发送方自己设置的,不一定从0开始
- ACK字段和确认号相互关联,ACK占1bit,确认号占32 bit
- 确认号记作ack或ack_seq,主要用于反馈,表示序号在该确认好之前的所有字节都已正确收到
- 当ACK = 0时,ack_seq无效;当ACK = 1时,ack_seq有效
- 只有建立连接时客户端向服务端发起的第一次"握手"TCP报文段的ACK = 0,其余包括后面几次握手,传输阶段,四次挥手的TCP报文段的ACK = 1
- ack_seq也同样具有累计确认的特性

- 数据偏移字段,Data Offset:
- 占用4bit,表示TCP报文段首部的长度,类同IP数据报,以4B为单位
- TCP首部中不会专门记录TCP报文段数据部分长度,也不会记录TCP报文段的总长度
- IP数据报中记录了IP数据报的首部长度和总长度,两者相减可以得到IP数据报数据部分的长度,即TCP报文段的总长度
- TCP报文段首部长度已知,两者相减,从而可得到TCP报文段的数据长度
- 保留字段目前无实际含义,占用6bit,通常全部置0
- 填充字段长度可变,主要作用是为了让整个TCP首部长度凑足4B的整数倍

这几个字段相对没这么重要
- URG字段,紧急位:
- 占1bit,当URG = 1时,紧急指针有效。此时表示该TCP数据段为紧急数据,应尽快插队发送
- 紧急指针:
- 紧急数据专用序号,和上面的序号字段功能类似
- PSH字段:
- 占1bit,当PSH = 1时,表示希望接收方尽快回复,主要用于交互式通信
- RST字段:
- 占1bit,当RST = 1时,表示出现严重差错,必须释放连接(如,主机崩溃)
- 也可用于拒绝一个非法报文段(如,恶意黑客的攻击)

- SYN字段,同步位:
- 占1bit,当SYN = 1时,表示这是一个连接起高球或连接接收报文
- 只有建立连接时第一次、第二次"握手"TCP报文段的SIN = 1,其余包括后面的握手,传输阶段,四次挥手的TCP报文段的SIN = 0
- FIN字段,终止位:
- 占1bit,当FIN = 1时,表示此报文段的发送方的数据已发送完毕,要求释放传输连接
- 只有建立连接时第一次"握手"TCP报文段,以及释放连接时第三次"挥手"TCP报文段的FIN = 1,其余包括后面几次握手,传输阶段,四次挥手的TCP报文段的FIN = 0

- 窗口字段:
- 占16bit,表示接收窗口的大小,记作rwnd或rcvwnd
- 表示从本报文段首部的ack_seq算起,接收方还能接收多少数据,以字节为单位
- 该字段用于实现流量控制
- 校验和:
- 原理和UDP校验和类同,占16bit,计算校验和之前也需要添加12B伪首部
- 与UDP不同的是,协议字段的17改为6,长度字段改为TCP段长度。注意,TCP段长度之前提到没有字段指明,需要另外通过IP数据报首部长度和总长度计算

- 选项字段:
- 该字段可为空, 也可非空
- 建立TCP连接时,在TCP连接第一次、第二次"握手"TCP段,选项中协商MSS
- MSS的值表示在接下来的数据传输中,一个TCP报文段最多携带多少数据(不包含首部,只考虑数据部分)
- 通常MSS不会设置太大,以免在网络层被分片,通常为1KB
5.3.2 TCP连接管理
只有第一次握手ACK = 0,其余报文段ACK = 1
只有第一次握手、第二次握手SYN = 1,其余报文段SYN = 0
只有第一次挥手、第三次挥手FIN = 1,其余报文段FIN = 0
建立连接:三次握手
建立连接阶段SYN、ACK、seq、ack变化:

- 只有握手1、握手2的SYN = 1
- 只有握手1的ACK = 0
- 握手1、握手2为SYN数据段,不允许携带数据,只包含TCP首部,但是需要消耗一个序号
- 因此客户端向服务器发送数据从seq=667开始
- 握手3数据段,客户端可以向服务端发送数据,也可以不发送数据

建立连接阶段TCP状态变化:

- 客户端向服务器发送请求前,TCP进程端口处于关闭,TCP处于
CLOSE关闭状态 - 接收方在接收到客户端发来的TCP报文段前可能关闭了TCP服务器端口,此时服务器TCP也处于
CLOSE关闭状态 - 客户端发出握手1时打开TCP客户端端口,客户端TCP状态转变为
SYS-SENT同步已发送状态 - 为了保证能接收到客户端建立连接,需要打开TCP服务器端口,将服务器TCP转为
LISTEN监听状态,等待客户端建立连接- 如果服务器TCP处于
CLOSE关闭状态,任何客户端发来的建立连接请求,其目的端口在服务器上找不到相关进程,自然会直接丢弃该TCP报文段
- 如果服务器TCP处于
- 处于监听状态的服务器收到来自客户端发来的握手1后,向客户端发出握手2,TCP状态变为
SYS-PCVD同步已收到状态 - 客户端收到来自服务器的握手2后,向服务器发出握手3,TCP状态改为
ESTABLISHED已建立连接状态 - 服务器收到来自客户端的握手3后,TCP状态也变为
ESTABLISHED已建立连接状态,两者开始相互传输数据
建立连接阶段耗时分析:
- 从发出握手1,到客户端进程可以发送数据,即从发出握手1到发送握手3之前,至少需要1RTT时间
- 从发出握手1,到服务器进程可以发送数据,即从发出握手1到收到握手3,至少需要1.5RTT时间
释放连接:四次挥手
释放连接阶段SYN、ACK、seq、ack变化:

- 只有挥手1、挥手3的FIN = 1
- 挥手1、挥手3为FIN段,允许携带数据,但是现实场景中并不携带数据
- 但是尽管不携带数据,依旧要消耗一个序号
- 挥手2可以携带数据
- 由于挥手3顺利接收后,客户端和服务器之间的连接已经断开,因此挥手4不可以携带数据
释放连接阶段TCP状态变化:

- 释放连接前,客户端和服务器TCP进程端口肯定处于打开状态,TCP状态均处于
ESTABLISHED已建立连接状态 - 此时可由客户端发起释放连接,也可由服务器发起释放连接,发送挥手1的一方TCP进入
FIN-WAIT1终止等待1状态,后续步骤以客户端发起为例 - 服务器收到挥手1后,向客户端发送挥手2,TCP并进入
CLOSE-WAIT关闭等待状态,挥手2中可以携带最后需要发送的数据 - 客户端收到挥手2后,TCP进入
FIN-WAIT2终止等待2状态 CLOSE-WAIT关闭等待状态主要携带在挥手2中最后数据的传输,传输结束后,再向客户端再次发送挥手3,TCP进入LAST-ACK最后确认状态- 客户端收到挥手3后,向服务器发送挥手4,TCP进入
TIME-WAIT时间等待状态,并启动计时器,等待2MSL延时后关闭TCP客户端端口,TCP进入CLOSE连接关闭状态
释放连接阶段耗时分析(基于客户端发出释放连接):

- 最长报文段寿命MSL,Maximum Segment Lifetime:
- 由TCP协议规定的一个固定的时间长度
- 客户进程收到挥手3后,立即进入
TIME-WAIT时间等待状态,并启动TIME-WAIT计时器- 由于服务器不一定能顺利收到挥手4,服务器发出挥手3后没有收到来自客户端的确认会重发挥手3
- 该重传只会进行一次,如果该FIN报文段依旧没有收到ACK,则服务器就直接放弃并关闭连接
- 如果在计时器倒计时之内如果再次收到服务器发来的挥手3,就重置计时器
- 由于服务器不一定能顺利收到挥手4,服务器发出挥手3后没有收到来自客户端的确认会重发挥手3
- 如果服务器收到挥手1时,已经没有后续需要传送的数据,那么可以连续发出挥手2、挥手3
- 那么服务器TCP处于
TIME-WAIT时间等待状态的时间极短暂,接近为0 - 同理客户端TCP处于
FIN-WAIT1终止等待2状态的时间也极短暂,接近为0 - 那么从客户端发出挥手1到客户端进入
CLOSE状态至少需要1RTT + 2MSL - 从客户端发出挥手1到服务器进入
CLOSE状态,至少需要1.5RTT
- 那么服务器TCP处于
5.3.3 TCP可靠传输与流量控制
序号:
- 进程在建立连接时确定起始序号,数据传输过程中,每个字节对应一个序号
确认机制:
- 累计确认规则:
- 如果收到ack_seq = n,说明序号在n之前的所有字节都已正确接收
- 返回ACK的时机:推迟确认
- TCP标准规定,推迟时间最多不能超过0.5s
- 如果回复ACK的一方也有数据要传送给对方,立即返回ACK段,并携带需要传输的数据
- 若连续收到两个长度为MSS的报文段,就应该立即返回ACK段
- 在推迟确认过程中,如果连续收到两个长度为MSS的报文段,说明原报文分段前很大,检验出错重传代价很大,因此需要立即确认
- 两种ACK段
- 一般确认:一个ACK只有TCP首部,没有携带数据
- 捎带确认:一个ACK段顺道携带了数据
重传机制:
- 超时重传:
- 每发送一个报文段,就设置一个计时器
- 若计时器到期还没收到确认,就重传这个报文段并重置计时器
- 快重传/冗余ACK
- 让可能出错的报文段尽早重传,而不是非要等到超时再重传
- 立即确认:
- 与快重传配套使用的机制。每收到一个TCP报文段就立即返回一个ACK段
- 即使收到一个失序报文段,也要立即返回ACK段
- 失序报文段会导致发送方收到冗余的ACK
- 当发送方连续收到三个确认号相同的冗余ACK时,就立即重传该确认号对应的报文段
- 与快重传配套使用的机制。每收到一个TCP报文段就立即返回一个ACK段
流量控制:即滑动窗口机制
- 接收方维持一个接收窗口,记为rcvwnd或rwnd
- 发送方维持一个发送窗口
5.3.4 TCP传输底层原理
Socket套接字对象:
- 套接字对象是一个数据结构,其中包含本机进程端口号,以及接收方的套接字
<ip:端口>,其中的内容需要应用程序自行填入 - 套接字对象中除了本机以及目的套接字外,还存放发送缓冲区和接收缓冲区
- 发送缓、接收冲区本质上是,操作系统为本次TCP连接,所分配的char[]数组
- 该缓冲区长度可由操作系统默认分配或者应用程序在系统调用socket()时指明(但操作系统有最大最小限制)
- 套接字对象创建后,也可通过系统调用setsockopt()对缓冲区大小进行调整
- 发送缓冲区和接收缓冲区大小可以不等长
- 发送/接收窗口大小不得超过发送/接收缓冲区大小,发送窗口大小也不得超过对方接收窗口大小
系统调用send()工作过程:
- 应用程序进行系统调用send()时,需要指明套接字对象、用户区中发送数据的数组名(指针)以及长度
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
- 调用send(),会将用户区的数据复制到对应套接字的发送缓冲区
系统调用recv()工作过程:
- 应用程序进行系统调用recv()时,也需要指明套接字对象、用户区存储接收数据的数组名(指针)以及长度
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
- 调用recv(),会将套接字对象的接收缓冲区的数据复制到用户区中
假设握手1为seq=599;握手2为ack=600、seq=199、rwnd=8;握手3为ack=200、seq=600、rwnd=10且不携带数据
- TCP连接发起方:
- 用户主机待发送数据为100B,设发送char[]数组序号为0 ~ 99,则对应用户端发送数据序号601 ~ 699
- 由于接收方的rwnd=8,因此用户主机发送窗口大小最大也为8
- TCP三次握手后,第一次发送过程,最大可发送 0 ~ 7序号,即接收方顺利收到整个发送窗口的数据后,ack = 608
-
- TCP协议会将0 ~ 7序号的数据封装成TCP报文段,在传输层进行传输
- TCP连接接收方:
- 服务器主机发送数据为50B,设发送char[]数组序号为0 ~ 49,则对应用户端发送数据序号201 ~ 250
- 由于接收方的rwnd=10,小于服务器主机发送窗口大小,服务器主机可以直接按最大发送窗口8发送
- TCP三次握手后,第一次发送过程,最大可发送 0 ~ 7序号,即接收方顺利收到整个发送窗口的数据后,ack = 208
- TCP协议会将0 ~ 7序号的数据封装成TCP报文段,在传输层进行传输
补充:

- 同一台主机中的一个进程可使用一个端口建立多个TCP连接
- 不同的TCP连接使用不同的套接字对象,每个TCP连接仅支持一对一全双工通信
TCP连接建立

- 客户主机166.1.1.1与服务器主机233.0.0.1进行TCP通信,数据在应用层进行分析处理
- 客户进程为进程A,端口号为1111;服务器进程为进程B,端口号为996
- 处理这些数据的应用程序以及所需资源位于操作系统内存的用户区,而TCP协议所使用的资源位于操作系统主存的内核区
- 假设进程A要向进程B发送100B数据,进程B要向进程A发送50B数据
- 发送的数据本质上是长度为100B的char[]数组,数组内为需要发送的数据(或者直接理解为连续输入的字节流)
- 接收的一方在应用层需要预留一段空间用于分析这些接收数据
- 本质上也是一个char[]数组,数组长度由用户自行决定,如果长度不足需要进行动态扩容
- 应用层准备好的数据交给传输层,需要进行系统调用socket()申请一个Socket套接字对象
- 由于用户区无法直接访问内核区的资源,必须通过系统调用才能访问
- 应用层准备好套接字对象后,进行系统调用send(),让操作系统的TCP协议根据套接字对象内的信息,对目标进程发起TCP连接,即发送握手1
- 接收方接收握手1后,操作系统也会生成一个套接字对象,包含发送方的套接字以及本机进程端口号信息
- 后续的TCP报文段交换过程,就通过这两个套接字对象进行
TCP通信过程——调整接收窗口大小

- 握手3过程并不携带数据,因此TCP建立阶段未传输任何数据
- 客户端向服务器发送TCP报文段ack=200,seq=600,rwnd=10,数据长度为3B
- 客户端应用程序此时进行了系统调用send(),先从用户区将复制3B到发送缓冲区,发送缓冲区再取出交给TCP协议封装为TCP报文段进行发送
- 客户端发送仅发送了3B,发送缓冲区还剩余7B,等待服务器发来ACK
- 服务器收到3B数据后,如果校验通过,更新接收窗口大小为5,返回ACK,ack = 603,不携带数据
- 传输过程中seq不会像建立连接阶段额外占用一个seq序号
- 服务器执行应用程序此时进行了系统调用recv(),从接收缓冲区取出3B封装上交给应用层
- 服务器接收窗口=接收缓冲区大小=8B,接收到3B,剩余5B,因此要将接收窗口最大调整为5B
- 如果服务器应用程序未进行系统调用recv(),接收缓冲区内的数据会一直占用,直至释放连接
- 客户端收到服务器的ACK,ack = 603,如果校验通过,将发送缓冲区的确认接收的数据移出,即移动滑动窗口(实际上是调整缓冲区指针)
- 如果客户端一直未收到ACK,且此时发送缓冲区已满,应用程序进行系统调用send(),会引起报错或阻塞,防止发送缓冲区溢出
- 客户端收到ACK确认后,会根据收到的rwnd调整自己的发送窗口大小
- 客户端应用程序进行系统调用send(),无论发送缓冲区还剩多少空间,是否已满,都不会修改客户端发送窗口大小
- 假如由于发送窗口大小限制,客户端应用程序进行系统调用send()所要求发送的数据未能一次性封装成一个TCP报文发送
- 假设收到ACK后发送窗口大小扩大,剩余的部分可和下一次进行系统调用send()一同封装成TCP报文段发送
- 如果客户端后续未进行新的系统调用send(),TCP协议会自动尝试发送剩余的部分,该过程不需要再执行新的系统调用send()来干预
TCP通信过程——接收方累计确认

- 客户端向服务器连续发送两个TCP报文段,长度分别为2B,seq分别为603、605
- 客户端发送缓冲区还剩6B,等待服务器发来ACK
- 服务器收到多个TCP报文后,如果校验通过,只返回一个ACK,ack = 607,且不携带数据,并更新rwnd = 1
- 服务器收到4B的数据,接收缓冲区剩余4B,接收窗口最大调整为4B
- 累计确认,只返回最后的seq的ACK
- 客户端收到服务器的ACK后,如果校验通过,根据rwnd调整发送窗口大小,只发送1B数据
- 客户端收到ACK,发送缓冲区释放已确认的空间,发送缓冲区剩余9B,等待服务器发送ACK
- 服务器再次收到TCP报文,如果校验通过,返回ACK,且不携带数据,由于缓冲区清空,更新rwnd = 8
- 服务器需要尽快将数据按需交付给应用层,释放缓冲区空间,以扩大接收窗口大小
TCP通信过程——接收方捎带确认

- 客户端向服务器发送包含数据2B的TCP报文段,seq=608
- 客户端发送缓冲区剩余8B,等待服务器发来ACK
- 服务器收到2B数据后,如果校验通过,返回ACK,ack = 610,且捎带5B的数据,更新rwnd = 6
- 如果服务器也有数据要发送,则收到一个TCP报文则立即回复ACK,并在该ACK报文中携带需要发送
- 服务器接收方缓冲区剩余6B,调整接收窗口rwnd = 6
- 客户端收到ACK后,如果检验通过,则顺带也接受了来自服务器发送的的5B数据,返回ACK,ack = 205,更新rwnd = 5
- 和服务器一样,进行捎带确认,收到TCP报文立即回复
- 只要双方都还有数据要发送,那么捎带确认就是一直轮流下去,直至收到一个不带数据的ACK
- 客户端、服务器如果没有按序收到数据,那么接收窗口内收到的乱序数据不能被交付到应用层
- 接收缓冲区的数据通常通过系统调用recv()接收,一般不会自动交付给应用层,以保证数据按序交付
TCP通信过程——超时重传机制
客户端发送的TCP报文段丢失

- 客户端发送TCP报文后,会设置一个超时重传计时器,如果在限定时间内未收到ACK会进行重传
- 重传过程,会将之前发送缓冲区未确认的数据再次封装成一个TCP报文,再次发送,ack,seq保持不变,并重置计时器
- 客户端如果在限定时间收到了ACK,则将接受的数据移出发送缓冲区,进行后续的发送
服务器返回的ACK丢失

- 客户端发送TCP报文后,设置一个超时重传计时器
- 服务器收到TCP报文,检验通过,发送ACK,但是传输过程中发生了丢失
- 计时器超时,客户端进行重传
- 服务器收到TCP报文,检验通过,但是发现该TCP报文已经接收,直接丢弃,并立即返回ACK
- 如果这次客户端顺利收到ACK,则确认发送;反之,放弃该数据的发送
TCP通信过程——快重传机制、立即确认机制

- 由于之前的延迟确认机制,在该期间内可能连续收到多个TCP报文段,但是其中有报文丢失,假设第二个报文丢失
- 服务器只能返回按序接收的第一个报文,其余报文虽然不是按序接收,但是保留在接收窗口内
- 接收窗口需要相应的调整,并保留出已接收的空间
- 对于用户端,由于第二个报文以及后续报文均超时,需要进行重传
- 除第二个报文外,实际其余报文都已顺利接收,但是延迟确认机制导致其必须重发
- 引入快重传机制,每接收一个TCP报文就回复一个ACK
- 由于第二个报文丢失,客户端会收到三个相同的ack的冗余ACK,此时客户端应立即重发ack对应的报文段,以避免超时等待继续无效重传
在实际应用场景中,由于TCP报文段长度远大于不携带数据的ACK报文段长度,无效重传造成的浪费远大于冗余ACK的影响
5.3.5 TCP拥塞控制
拥塞控制算法:
- 慢开始算法:
- 适用于严重拥塞场景
- cwnd拥塞窗口大小值从1开始,没收到一个ACK就让cwnd + 1
- 拥塞避免算法:
- 在未拥塞状态,提前动态调整cwnd拥塞窗口大小
- 在一个RTT内,及时收到多个ACK,也只能让cwnd + 1
- 快恢复算法:
- 对于立即确认、快重传机制,发生快重传现象时
- 一旦发生快重传,将阈值、cwnd都设置为当前cwnd的一般,然后切换到拥塞避免算法
拥塞控制与流量控制对比:
- 拥塞控制:
- 控制端到端的数据发送量,是对局部网络拥塞情况的调整
- 流量控制:
- 控制整个网络中每台主机的数据发送量,降低路由器负载,是对全局网络拥塞情况的调整
网络中的拥塞状况反映在发出的报文未能按时收到ACK,即丢包现象
- 网络不拥塞:
- 发出的每个报文段都能顺利收到ACK
- 网络有点拥塞:
- 如果使用立即重传机制,发送方收到冗余ACK引发快重传
- 网络严重拥塞:
- 如果发出的报文未能按时收到ACK,引发超时重传
当网络发生拥塞时,需要限制网络中各台主机的数据发送量
- 主机发送窗口大小上限 = min
通常考研题目,会围绕以下特点:
- 通常只涉及单项传输,即TCP连接双方只有一方发送数据
- 通常默认每个TCP报文段都已最大段长MSS满载数据
- 拥塞窗口的大小常以MSS的倍数作为单位
- 接收方受到报文段,会立即确认;如果受到冗余ACK,会进行快重传
- 一般假设接收方接收窗口足够大,发送窗口大小只受拥塞窗口限制
慢开始算法、拥塞避免算法

- 横轴为传输轮次,即时间,以RTT为单位;纵轴表示拥塞窗口大小cwnd,以MSS为单位
- 慢开始门限ssthresh:
在考研题目中一般称作拥塞控制阈值
- 当实际的拥塞窗口大小小于ssthresh时,拥塞窗口大小增长幅度快;当拥塞窗口大于等于ssthresh时,拥塞窗口增长幅度慢
- 即拥塞窗口大小增长速度变化分界线
拥塞窗口变化过程:

- cwnd初值从1开始,ssthresh初值为16,因此一开始只能发送一个MSS长度的TCP报文段
- cwnd小于ssthresh时,使用慢开始算法,每收到一个ACK,拥塞窗口+1,那么相应的发送窗口大小也+1
- 一开始只收到一个ACK,因此加1;后续由于发送窗口的增大,由于立即确认机制,每次收到的ACK都加倍,也导致发送窗口大小也加倍
- 当拥塞窗口大小达到ssthresh后,切换为拥塞避免算法,每个RTT/每轮接收内,即使收到多个ACK,至多让cwnd+1
- 拥塞避免算法,持续增长拥塞窗口大小,直到在某RTT/某轮次,出现了超时重传,说明网络中已经存在严重拥塞现象,需要迅速缩小拥塞窗口
- 出现严重拥塞现象,立即切换为慢开始算法,cwnd回到1,同时此时调整阈值,ssthresh = 拥塞时cwnd / 2
- 每次发生严重拥塞就减半,但是ssthresh不能小于2
- 由于出现的是超时重传,即使用延迟确认,一旦出现严重拥塞就将cwnd变为1
- 如果慢开始算法,让拥塞窗口进行倍增后超过ssthresh阈值时,本轮次拥塞窗口只能增长到ssthresh阈值,并切换为拥塞避免算法
快恢复算法

- 横轴为传输轮次,即时间,以RTT为单位;纵轴表示拥塞窗口大小cwnd,以MSS为单位
拥塞窗口变化过程:

- 在发生严重拥塞前,增长过程类延迟确认
- 发生拥塞后,出现了冗余ACK,说明网络中有点拥塞
- 立即切换为拥塞退避算法,ssthresh = cwnd = 拥塞时cwnd / 2
- 那么后续都一旦发生冗余ACK,都会将ssthresh、cwnd减半,但是ssthresh不会小于2,不会再采用慢开始算法
- 在TCP早期版本,Tahoe版本中,当出现了冗余ACK时,会切换为慢开始算法,类同延迟确认过程,目前已启用
- 现在新版TCP,发生冗余ACK,一律切换为拥塞退避
本文来自博客园,作者:GK_Jerry,转载请注明原文链接:https://www.cnblogs.com/GKJerry/articles/18524271

浙公网安备 33010602011771号