TCP 协议使用如下几种机制来保证可靠传输。
包应答序列号及包重组
面临的问题:网络传输中,会出现数据的破坏,丢包,重复,分片混乱等问题。
本质上,要想保证传输的可靠性,则需要对传输的内容进行验证。对于网络数据的破坏,采取的策略是丢弃重新发送,以确保不会出现致命的错误。TCP 在自身协议中单独划了一块 checksum 用于这种校验,校验算法本质上是将整块数据通过某个函数映射到 16 位的校验位上(比如用字符相加的和来校验)
对于数据传输正确,但是分片乱序,重复等问题,或是丢包,采取的策略并非丢弃而是自行进行包重组。
考虑两种情况:第一种情况是某个包缺少了,导致整个数据中间缺了一段 1000 字节,那么如何通知到对方自己少了哪一段数据;另一种情况是由于网络或者重发机制的原因导致某一个包收到多次,如何把多余的包都排除掉,仅保留已有数据。
TCP在设计时候充分考虑这点,其中 SYN 和 ACK 就是用来确保这个过程的,SYN 发送的是字节顺序,ACK 则应答收到的字节序加 1。这样,无论是发送方还是接收方,都可以准确的维护一张发送接收字节的列表。从而可以知道对方还需要哪些字节,或自己已经接收了哪些字节。
重传机制
超时重传
为了保证数据一定被接收到,就必须妥善处理超时,对于超时没得到响应,则最好的办法是重新发送。
首先将数据拷贝到发送缓冲区,每个包在发送时都会启动一个定时器,如果定时器超时前收到了对方应答,则发送成功,清除缓冲区,否则重传数据包,直到达到最大次数。
TCP 在每次发包时都会计算往返时间极其偏差,通过这个记录可以大致判断双方的网络情况从而确定超时时间。通常刚开始超时时间较长(如 6s),而后可能到 0.5s 这样较小的时间。
快重传
超时重传是等到超时还未收到接收方的回复,才开始进行重传。而快重传的设计思路是:如果发送方收到3个重复的接收方的 ACK,就可以判断有报文段丢失,此时就可以立即重传丢失的报文段,而不用等到设置的超时时间到了才开始重传,提高了重传的效率。
比如:假设发送方仍然发送 1、2、3、4、5 共 5 份数据;接收方先收到 Seq 1,回 Ack 2;然后 Seq 2 因网络原因丢失了,正常收到 Seq 3,继续回 Ack 2;后面 Seq 4 和 Seq 5 都到了,最后一个可能被丢了的包还是 Seq 2,继续回 Ack 2;现在,发送方已经连续收到4次(大于等于3次)相同的 Ack(即 Ack 2),知道最大序号的未收到包是 Seq 2,于是重传 Seq 2,并清空 Ack 2 的计数器;最后,接收方收到了 Seq 2,查看窗口发现 Seq 3、4、5 都收到了,回 Ack 6。示意图如下:
流量控制(滑动窗口)
一般来说,我们都希望数据传输得更快一些。但如果发送法把数据发送得过快,接收方可能就来不及接收,就会造成数据丢失。流量控制,就是让发送方的发送速率不要太快,要让接收方来得及接收。
TCP 利用滑动窗口机制来实现对发送方的流量控制。滑动窗口本质上是为了在通信过程中同步收发双方的速率。通过发送端的发送窗口和接收端的接收窗口来保证发送的可靠性,同时协调发送的速度。
对于发送端来说,整个窗口分为下面四段,一是已经发送也收到确认回复的;二是已经发送但尚未收到回复的;三是允许发送但还没有发送的;四是不允许发送的
同理,对于接收方来说,整个窗口分为三段,一是已经接收并且已经回复ACK的;二是已经接收的;三是不能接收的
而所谓的滑动,则是将窗口从上一次收到的连续 ACK 的位置整个划到下一次收到连续 ACK 的位置而已。注意连续二字,不连续则不能算作已经接收完毕。
拥塞控制
拥塞控制的起因是,作为 TCP 本身虽然已经有了各种校验和检测方法保证通信双方能否互相通信并且能够同步双方的情况了。但是它还忽略了一个关键因素 — 网络状况。网络是通路,如果这个通路太拥挤,应该适当减少发送,而如果这个通路比较宽松,则可以适当增加发送。
TCP 的拥塞控制包括:慢启动,拥塞避免,拥塞发生,快速恢复。
关于慢启动和拥塞避免请看下图:
一开始使用慢启动,即拥塞窗口设为 1,然后拥塞窗口指数增长到慢开始的门限值(ssthresh=16),则切换为拥塞避免,即加法增长,这样增长到一定程度,导致网络拥塞,则此时会把拥塞窗口重新降为 1,即重新慢开始,同时调整新的慢开始门限值为 12,之后以此类推。