计算机网络基础(六) 传输层之TCP可靠传输、确认应答、超时重传、回退N 与 Nagle算法
我们知道TCP协议是一个可靠传输的协议,所谓的可靠传输是指需要保证报文在网络传输过程中不丢失、不重复、无差错、不乱序。本节我们将谈谈实现一个可靠传输需要怎么做。

01 停止等待协议
停止等待协议用一句话概括就是:发送方每发一个包(或者叫分组),就需要等待这个包的ack响应包,然后发送方才会再发下一个包;如果一段时间内等不到ack响应包就重发上一个包。
停止等待协议是一个保证可靠传输的协议,该协议包含以下机制。
确认应答机制
接收方接收到一个正确分组时需要回复一个带ack号和ACK位为1的确认报文给发送方。此时发送方就知道分组已到达;
如果不清楚ack号和ACK位的朋友可以参见上一节介绍TCP报文的文章: 计算机网络基础(五) 传输层之UDP协议和TCP协议概述和报文格式
传统的确认应答机制中,分组由于比特差错被接收方检测到会被直接丢弃,不发送ACK报文。不过在TCP协议中即使一个分组发生比特错误,对端接收到这个错误的分组后也会发送ACK分组表示确认,但确认的是出错报文以前的报文;
分组由于丢失无法到达接收方则不发送ACK报文;
停等机制
指发送方每发送完一个分组就停止发送,等待对方确认,收到确认(ACK分组)后再发下一个分组。
自动超时重传机制(ARQ)
发送方为每个分组设置一个超时计时器(TCP中是为每个窗口设置一个超时计时器),超过指定重传时间(RTO)未收到接收方的ACK报文,发送方就会重发报文。
自动的意思是不用接收方请求发送方重传,而是发送方通过计时器计来自动重传。
这里需要注意3点:
A 发送方必须暂时保留已发送的分组的副本以便重传时使用,收到相应分组的确认才可以清除副本。
B 分组必须编号,这才能明确哪个分组得到确认(而且编号也有助于接收方丢弃重复接收过的分组)。
C 重传时间RTO 应该略大于RTT
一个问题:超时重传时间应该定为多少?
超时重传时间太大会使网络链路空闲,降低传输效率;太小会使报文不必要的重传,加大网络负荷引起拥塞。
TCP采用了一种自适应算法计算超时时间RTO。RTO应该略大于报文往返时间RTT。
_ 新的RTTS = (1-α) * (旧的RTTS) + α * (新的RTT样本) RTTD = (1-β)* 旧的RTTD + β * | RTTS
- 新的RTT样本 | RTO = RTTS + 4*RTTD _
TCP需要计算一个加权平均往返时间RTTS,用于反映多次报文传输的整体RTT,每次确认报文到达发送端,发送端都会更新一次RTTS。
“新的RTT样本”可以使用发送方接收到ACK分组的时间戳 - 分组头部时间戳选项计算得到。
α由系统和协议栈开发者决定,α接近0表示新的RTT影响不大,更新较慢;建议标准的RFC规范推荐α=1/8。
RTTD是RTT的加权平均标准差,反应了多次RTT的抖动程度。β推荐为 1/4。
另一个问题:假设发送方发生了重传,然后发送方收到了确认报文,如何确定该ACK报文是对先发送的报文的确认还是重传报文的确认?这个问题对RTTS的计算很重要。如果该ACK是对重传报文的确认,却被误认为是对原来报文的确认,则更新后的RTTS和RTO会偏大。该问题不解决,RTO会因为重传越变越大。

答:Karn算法提出,如果报文段重传收到ACK,无需判断ACK是之前报文的确认还是重传报文的确认,直接不更新本次的RTTS和RTO即可。
这会带来新的问题:如果一段时间内TCP会重传很多报文,采用上述做法会导致RTO失去多次更新,变得不准确。
修正的Karn算法提出,报文段每重传一次就把RTO增大一点:
_ 重传时新RTO = γ * 旧的RTO _
停止等待协议下的通信会出现如下情况:
A 无差错的情况

B 有差错的情况(分组丢失或差错)
在接收⽅ B 会出现两种情况:
一种情况是B 接收 M1 时检测出了差错,就丢弃 M1,其他什么也不做;
另一种情况是 M1 在传输过程中丢失了,这时 B 当然什么都不知道,也什么都不做。
如何解决上面两种情况的问题,答案是依靠超时重传:
发送方 A 为每⼀个已发送的分组都设置超时计时器,A由于重传时间RTO内(略大于RTT)没收到M1的确认因此会重发M1;
发送方 A 在超时计时器到期之前收到了相应的确认,撤销该超时计时器,继续发送下⼀个分组 M2 。
C 确认报文丢失或迟到
假设 发送端 A 的报文没丢失, 接收方B收到了报文而且报文没有错误,但是B的确认报文丢失或过了很久才到达A。
- 子情况1:确认报文丢失。
由于A收不到 M1 的确认报文,A 在超时计时器到期后重传 M1。B ⼜收到了重传的分组 M1,所以丢弃这个重复的分组
M1(可以通过序号和时间戳选项判断分组是否重复),不向上层交付,并向 A 发送确认。

- 子情况2:确认报文迟到
由于A收不到 M1 的确认报文,A 在超时计时器到期后重传 M1。B ⼜收到了重传的分组 M1,所有丢弃这个重复的分组 M1,并向 A
发送确认。A由于会收到2个ack号相同的确认报文,A会丢弃其中晚到达的一个。

结论 : 停等协议可以保证可靠传输的实现,但通信效率不高 。
02 连续ARQ协议
停止等待协的缺点在于传输效率低,必须发送方必须要收到接收方的ACK后才能发送下一个分组,但是如果发送方的分组丢失 又或者
接收方的ACK分组丢失,都会导致发送方的发送过程停滞,后面数据的传输会走走停停;
连续ARQ协议在停止等待协议的基础之上通过提升信道利用率而提升传输效率,它除了要遵循确认应答机制和自动超时重传机制外,还包括以下机制:
流水线传输机制
流水线传输指发送方可以同时连续发送多个分组,而不必每发一个分组就等待对方的确认,与停止等待协议对立,可以提高信道利用率和链路的吞吐量。当然连续发送的分组数量是有限的,这取决于滑动窗口的大小。

累积确认机制
累积确认指接收方不必为每个到达的分组都发送确认,而是收到若干个分组后对按序到达的最后一个分组发送确认。当然TCP协议既可能出现累积确认,也可能出现对单个分组确认。
确认的时机:
A. 收到 2*MSS 长度的数据就做确认应答(有些系统是不管数据长度,而是收到2个报文就确认);
B. 最大延迟0.5秒发送确认应答,也就是说接收方接收到一个分组,然后 在0.5秒内没有收到第二个分组,接收方此时也需要发送
确认分组(多数系统是0.2秒),该延迟时间由延迟应答计时器来计时;
C. 当接收方接收到失序的报文段时就立刻发出确认(对最后一个有序分组的确认),以便快速重传。当发送方收到连续3个ack号相同的ack报文时就会重传。
累积确认是为了提高信道的利用率,提升系统,能少ACK发报文就少发ACK报文。当然如果延迟确认的时间长了可能引发发送端重传,也会降低传输效率。
捎带确认机制
如果接收方发送确认时刚好也要发送自己的数据报文,那么这个ACK确认可能会捎带到这个数据报文中,此时就减少了一个报文头部的开销,这叫做 捎带确认
。
回退N机制
回退N机制指当报文乱序到达接收端,接收端收到的分组不是连续的,而是缺了某些分组,此时接收端只确认第一个空缺分组之前的分组,空缺分组以及其之后的n个分组都要发送方重传,这就是回退N。
举个例子,接收方B收到1 2 4 5号分组但由于3号分组未收到就到达确认时机,此时接收方B只能选择确认1和2分组,发送端A需要重发 3~5 号分组。
为了避免回退N机制重复发送已经发过的报文,可以使用TCP选项中的“选择确认SACK功能”。其机制如下:

上图空缺的序号是 1000~1500 和 3001~3501。
SACK的原理是,把乱序到达的分组先暂存在接收缓冲区,并且把空缺分组相邻分组的左右边界序号(必须是成对边界,在本例子中有3个边界0~999 /
1501~3000 / 3501~4500,共 6 *
4字节=24字节)放到头部的SACK选项告诉发送方,发送方就会只重传空缺的数据给接收方,而无需回退N步。
⾸部选项的⻓度最⼤有 40 字节,指明⼀个对序号⽤掉 8 字节,因 此在选项中最多只能指明 4 对序号的边界信息,也就是指明最多3个空缺范围(4对序号用掉
244字节=32字节,还需要2个字节指明选项类型和长度)。
SACK文档并未有指明发送方应该怎样响应SACK,所以大多数的实现还是会回退N,重传所有未确认的数据块1000~4500。
对比连续ARQ协议和停止等待协议

03 TCP可靠传输的实现
TCP的可靠传输是以连续ARQ协议为基础做出了一些变动,并添加了更多的细节,如滑动窗口的实现、超时计时器如何设置超时时间、选择确认、流量控制和拥塞控制。
TCP可靠传输基于4点: 窗口、序号、确认和重传 。 其中后3点实现了可靠传输,第1点提高TCP传输效率。
TCP分组的发送时机
为了保证TCP传输效率,TCP不会在发送缓冲区一有数据就立刻发送,而是会遵循一些发送的时机,当满足以下3个条件中的一个才会发送数据:
1、TCP维持一个MSS变量,当缓存中的数据到达MSS字节时,就组装成一个报文段发送;
2、当发送方进程指明要求推送报文段(PSH=1)或者是发送紧急数据(USG=1)
3、发送方设置一个 TCP发送报文计时器 , 如果到时了,即便发送缓存中的数据量不够MSS也要发送出去。
当然 情况1和3要在发送方的可用窗口大于0 的情况下才能发送出去。
Nagle算法
Nagle算法用于发送方比较空闲,没有什么数据要发送的情况下,为提高传输效率的一种算法。假设发送方的应用层是逐字节发送数据给协议栈的缓冲区,则Nagle会这样处理:
1. 若进程要把发送的数据逐个字节的发送到TCP的发送缓存,则发送⽅先发送第⼀个数据字节,缓存后⾯到达的数据字节;
2. 发送⽅收到 接收方
对第⼀个数据字符的确认后,发送方把发送缓存中的所有数据组装成⼀个报⽂段(不超过MSS)发送出去,并继续对随后到达的数据进⾏缓存;
3. 只有在收到对前⼀个报⽂段的确认后继续发送下⼀个报⽂段(相当于退化成停等协议);
4. 当到达缓冲区的数据已达到发送窗⼝⼤⼩的⼀半或已达到报⽂段的最⼤⻓度时,(即使上一个报文的确认没到达)也⽴即发送⼀个报⽂段。
5. 接收方此时应该适当延迟回发确认报文,并尽量使用捎带确认。
该算法总结一下就是,在应用进程的数据到达发送缓冲区的速度比较慢的时候就 退化成停等协议
,数据到达发送缓冲区的速度比较快且快到满足上述第4点的时候就立刻发送报文。