TCP可靠传输的工作原理

TCP 发送的报文段是交给 IP层 传送的。但 IP层 只能提供尽最大努力交付,也就是说,TCP 下面的网络所提供的是不可靠的传输。因此,TCP必须采用适当的措施才能使得两个运输层之间的通信变得可靠。

理想的传输信道有以下两个特点:

  • 传输信道不产生差错
  • 不管发送方发送速度多快,接收方总是来得及处理到来的数据。

然鹅,理想很丰满,现实很骨感。实际网络并不具备这两个条件,但是可以使用一些可靠的传输协议,当

  • 出现差错时让发送方重传出现差错的数据
  • 在接收方来不及处理到来的数据时,及时告诉发送方适当降低发送数据的速率。

停止等待协议

全双工通信的双方既是发送方也是接收方。以下仅考虑 A 发送数据而 B 接收数据并发送确认。称 A 为发送方,B 为接收方。

这里讨论可靠传输的原理,因此把传送的数据单元都称为分组,而并不考虑数据是在哪一层次上传送的。

“停止等待”就是每发送完一个分组就停止发送,等待对方的确认。在收到确认后再发送下一个分组。

  1. 无差错情况

最简单的情况,如 (a) 所示,A 发送完分组 $M_1$ ,发完就暂停发送,等待 B 的确认。B 收到 $M_1$ 后向 A 发送确认,A 收到对 $M_1$ 的确认后才发送下一个分组 $M_2$ 。其他的分组也采用同样的策略发送。

停止等待协议

  1. 出现差错

图 (b) 是分组在传输中出现差错的情况。

  • B 收到 $M_1$ 时检测到差错便丢弃 $M_1$,不对 A 作出应答。
  • $M_1$ 在传输过程中走丢了,B 没有收到过,也不会作出应答。

可靠传输协议:A只要超过了一段时间仍然没有收到确认,就认为刚才发送的分组丢失了,因而重传前面发送过的分组。这就叫做超时重传

要实现超时重传,要在每发送完一个分组时设置一个超时计时器。如果在超时计时器到期之前收到确认,就撤销已设置的超时计时器。

🧐 需要注意三点:

  • A 在发送完一个分组后,必须暂存已发送的分组的副本(超时重传时是用),收到相应的确认后才能清除。
  • 分组和确认分组都必须进行编号,这样才能知道是哪一个发送出去的分组收到了确认,而哪一个分组还没有收到确认。
  • 超时计时器设置的重传时间应当比数据在分组传输的平均往返时间更长一些。(其实这个时间的选择很有讲究)
  1. 确认丢失和确认迟到

如下图 (a) 所示,B 收到了 $M_1$ 并且也对 A 发出了确认,但是确认在中途丢失了。A 在设定的超时重传时间内没有收到确认,它也无法得知是自己发送的 $M_1$ 出错、丢失、还是 B 的确认丢失了,总之,A 会重传。

A 重传之后,B 收到了重传的分组 $M_1$ ,这时摆在它面前的有两个选择:

  • 直接丢弃 $M_1$ ,不向上层报告(交付)。
  • 继续对 A 发送确认。B 不能认为已经发送给确认就不再崇法确认,毕竟 A 就是因为没有收到确认才重发的,如果不回复确认,A 还会一直重发。

确认丢失和确认迟到

上图 (b) 是另一种可能的情况。B 对 A 发出了确认,但是确认在网络中滞留了很久(可能是迷路了,可能是堵车了,可能是被裹挟了),此时 A 会收到重复的确认,对重复的确认的处理很简单:收下后就丢弃。B 仍然会收到重复的 $M_1$(超时重传的),并且同样要丢弃重复的 $M_1$,并重传确认分组。

通常 A 最终总是可以收到对所有发出的分组的确认。如果 A 不断重传分组但总是收不到确认,就说明通信线路太差,不能进行通信。

上述可靠传输协议称为自动重传请求ARQ(Automatic Repeat reQuest)。

意思是重传的请求是自动进行的,接收方不需要请求发送方重传某个出错的分组。

  1. 信道利用率

显而易见,每次只传一个分组有点太浪费了,完全没有充分利用信道。

想起 Amdahl 定律,我想我们应该意识到的一点是:现代社会如果想获得最大限度的发展,那就要用尽种种方法去压榨机器的潜能,提高机器的利用率。

停止等待协议的信道利用率太低

为了提高传输效率,发送方可以不使用低效率的停止等待协议,而采用流水线传输

流水线传输就是发送方可连续发送多个分组,不必每发完一个分组就停顿下来等待对方的确认。这样可使信道上一直有数据不间断地在传送。

流水线传输可提高信道利用率

当使用流水线传输时,就要使用下面将要介绍的连续ARQ协议滑动窗口协议

连续 ARQ 协议

下图(a)表示发送方维持的发送窗口,它的意义是:位于发送窗口内的5个分组都可连续发送出去,而不需要等待对方的确认。

向前是指向着时间增大的方向,分组发送是按照分组序号从小到大发送的。

连续ARQ协议的工作原理

连续 ARQ 协议规定:发送方每收到一个确认,就把发送窗口向前滑动一个分组的位置。

上图(b)表示发送方收到了对第1个分组的确认,于是把发送窗口向前移动一个分组的位置。

接收方采用累积确认的方式。即,接收方不必对收到的分组逐个发送确认,而是在收到几个分组后,按序到达的最后一个分组发送确认。这表示:到这个分组为止的所有分组都已正确收到了。

累积确认有优点也有缺点

  • 优点:容易实现,即使确认丢失也不必重传。
  • 缺点:不能向发送方反映出接收方已经正确收到的所有分组的信息。

比如,发送方发送了前 5 个分组,而第 3 个分组丢失了。接收方只能对前两个分组发出确认,发送方无法知道后面三个分组的下落,而只好把后面的三个分组都再重传一次。这就叫做 Go-back-N(回退 N 帧),表示需要再退回来重传 已发送过的 N 个分组。

TCP 可靠传输的实现

TCP报文段的首部格式

TCP 虽然是面向字节流的,但 TCP 传送的数据单元却是报文段。一个TCP报文段分为首部数据两部分,而 TCP 的全部功能都体现在它首部中各字段的作用。

TCP 报文段首部的前 20 个字节是固定的,后面有 4n 字节是根据需要而增加的选项(n是整数)。因此TCP首部的最小长度是 20 字节,而 UDP 首部 8 字节。

TCP报文段的首部格式

TCP 首部中的信息有很多,参考图片基本已经可以见字知意了。如果没有,那就要反思一下自己的六级了。

TCP 可靠传输的实现

假定数据传输只在一个方向进行,即 A 发送数据,B 给出确认。但是不要忘记 A 与 B 的通信其实是全双工通信。

以字节为单位的滑动窗口

TCP的滑动窗口是以字节为单位的。为方便,图中字节编号都取得很小。

假定 A 收到了 B 发来的确认报文段,其中窗口是 20 字节,而确认号是 31 (表明 B 期望收到的下一个序号是 31,而序号 30 为止的数据已经收到了)。根据这两个数据,A就构造出自己的发送窗口。

根据B给出的窗口值,A构造出自己的发送窗口

A 的发送窗口:在没有收到 B 的确认的情下,A 可以连续把窗口内的数据都发送出去。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用。

接收方会把自己的接收窗口数值放在窗口字段(首部字段)中发送给对方。 因此,A 的发送窗口一定不能超过 B 的接收窗口数值。

发送方的发送窗口大小还要受到当时网络拥塞程度的制约。

发送窗口后沿的后面部分表示已发送且已收到了确认,不需要保留。而发送窗口前沿的前面部分不允许发送,因为接收方都没有为这部分数据保留临时存放的缓存空间。

发送窗口的位置由窗口前沿和后沿的位置共同确定。发送窗口后沿的变化情况 有且仅有两种可能,即不动(没有收到新的确认)和前移(收到了新的确认),不能走回头路。

A发送了11个字节的数据

从所述可以看出,要描述一个发送窗口的状态需要三个指针:P1、P2和 P3,指针都指向字节的序号。

小于 $P_1$ 的是已发送并已收到确认的部分,而大于 $P_3$ 的是不允许发送的部分。

  • $P_3–P_1$=A的发送窗口
  • $P_2–P_1$=已发送但尚未收到确认的字节数
  • $P_3–P_2$=允许发送但当前尚未发送的字节数(又称为可用窗口或有效窗口)

B 的接收窗口:B 只能对按序收到的数据中的最高序号给出确认,因此 B 发送的确认报文段中的确认号仍然是 31。

可能情况:发送窗口内所有的数据都已正确到达 B,B 也早已发出了确认。但所有确认都滞留在网络中。

在没有收到 B 的确认时,A 不能猜测:“或许 B 已经收到了?” 为了保证可靠传输,A 只能认为 B 还没有收到这些数据。于是,A 在经过一段时间后(超时计时器控制)就重传这部分数据,重新设置超时计时器,直到收到 B 的确认为止。

发送方的应用进程把字节流写入 TCP 的发送缓存,接收方的应用进程从 TCP 的接收缓存中读取字节流。

TCP的缓存和窗口的关系

发送缓存用来暂时存放:

  • 发送应用程序传送给发送方 TCP 准备发送的数据
  • TCP已发送出但尚未收到确认的数据

接收缓存用来暂时存放:

  • 按序到达的、但尚未被接收应用程序读取的数据
  • 未按序到达的数据

根据以上内容,需要注意三点:

  • 虽然 A 的发送窗口是根据 B 的接收窗口设置的,但在同一时刻,A 的发送窗口并不总是和 B 的接收窗口一样大。
  • TCP 通常对不按序到达的数据是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程。
  • TCP 要求接收方必须有累积确认的功能,这样可以减小传输开销。

超时重传时间的选择

报文段每重传一次,就把超时重传时间 RTO 增大一些。典型的做法是取新的重传时间为旧的重传时间的 2 倍。当不再发生报文段的重传时,才根据下式计算超时重传时间。
$$
RTO=RTT_s+4\times RTT_d\tag{1}
$$
$RTT_S$代表加权平均往返时间,$RTT_D$ 是 RTT 的偏差的加权平均值。
$$
新RTT_d=(1-\beta)\times (旧RTT_d)+\beta \times |RTT_s-新的RTT样本|\tag{2}
$$
$\beta$ 是小于 1 的系数,推荐值为 0.25。

选择确认SACK

若收到的报文段无差错,只是未按序号,中间缺少某些序号的数据,那么能否设法只传送缺少的数据而不重传已经正确到达接收方的数据?

选择确认(Selective ACK) 就是一种可行的方法。

接收到的字节流序号不连续

RFC 2018 规定,如果要使用选择确认SACK,那么在建立TCP连接时,就要在TCP首部的选项中加上“允许SACK”的选项,双方必须事先商定好。

SACK 并没有指明发送方应当怎样响应 SACK,大多数的实现还是重传所有未被确认的数据块。

TCP的流量控制

利用滑动窗口实现流量控制

如果发送方把数据发送得过快,接收方就可能来不及接收,这就会造成数据的丢失。所谓流量控制(flow control)就是让发送方的发送速率不要太快,要让接收方来得及接收。

利用可变窗口进行流量控制举例

在上图最下面,B 向 A发送了零窗口的报文段。

如果 B 的接收缓存又有了一些存储空间,于是 B 向 A 发送了 rwnd=400 的报文段。然而这个报文段在传送过程中丢失了。A 一直等待收到 B 发送的非零窗口的通知,而 B 也一直等待A发送的数据。形成 死锁 局面。

为了解决这个问题,TCP 为每一个连接设有一个持续计时器(persistence timer)。只要 TCP 连接的一方收到对方的零窗口通知,就启动持续计时器。

若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出现在的窗口值。如果窗口仍然是零,那么收到这个报文段的一方就重新设置持续计时器。如果窗口不是零,死锁的僵局就可以打破。

TCP的拥塞控制

拥塞(congestion):在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。

$$
\sum对资源的需求>可用资源\tag{3}
$$

要进行拥塞控制,实际上就是寻求打破表达式(3) 的条件。

TCP 进行拥塞控制的算法有四种,即

  • 慢开始(slow-start)
  • 拥塞避免 (congestion avoidance)
  • 快重传(fast retransmit)
  • 快恢复(fast recovery)

仍通之前一样,假定:

  • 数据是单方向传送的,对方只传送确认报文。
  • 接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度 来决定。

慢开始和拥塞避免

发送方控制拥塞窗口:

  • 只要网络没有出现拥塞,拥塞窗口就可以再增 大一些,以便把更多的分组发送出去,这样就可以提高网络的利用率。
  • 只要网络出现拥塞或有可能出现拥塞,就必须把拥塞窗口减小一些,以减少注入到网络中的分组数,以便缓解网络出现的拥塞。

发送方确认网络拥塞:

  • 网络发生拥塞时,路由器就要丢弃分组。
  • 因此,只要发送方没有按时收到应当到达的确认报文,也就是说,只要出现了超时,就可以猜想网络可能出现了拥塞。

慢开始算法的思路:

当主机开始发送数据时,由于并不清楚网络的负荷情况,所以如果立即把大量数据字节注入到网络,那么就有可能引起网络发生拥塞。经验证明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。

慢开始规定,在每收到一个对新的报文段的确认后,可以把拥塞窗口增加最多 一个最大报文段SMSS(Sender Maximum Segment Size)的数值。

使用慢开始算法后,每经过一个传输轮次(transmission round),拥塞窗口 cwnd 就加倍。(指数增长)

为了防止拥塞窗口 cwnd 增长过大引起网络拥塞,还需要设置一个慢开始门限

ssthresh 状态变量。

  • cwnd<ssthresh时,使用慢开始算法。
  • cwnd>ssthresh时,停止使用慢开始算法,改用拥塞避免算法。
  • cwnd=ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。

拥塞避免算法的思路是让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间 RTT就把发送方的拥塞窗口 cwnd 加 1。(线性增长)

TCP拥塞窗口cwnd在拥塞控制时的变化情况

快重传与快恢复

快重传算法:要求接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。

快重传算法规定,发送方只要一连收到 3 个重复确认,就知道接收方确实没有收到报文段 $M_3$,因而应当立即进行重传(即“快重传”),这样就不会出现超时,发送方也不就会误认为出现了网络拥塞。

快重传的示意图

快恢复:是把快恢复开始时的拥塞窗口 cwnd 值再增大一些 (增大3个报文段的长度),即等于新的ssthresh+3xMS

拥塞避免流程图

TCP的拥塞控制的流程图

TCP 可靠传输的总结

  • 首先,采用三次握手来建立 TCP 连接,四次握手来释放 TCP 连接,从而保证建立的传输信道可靠
  • 其次,TCP 采用了停止等待协议连续 ARQ 协议(回退N,Go-back-N;超时自动重传)来保证数据传输的正确性。
  • 使用滑动窗口协议来保证接方能够及时处理所接收到的数据,进行流量控制
  • 最后,TCP 使用慢开始拥塞避免快重传快恢复来进行拥塞控制,避免网络拥塞。

也看到网上比较流行的另外一个版本的总结:

TCP 通过序列号超时重传检验和流量控制滑动窗口拥塞控制实现可靠性。

  1. 应用数据被分割成 TCP 认为最适合发送的数据块。
  2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
  3. TCP 的接收端会丢弃重复的数据。
  4. 超时重传:当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
  5. 校验和TCP 将保持它首部和数据的检验和,发送的数据包的二进制相加然后取反。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。
  6. 流量控制TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。接收方有即时窗口(滑动窗口),随 ACK 报文发送。(TCP 利用滑动窗口实现流量控制)
  7. 滑动窗口:实际中的传输方式。
  8. 拥塞控制:当网络拥塞时,减少数据的发送。发送方有拥塞窗口,发送数据前比对接收方发过来的即使窗口,取小慢启动、拥塞避免、拥塞发送、快速恢复。应用数据被分割成TCP认为最适合发送的数据块,TCP 的接收端会丢弃重复的数据。

停止等待协议也是为了 TCP 协议传输稳定可靠,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。

参考

评论