传输层协议
以下部分图片节选自小林coding,有侵权问题必删
传输层概述
传输层的两个重要协议
TCP和UDP
TCP:
- 面向连接
- 可靠连接
- 基于字节流
- 仅支持单播
UDP:
- 无连接
- 面向报文
- 支持单薄、广播、多播
- 不可靠
传输层复用、分用和端口
用户数据报协议(User Datagram Protocol, UDP)
UDP概述
UDP格式
可靠传输原理
停止等待协议
连续ARQ协议
滑动窗口
回退N步(GBN)协议
对比停等协议和滑动窗口协议的基本概念,不难发现,停等协议实质上是发送窗口长度为1,接收窗口长度也为1的滑动窗口协议
GBN协议是发送窗口长度大于1,接收窗口长度等于1的滑动窗口协议
发送方行为:
接收方行为:
GNB的信道利用率:
- 观察GBN协议的运行过程,可以发现流水线方式的传输使信道中不断有数据在传送,确实可以提高信道利用率
- 但由于接收窗口仅为1,造成丢失或差错的PDU之后到达的所有PDU均被发送方重传,即使这些失序到达的PDU都是正确的。这种处理方式造成了信道资源的浪费
- 从发送方角度来看,一旦发生超时重传事件,则需要回退N步,从超时的PDU开始重新发送所有后续PDU
选择重传(SR)协议
发送方行为:
接收方行为:
此外,还有否定应答NAK:
- 选择重传SR协议可以跟否定策略结合在一起使用,即当接收方检测到错误的PDU时,它就发送一个否定应答(Negative Acknowledgement,NAK)
- 在发送方,收到NAK可以触发该PDU的重传操作,而不需要等到对应的超时计时器超时,因此可以提高协议性能
传输控制协议(TCP)
TCP概述
- TCP连接是逻辑连接,TCP把连接作为最基本的抽象。
- TCP连接的端点称为套接字(socket)
- RFC793中定义套接字由端口号拼接到IP地址构成: 套接字=(𝐈𝐏地址:端口号)
- 每一条TCP连接有且仅有两个端点,每一条TCP连接唯一地被通信两端的两个套接字确定
- TCP连接两端的主机需要维护TCP连接状态
- 一旦建立连接,主机中的TCP进程将设置并维护发送缓存和接收缓存
TCP报文格式
紧急指针:
当URG标志位置1时才有效,因为只有一个紧急指针,这也意味着它只能标识一个字节的数据。这个指针指向了紧急数据最后一个字节的下一个字节
我们知道 TCP 在传输数据时是有顺序的,它有字节号,URG 配合紧急指针,就可以找到紧急数据的字节号。紧急数据的字节号公式如下:
紧急指针的作用:
一旦 TCP 知道了你要发送紧急数据,那么在接下来的数据发送中,TCP会将所有的TCP报文段中的URG标志置位,哪怕该报文段中不包含紧急数据,这个行为会持续到紧急数据被发送出去为止
一些坑:
如果发送方多次发送紧急数据,最后一个数据的紧急指针会将前面的覆盖。比方说你发送了一个字节的紧急数据 ‘X’,在 ‘X’ 尚未被 TCP 发送前,你又发送了一个紧急数据 ‘Y’,那么在后面的 TCP 报文中,紧急指针都是指向了 ‘Y’ 的
很多系统的实现,包括 Linux 将紧急数据称之为带外数据(out-of-band data, OOB),意为在连接之外传送的数据,实际上这是不对的(《TCP/IP 详解》一书称此不正确的)。即使是紧急数据,仍然会随着普通数据流一起发送,并不会单独为紧急数据开辟一条新的连接通道单独发送
选项字段:
MSS和MTU
TCP面向字节流
TCP三次握手
为什么TCP建立连接不能2次握手?
为了避免历史连接:防止旧的重复连接初始化造成混乱。比如在网络拥堵的情况下连续发了2个SYN报文(旧的报文SYN是90,新的是100)
- 一个「旧 SYN 报文」比「最新的 SYN」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是 91(90+1)。
- 客户端收到后,发现自己期望收到的确认号应该是 100 + 1,而不是 90 + 1,于是就会回 RST 报文
- 服务端收到 RST 报文后,就会释放连接。 后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了
在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费
TCP四次挥手
注意客户端在进入TIME_WAIT状态后,需要等2MSL才能close
- 为什么需要等待2MSL?
MSL,即 Maximum Segment Lifetime,一个数据分片(报文)在网络中能够生存的最长时间,在RFC 793中定义MSL通常为2分钟,即超过两分钟即认为这个报文已经在网络中被丢弃了。可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时
- 为什么需要 TIME_WAIT 状态
- 防止历史连接中的数据,被后面相同四元组的连接错误的接收
- 保证被动关闭连接的一方,能被正确的关闭
TCP可靠传输
TCP的可靠传输协议是以字节为单位的滑动窗口协议
TCP可靠传输的特点:
- TCP窗口内的序号不是以PDU为单位编号,而是以字节为单位编号
- TCP的发送窗口和接收窗口均大于1
- TCP的发送窗口和接收窗口长度不是固定的,而是动态变化的
- TCP支持多种重传机制:超时重传、快重传和SACK重传
重传机制
超时重传
超时重传发生的情况:
- 数据包丢失
- 确认应答丢失
超时重传时间 RTO 的值应该略大于报文往返 RTT 的值
快速重传
不以时间为驱动,而是以数据驱动重传
连续收到三个相同的ACK序列号说明该Seq丢失
SACK
超时重传有一个缺点:就是重传的时候,是重传一个,还是重传所有的
SACK( Selective Acknowledgment), 选择性确认
这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据
Duplicate SACK
主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了
好处:
- 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
- 可以知道是不是「发送方」的数据包被网络延迟了
- 可以知道网络中是不是把「发送方」的数据包给复制了
滑动窗口
流量控制
拥塞控制
慢启动
- 拥塞窗口cwnd,以MSS的个数做为cwnd的单位
- 慢启动门限ssthresh
- 初始化时:cwnd为1个MSS, ssthresh为65535个字节
- 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据
- 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个
- 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个
- 慢启动阶段cwnd是以指数的形式增长的,直到拥塞窗口大小大于门限值为止
拥塞避免
当cwnd到达ssthresh,一般来说 ssthresh 的大小是 65535 字节
- 每当收到一个 ACK 时,cwnd 增加 1/cwnd
- 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长
拥塞发生
当网络出现拥塞,也就是会发生数据包重传,会启用拥塞发生算法
在超时重传和快速重传两种重传机制下,拥塞发生算法不同
超时重传:
- ssthresh 设为 cwnd/2
- cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)
快速恢复:
快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈
- cwnd = cwnd/2 ,也就是设置为原来的一半;
- ssthresh = cwnd
- cwnd = ssthresh + 3
- 重传丢失的数据包
- 如果再收到重复的 ACK,那么 cwnd 增加 1
- 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态
为什么收到新的数据后,cwnd 设置回了 ssthresh ?
- 首先,快速恢复是拥塞发生后慢启动的优化,其首要目的仍然是降低 cwnd 来减缓拥塞,所以必然会出现 cwnd 从大到小的改变
- 其次cwnd+1的存在是为了尽快将丢失的数据包发给目标,从而解决阻塞的根本(三次相同的ACK会重传),所以这一过程cwnd是增大的