以下部分图片节选自小林coding,有侵权问题必删

传输层概述

img

img

传输层的两个重要协议

TCP和UDP

TCP:

  • 面向连接
  • 可靠连接
  • 基于字节流
  • 仅支持单播

UDP:

  • 无连接
  • 面向报文
  • 支持单薄、广播、多播
  • 不可靠

传输层复用、分用和端口

img

img

img

用户数据报协议(User Datagram Protocol, UDP)

UDP概述

img

img

UDP格式

img

img

img

img

img

img

img

img

img

img

可靠传输原理

停止等待协议

img

img

img

img

连续ARQ协议

滑动窗口

img

img

img

img

img

回退N步(GBN)协议

对比停等协议和滑动窗口协议的基本概念,不难发现,停等协议实质上是发送窗口长度为1,接收窗口长度也为1的滑动窗口协议

GBN协议是发送窗口长度大于1,接收窗口长度等于1的滑动窗口协议

img

发送方行为:

img

接收方行为:

img

GNB的信道利用率:

  • 观察GBN协议的运行过程,可以发现流水线方式的传输使信道中不断有数据在传送,确实可以提高信道利用率
  • 但由于接收窗口仅为1,造成丢失或差错的PDU之后到达的所有PDU均被发送方重传,即使这些失序到达的PDU都是正确的。这种处理方式造成了信道资源的浪费
  • 从发送方角度来看,一旦发生超时重传事件,则需要回退N步,从超时的PDU开始重新发送所有后续PDU

选择重传(SR)协议

img

img

发送方行为:

img

接收方行为:

img

此外,还有否定应答NAK:

  • 选择重传SR协议可以跟否定策略结合在一起使用,即当接收方检测到错误的PDU时,它就发送一个否定应答(Negative Acknowledgement,NAK)
  • 在发送方,收到NAK可以触发该PDU的重传操作,而不需要等到对应的超时计时器超时,因此可以提高协议性能

传输控制协议(TCP)

TCP概述

img

  • TCP连接是逻辑连接,TCP把连接作为最基本的抽象。
  • TCP连接的端点称为套接字(socket)
  • RFC793中定义套接字由端口号拼接到IP地址构成: 套接字=(𝐈𝐏地址:端口号)
  • 每一条TCP连接有且仅有两个端点,每一条TCP连接唯一地被通信两端的两个套接字确定
  • TCP连接两端的主机需要维护TCP连接状态
  • 一旦建立连接,主机中的TCP进程将设置并维护发送缓存和接收缓存

TCP报文格式

img

紧急指针:

当URG标志位置1时才有效,因为只有一个紧急指针,这也意味着它只能标识一个字节的数据。这个指针指向了紧急数据最后一个字节的下一个字节

img

我们知道 TCP 在传输数据时是有顺序的,它有字节号,URG 配合紧急指针,就可以找到紧急数据的字节号。紧急数据的字节号公式如下:

img

紧急指针的作用:

一旦 TCP 知道了你要发送紧急数据,那么在接下来的数据发送中,TCP会将所有的TCP报文段中的URG标志置位,哪怕该报文段中不包含紧急数据,这个行为会持续到紧急数据被发送出去为止

一些坑:

如果发送方多次发送紧急数据,最后一个数据的紧急指针会将前面的覆盖。比方说你发送了一个字节的紧急数据 ‘X’,在 ‘X’ 尚未被 TCP 发送前,你又发送了一个紧急数据 ‘Y’,那么在后面的 TCP 报文中,紧急指针都是指向了 ‘Y’ 的

很多系统的实现,包括 Linux 将紧急数据称之为带外数据(out-of-band data, OOB),意为在连接之外传送的数据,实际上这是不对的(《TCP/IP 详解》一书称此不正确的)。即使是紧急数据,仍然会随着普通数据流一起发送,并不会单独为紧急数据开辟一条新的连接通道单独发送

选项字段:

img

MSS和MTU

img

img

img

img

img

TCP面向字节流

img

TCP三次握手

img

为什么TCP建立连接不能2次握手?

为了避免历史连接:防止旧的重复连接初始化造成混乱。比如在网络拥堵的情况下连续发了2个SYN报文(旧的报文SYN是90,新的是100)

  • 一个「旧 SYN 报文」比「最新的 SYN」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是 91(90+1)。
  • 客户端收到后,发现自己期望收到的确认号应该是 100 + 1,而不是 90 + 1,于是就会回 RST 报文
  • 服务端收到 RST 报文后,就会释放连接。 后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了

在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费

TCP四次挥手

img

注意客户端在进入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 时间将重新计时

img

  • 为什么需要 TIME_WAIT 状态
    1. 防止历史连接中的数据,被后面相同四元组的连接错误的接收
    2. 保证被动关闭连接的一方,能被正确的关闭

TCP可靠传输

TCP的可靠传输协议是以字节为单位的滑动窗口协议

TCP可靠传输的特点:

  • TCP窗口内的序号不是以PDU为单位编号,而是以字节为单位编号
  • TCP的发送窗口和接收窗口均大于1
  • TCP的发送窗口和接收窗口长度不是固定的,而是动态变化的
  • TCP支持多种重传机制:超时重传、快重传和SACK重传

重传机制

超时重传

超时重传发生的情况:

  • 数据包丢失
  • 确认应答丢失

超时重传时间 RTO 的值应该略大于报文往返 RTT 的值

快速重传

不以时间为驱动,而是以数据驱动重传

img

连续收到三个相同的ACK序列号说明该Seq丢失

SACK

超时重传有一个缺点:就是重传的时候,是重传一个,还是重传所有的

SACK( Selective Acknowledgment), 选择性确认

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

img

Duplicate SACK

主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了

img

好处:

  • 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
  • 可以知道是不是「发送方」的数据包被网络延迟了
  • 可以知道网络中是不是把「发送方」的数据包给复制了

滑动窗口

img

img

流量控制

img

拥塞控制

慢启动

  • 拥塞窗口cwnd,以MSS的个数做为cwnd的单位
  • 慢启动门限ssthresh
  1. 初始化时:cwnd为1个MSS, ssthresh为65535个字节
  2. 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据
  3. 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个
  4. 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个
  5. 慢启动阶段cwnd是以指数的形式增长的,直到拥塞窗口大小大于门限值为止

拥塞避免

当cwnd到达ssthresh,一般来说 ssthresh 的大小是 65535 字节

  1. 每当收到一个 ACK 时,cwnd 增加 1/cwnd
  2. 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长

拥塞发生

当网络出现拥塞,也就是会发生数据包重传,会启用拥塞发生算法

在超时重传和快速重传两种重传机制下,拥塞发生算法不同

超时重传:

  1. ssthresh 设为 cwnd/2
  2. cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)

快速恢复:

快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈

  1. cwnd = cwnd/2 ,也就是设置为原来的一半;
  2. ssthresh = cwnd
  3. cwnd = ssthresh + 3
  4. 重传丢失的数据包
  5. 如果再收到重复的 ACK,那么 cwnd 增加 1
  6. 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态

为什么收到新的数据后,cwnd 设置回了 ssthresh ?

  1. 首先,快速恢复是拥塞发生后慢启动的优化,其首要目的仍然是降低 cwnd 来减缓拥塞,所以必然会出现 cwnd 从大到小的改变
  2. 其次cwnd+1的存在是为了尽快将丢失的数据包发给目标,从而解决阻塞的根本(三次相同的ACK会重传),所以这一过程cwnd是增大的