TCP
TCP 是一种提供可靠性交付的协议。
也就是说,通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
但是在网络中相连两端之间的介质,是复杂的,并不确保数据的可靠性交付,那么 TCP 是怎么样解决问题的?
TCP 是通过下面几个特性保证数据传输的可靠性:
-
序列号和确认应答信号
-
超时重发控制
-
连接管理
-
滑动窗口控制
-
流量控制
-
拥塞控制
通过序列号和确认应答信号提高可靠性
如下图,在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个已收到消息的通知,这个消息叫做确认应答(ACK)。
当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。
但是,如果在一定时间内发送端都没有得到确认应答ACK,发送端就会认为数据丢失,并进行数据重发。所以,即使产生了丢包,TCP仍然能保证数据能够到达对端,实现可靠的传输。
没有 ACK 的原因
发送端没有得到确认应答ACK的原因,主要分两种情况:
1. 发送端发送的数据丢包
上图中的主机A发出数据后因为网络拥堵等原因导致了丢包,数据无法达到主机B,此时,主机A如果在一个特定的时间间隔内都没收到主机B的ACK,则会将数据进行重发
2. 接收端发送的确认应答ACK丢包或延迟
这个图中主机A的数据正常发送到主机B,但由于网络堵塞等原因,主机B的ACK没有达到主机A。
主机A在一定时间间隔内始终没收到ACK,则会重发这个数据。
此时,主机B收到数据后就会再次发送ACK,但是由于主机B已经接收过1-1000的数据,所以当再有相同数据达到它时就会放弃这个数据。
ps: 重发与幂等。
此外,也有可能因为一些其他原因导致ACK延迟到达,在源主机重发数据以后才到达的情况也屡见不鲜。此时,源主机只要按照机制重发数据即可。
虽然目标主机通过重发数据可以提供可靠的传输,但是对于目标主机来说,反复收到相同的数据可能会是一个”灾难“,既浪费网络资源,还要耗资源对它处理。
所以,我们需要一种机制来识别是否已经接收到了这个数据包、又能够判断数据包是否需要接收。
如何判断数据是否重复?
目标主机反复收到相同数据是不可取的,为了保持数据的一致性,目标主机必须扔掉重复的数据包,那么怎么判断该数据包是已经重复收取过呢?
为此我们引入了序列号。
序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号。接收端查询接收数据 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。
通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。
所以,通过序列号,上面说的确认应答ACK处理, 重发控制,重复控制都能实现了。
ps: 类似于交易中的唯一订单号。
超时重发如何确定呢?
重发超时是指在重发数据之前,等待确认应答到来的那个特定时间间隔。
如果超过这个时间仍未收到确认应答,发送端将进行数据重发。
最理想的是,找到一个最小时间,它能保证“确认应答一定能在这个时间内返回”。
TCP 要求不论处在何种网络环境下都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。
为此,它在每次发包时都会计算往返时间(RTT Round Trip Time)及其偏差(RTT波动的时间,也叫抖动)。将这个往返时间和偏差时间相加,重发超时的时间就是比这个总和要稍大一点的值。
重发超时既要考虑RTT往返时间,又要考虑网络抖动的偏差,如下图所示,网络网络环境不同可能会造成RTT大幅度摆动,TCP/IP的目的就是即使在这种环境下也能进行控制,不浪费网络流量。
在 BSD 的 Unix 以及 Windows 系统中,超时都以0.5秒为单位进行控制,因此重发超时都是0.5秒的整数倍。不过,最初其重发超时的默认值一般设置为6秒左右。
数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
此外,数据也不会被无限、反复地重发。达到一定重发次数之后,如果仍没有任何确认应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。
通用的思想
(1)超时时间的界定
最简单的是固定。也可以通过统计,动态调整。
(2)重发的次数
不是无限次重发的,重发一段时间以后,暂定重发。
(3)重发的等待策略
重发失败,则越往后等待的时间越长。
连接管理
TCP是面向连接的通信协议,面向连接是指在数据通信之前先做好通信两端之间的准备工作。
因此,在数据通信之前,会通过TCP首部发送一个 SYN
包作为建立连接和等待确认应答,如果对端发来确认应答ACK,则认为可以进行通信,否则如果对端没有发送正确的ACK应答,那么就不会通信。
另外通信完毕需要发送 FIN
包来关闭连接
这就是我们常常说的 三次握手建立连接 和四次挥手关闭连接
TCP是以段为单位进行数据包的发送的
在建立 TCP 连接的同时,也可以确定发送数据包的单位,我们也可以称其为“最大消息长度”(MSS,Max Segment Size),也就是一个段。
最理想的情况是,最大消息长度正好是 IP 中不会被分片处理的最大数据长度。
TCP 在传送大量数据时,是以 MSS 的大小将数据进行分割发送。进行重发时也是以 MSS 为单位。
MSS 在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在 TCP 首部中写入 MSS 选项,告诉对方自己的接口能够适应的 MSS 的大小。
然后会在两者之间选择一个较小的值投入使用。
利用滑动窗口控制提高速度
上面说了,TCP 以1个段为单位,如果每发送一个段进行一次确认应答,才能进行下一次通信,那这样的传输方式有一个缺点,就是包的往返时间(RTT)越长通信性能就越低。
如下图:
这种方式有点类似于数据库不能并发请求,只能一个挨一个的处理,自然这样的效率肯定是比并发低的
为解决这个问题,TCP 引入了窗口这个概念。
确认应答不是以每个分段来确认,而是以更大的单位进行确认,转发时间将会被大幅地缩短。
也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。
如下图所示:
如上图,我们假设窗口大小是4000字节,主机A可以一口气发送把4000字节的序列号发送完毕。这个跟前面每个段接收ACK后才能继续发送新一个段的情况相比,即使RTT变长也不会影响网络的吞吐量。
窗口大小就是指无需等待确认应答ACK而继续发送数据的最大值。
这种窗口机制实现了使用了大量的缓冲区(Buffer,指的是计算机存储收发数据的的内存空间),通过对多个段同时进行确认应答的功能。
滑动窗口示意图如下:
上面这个图一个段为1000字节,滑动窗口是4个段,在①的状态下,如果收到一个序列号为2000的ACK,那么2001 之前的数据就没必要重发了,这部分的数据可以被过滤掉,滑动窗口成为③的样子。
特点
对于滑动窗口有以下几点特点:
上图中的窗口内的数据即便没有收到确认应答也可以被发送出去。不过,在整个窗口的确认应答没有到达之前,如果其中部分数据出现丢包,那么发送端仍然要负责重传。为此,发送端主机需要设置缓存保留这些待被重传的数据,直到收到他们的确认应答。
在滑动窗口以外的部分包括未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再进行重发,此时数据就可以从缓存区清除。
收到确认应答的情况下,将窗口滑动到确认应答中的序列号的位置。这样可以顺序地将多个段同时发送提高通信性能。这种机制也别称为滑动窗口控制。
滑动窗口控制与重发控制
在使用了窗口控制中,如果出现了丢包怎么办呢?
这里我们还是分两种情况分析:
1. 确认应答ACK未能正确返回的情况
在这种情况下,数据是已经被对端主机成功接收了的,是不需要进行重新发送的。
然而,如果在没有使用窗口控制的前提下,没有收到确认应答包的数据包都会被重发。
但是,在使用了窗口控制以后,就如下图所示,
如上图所示,一个段大小为1000字节,一个窗口大小为6000个字节的情况,主机A连续发送了6000序列号的数据,中间的主机B对1001的ACK丢失了,但是后面的2001的ACK正常返回了,说明前2000的序列号的数据都正常读取了,那么即使1001的ACK丢失也不需要进行数据重发!
ps: 这里其实就要求接收者。在正常处理前面的之后,才继续处理后面的。
2.某个报文丢失的情况
如果当接收端主机接收到一个自己应该接收的序列号之外的数据包时,它会一直对当前接收到的数据包返回确认应答包。
如上图所示,主机A的1001-2000序列号的报文丢失了,它会一直收到来自主机B的一个1001的ACK,这个ACK就像在跟主机A提醒 “我想接收从1001开始的数据”。
当主机A连续收到这个1001的确认应答ACK 3次后,就会认为数据丢失了,需要重发。
在滑动窗口比较大的情况下,同一个序列号的确认应答将会被重复不断地返回。
而发送端主机如果 连续 3 次 接收到同一个确认应答包,就会将其对应的数据重发,这种机制比之前提到的“超时重发”更加高效,所以被称之为“高速重发控制”
快恢复
快恢复算法是与快重传算法配合使用的一个算法。
快恢复主要是指,当快重传的时候,发送方快速收到了3个重复的确认,因此会认为网络不是拥塞状态,所以在乘法减小过程(设置sstresh为原来一半),会启动 “拥塞避免”,而不是TCP超时重发机制的重新启动的慢启动
流量控制
接收端在接收到数据后,对其进行处理。
如果发送端的发送速度太快,导致接收端的结束缓冲区很快的填充满了。
此时如果发送端仍旧发送数据,那么接下来发送的数据都会丢包,继而导致丢包的一系列连锁反应,超时重传呀什么的。
而TCP根据接收端对数据的处理能力,决定发送端的发送速度,这个机制就是流量控制。
窗口大小
在TCP协议的报头信息当中,有一个16位字段的窗口大小。在介绍这个窗口大小时我们知道,窗口大小的内容实际上是接收端接收数据缓冲区的剩余大小。
这个数字越大,证明接收端接收缓冲区的剩余空间越大,网络的吞吐量越大。
接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去。
而发送方根据ACK报文里的窗口大小的值的改变进而改变自己的发送速度。
如果接收到窗口大小的值为0,那么发送方将停止发送数据。
并定期的向接收端发送窗口探测数据段,让接收端把窗口大小告诉发送端。
ps: 这种设计思想,可以放在几乎所有的生产-消费者模式中
注:16位的窗口大小最大能表示65535个字节(64K),但是TCP的窗口大小最大并不是64K。
在TCP首部中40个字节的选项中还包含了一个窗口扩大因子M,实际的窗口大小就是16为窗口字段的值左移M位。每移一位,扩大两倍。
拥塞控制
窗口控制解决了 两台主机之间因传送速率而可能引起的丢包问题,在一方面保证了TCP数据传送的可靠性。
然而如果网络非常拥堵,此时再发送数据就会加重网络负担,那么发送的数据段很可能超过了最大生存时间也没有到达接收方,就会产生丢包问题。
为此TCP引入慢启动机制,先发出少量数据,就像探路一样,先摸清当前的网络拥堵状态后,再决定按照多大的速度传送数据。
此处引入一个拥塞窗口:
发送开始时定义拥塞窗口大小为1;每次收到一个ACK应答,拥塞窗口加1;而在每次发送数据时,发送窗口取拥塞窗口与接送段接收窗口最小者。
慢启动
在启动初期以指数增长方式增长;设置一个慢启动的阈值,当以指数增长达到阈值时就停止指数增长,按照线性增长方式增加至拥塞窗口;线性增长达到网络拥塞时立即把拥塞窗口置回1,进行新一轮的“慢启动”,同时新一轮的阈值变为原来的一半。
TCP的拥塞控制采用了四种算法,即慢开始、拥塞避免、快重传和快恢复。
在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
慢开始:慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。
拥塞避免:拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1.
快重传与快恢复:在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。
总结
TCP协议在实现传输可靠性上面做了很多:
通过序列号和确认应答信号确保了数据不会重复发送和重复接收。
同时通过超时重发控制保证即使数据包在传输过程中丢失,也能重发保持数据完整。
通过三次握手,四次挥手建立和关闭连接的连接管理保证了端对端的通信可靠性。
TCP还使用了滑动窗口控制提高了数据传输效率
拓展阅读
由于篇幅有限,这个TCP协议可靠性的专题我分为上下两篇来写,这一篇先讨论前四个可靠性机制,剩下的流量控制和拥塞控制,会写到另外一篇文章,请关注。
关于TCP协议的文章还有几篇,大家也可以看看:
一文彻底搞懂 TCP三次握手、四次挥手过程及原理
面试官:说说UDP和TCP的区别及应用场景
参考资料
TCP协议可靠性是如何保证之滑动窗口,超时重发,序列号确认应答信号
https://icode.best/i/34339046006344
https://notebook.grayson.top/project-26/doc-930/
https://zhuanlan.zhihu.com/p/112317245
https://blog.csdn.net/liuchenxia8/article/details/80428157
https://juejin.cn/post/6844904073611722760
https://segmentfault.com/a/1190000022944999