更多文章分享在个人微信公众号:极客熊猫
欢迎扫码关注:
引言
所谓可靠传输,有以下四点要求:
- 不损坏,即接收到的数据不存在比特差错;
- 不丢失,即接收到的数据无间隙;
- 不重复,即接收到的数据不重复;
- 不乱序,即接收到的数据是按次序的。
通信介质由于一些原因可能会造成比特差错和分组丢失。
- 使用差错校正码,即添加一些冗余比特用于恢复比特差错;
- 重传机制:即重新传送信息,直到它被正确接收为止。重传可以解决比特差错和分组丢失。TCP就采用重传机制。
重传机制
通过重传机制解决比特差错和分组丢失,需要判断两个问题:
- 是不是发生了分组丢失,即接收方是否已收到分组?
- 是不是发生了比特差错,即接收方收到的分组是否与之前发送方发送的一样?
对于检测是否发生了比特差错,可以通过校验和来完成;但是校验和只能检测,并不能实现差错纠正。
ACK机制
为了判断重传机制带来的第一个问题(即是否发生分组丢失),引入了ACK机制:
接收方收到分组后,给发送方发送一个ACK,以确认自己收到了分组;发送方收到ACK后,再发送下一个分组,继续等待新分组的ACK,这个过程就这样进行下去。
但ACK机制也带来了一些问题:
- 发送方等待ACK应该等多长时间?
这个问题比较复杂,暂时不讨论,到《TCP的超时与重传》再讨论。
- 如果ACK丢失了怎么办?
这个问题比较容易,超过了发送方等待ACK的时间,发送方就再把原分组发送一遍就可以了。当然这可能带来分组重复问题。
- 如果分组接收到了,但是通过校验和检测到分组存在比特差错怎么办?
这个也比较简单,接收方收到存在比特差错的分组后,将不发送ACK,时间一到,发送方重发完整到达的无差错的分组。
序列号机制
从上边可以看出,对于ACK丢失的情况,发送方简单地重发原分组,这将导致接收方接收到重复的分组。
为了解决分组重复问题,引入序列号机制来解决
发送方发送分组时,每个分组都有一个唯一的序列号,这个序列号由分组自身一直携带着。接收方可以使用这个序列号来判断它是否已经收到过这个分组,如果是则丢弃它,保留之前的就好了。
滑动窗口机制
到目前为止,以上的协议是可靠的,但是效率比较低,因为它是一个停等协议,即发送方注入一个分组到通信路径,然后停下来等待,直到接收到来自接收方给这个分组反馈的ACK,发送方才能发送下一个分组。
为了提高吞吐量,我们可以允许发送方同时发送多个分组,这将带来更多需要考虑的问题:
- 发送方每次应该发多少个分组?
- 发送方应该保存哪些分组的副本已备重传?
- 接收方的ACK机制如何区分哪些分组收到了,哪些分组还没收到?
- 接收方收到分组的顺序与发送方的发送顺序不同,即乱序问题如何解决?
为了解决这些问题,引入了滑动窗口机制。
上图显示了一个发送方窗口,3号分组已被发送并确认,所以由发送方保存的它的副本可以被释放;7号分组在发送方已准备好,但还未被发送,因为它还没有进入窗口。
现在我们可以想象,发送方下一步就接收到分组4的ACK,所以整个窗口向右滑动一个分组,变成下图:
这意味着4号分组现在可以释放了,而7号分组可以被发送了。
一般,发送方和接收方都有自己的滑动窗口结构。
发送方的窗口记录着哪些分组可以被释放,哪些分组正在等待ACK,哪些分组还不能被发送;
接收方的窗口记录着哪些分组已接收和确认,哪些分组是下一步期望接收的,以及哪些分组即使被接收也会因内存限制而被丢弃。
滑动窗口机制又带来一些需要考虑的问题:
- 窗口大小应该是多少?
- 如果接收方或网络处理不过来发送方的数据率时该怎么办?
流量控制和拥塞控制
为了解决滑动窗口机制带来的问题,引入了流量控制和拥塞控制。
流量控制
流量控制:当接收方相对于发送方太慢时,强迫发送方慢一点。
流量控制的主要形式为基于滑动窗口进行流量控制,在这种方法里:
- 滑动窗口大小不固定,允许随时间而变动;
- 接收方通过窗口通告通知发送方更新自己的窗口大小;
- 窗口通告通常与ACK一起由同一个分组携带,即发送方在向右滑动窗口的同时调整窗口的大小。
拥塞控制
流量控制解决了接收方相对于发送方慢的问题,但是一般在发送方和接收方之间还有很多路由器,它们的内存有限,如果发送方发送的太快,快到超过了这之间某台路由器的承受能力,就会导致丢包。
这个问题通过拥塞控制机制来解决。
超时重传机制
为解决“发送方等待ACK应该等多长时间才能判定分组丢失并重发它”这个问题,引入了超时重传机制。
直观上,发送方重新发送一个分组之前应等待以下时间的总和:
- 发送分组所用的时间;
- 接收方处理分组的时间;
- 接收方发送一个ACK所用的时间;
- ACK到达发送方所用的时间;
- 发送方处理ACK所用的时间。
不幸的是,谁都不知道这些时间是多少,而且它们会随网络环境和主机负载而变化。
所以采用的策略是:让协议实现尝试去估计这些时间,称为往返时间估计(RTT)。
TCP中的可靠性
TCP服务模型
TCP提供了一种面向连接的可靠的字节流服务。
面向连接的,即通信双方需要建立一条端到端连接;
字节流:应用层传下来的数据会被TCP打散成TCP认为的最佳大小的块来发送,一般使得每个报文段按照不会被分片的单个IP层数据报的大小来划分。
TCP通过前述各种技术机制的变种,实现了可靠数据传输。
序列号:在TCP中序列号实际代表了每个分组的第一个字节在整个字节流中的字节偏移,而非分组号;
校验和:在TCP中用于检测传送中的比特差错;
重传计时器:当TCP发送一组报文段时,它通常设置一个重传计时器,等待对方的ACK。TCP不会为每个报文段各自设置一个重传计时器,而是发送一个窗口的数据只设置一个重传计时器,当ACK到达时更新超时;
ACK机制:TCP的ACK是累积的,即指示字节号N的ACK到达意味着N(不含N)之前的所有字节都成功收到了。
窗口通告:因为TCP提供的是双工服务,A给B发数据时的TCP报文段同时也包含了对B发给A的数据的ACK,同时每个报文段也包含一个窗口通告实现相反方向上的流量控制。
乱序问题:TCP绝不会以杂乱次序给上层应用程序发数据。因此,TCP接收端可能会被迫先保持大序列号的数据不交给应用程序,直到缺失的小序列号报文段一一到达,空洞被填满再往应用程序交付数据。
TCP头部结构
- 源/目的端口:与IP头部中的源/目的IP地址一起,唯一地标识了每个连接;
- 序列号:标识了TCP发送端到接收端的数据流的一个字节,该字节代表着包含该序列号的报文段的数据中的第一个字节;
- 确认号:其值是该确认号的发送方期待接收的下一个序列号,即最后被成功接收的数据字节的序列号加1;
- 头部长度:指出TCP头部的长度,以4字节为单位。作为一个4位字段,TCP头部被限制为最大60字节;
- 保留:4位,暂时没用,填充为0;
- CWR:1位,拥塞窗口减(发送方降低发送速率);
- ECE:1位,ECN回显(发送方接收到了一个更早的拥塞通告);
- URG:1位,紧急(使紧急指针字段有效);
- ACK:1位,确认(使确认号字段有效);
- PSH:1位,推送(接收方应尽快给应用程序传送这个数据);
- RST:1位,重置连接;
- SYN:1位,用于初始化一个连接的同步序列号;
- FIN:1位,该报文段的发送方已经结束向对方发送数据;
- 窗口大小:窗口通告,以字节为单位通知对方更新窗口大小;
- TCP校验和:检测比特差错;
- 紧急指针:用于发送紧急数据,参考《APUE》带外数据;
- 选项:最常见的选项是MSS(最大端大小);