原创文章首发于公众号:「码农富哥」,致力于分享后端技术 (高并发架构, 中间件, Linux, TCP/IP, HTTP, MySQL, Redis), Python 等原创干货和面试指南!
TCP/IP协议是非常重要的一个知识点,也一直是面试的高频题,当面试官问你,能说说TCP协议是怎么保证可靠传输的吗,你能回答上吗?
TCP 是一种提供可靠性交付的协议。
也就是说,通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
TCP 是通过下面几个特性保证数据传输的可靠性:
上一篇 TCP协议可靠性是如何保证之滑动窗口,超时重发,序列号确认应答信号 我们讨论了TCP协议可靠性的几个机制:序列号和确认应答信号, 超时重发控制,连接管理,滑动窗口控制
这一篇我们继续要讨论TCP协议可靠性的另外两个机制: 流量控制,拥塞控制
关于TCP协议的文章还有几篇,大家也可以看看:
所谓流量控制,就是让发送端不要发送的过快,让接收端能来得及接收
假设没有流量控制,发送端根据自己的实际情况发送数据,如果发送的速度太快,导致接收端的接收缓冲区很快填满了,此时发送端如果继续发送数据,接收端处理不过来,这时接收端就会把本来应该接收的数据丢弃,这会触发发送端的重发机制,从而导致网络流量的无端浪费。
所以TCP需要提供一种机制:让发送端根据接收端实际的接收能力控制发送的数据量。这就是所谓的流量控制。
TCP 利用滑动窗口实现流量控制的机制, 而滑动窗口大小是通过TCP首部的窗口大小字段来通知对方。
我们重温一下TCP的头部:
在TCP协议的头部信息当中,有一个16位字段的窗口大小,窗口大小的内容实际上是接收端接收数据缓冲区的剩余大小。这个数字越大,证明接收端接收缓冲区的剩余空间越大,网络的吞吐量越大。
不过,当接收端这个接收缓冲区面临数据溢出时,窗口大小的值就会随之设置成一个更小值,告诉发送端要控制一下发送的数据量了。
发送端接收到接收端的窗口变化指示后,就会对数据发送量进行调整,从而形成一个完整的流量控制。
流量控制的具体操作就是:接收端会在确认应答发送ACK报文时,将自己的即时窗口大小rwnd(receiver window)填入,并跟随ACK报文一起发送过去。而发送方根据ACK报文里的窗口大小的值进而改变自己的发送速度。
看看下面的图,展示了TCP流量控制的大概过程:
如上图所示,主机B接收到了一个1-1000序列号的数据包以后,返回一个ACK给发送端,并且告诉发送端它的窗口大小为3000,意味着发送端还能发送3000个字节的数据。
主机A收到指示后,继续发送数据,直到主机B收到3001-4000的数据段后其接收缓冲区满了,主机B的返回窗口大小为0,让主机A要暂停发数据了。
就是这样一个流程,可以防止发送端一次发送过大的数据导致接收端无法处理的情况。
那么另外一个问题来了:发送端停止发送数据后,什么时候可以继续发送数据呢?
我们继续看上图,答案就是等接收端处理完了缓冲区的数据后发送一个窗口更新的数据包通知,发送端才可以继续根据窗口大小发送数据。
但是如果发送端在重发超时的时间内都没有收到窗口更新的通知或者窗口更新的包丢失了,就没法正常通信了,那怎么办呢?
TCP为每一个连接设有一个持续计时器(persistence timer)。 只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口控测数据段(这个数据段只包含一个字节),那么收到这个报文段的一方就重新设置持续计时器。
所以发送端会定时向接收端发送一个 窗口探测 的数据段,这目的是为了获取最新的窗口大小信息。
就这样,完成了TCP流量控制的整个过程。
我们都知道计算机网络中的资源是有限的。某段时间内网络中对资源的需求超过了网络中的可用部分,而导致网络性能下降的情况就是拥塞。
通俗点说就是发送的数据包太多网络中的设备处理不过来,而导致网络性能下降的情况。
网络中的路由器会有一个数据包处理队列,当路由器接收到的数据包太多而一下子处理不过来时,就会导致数据包处理队列过长。此时,路由器就会无条件的丢弃新接收到的数据封包。
这就会导致上层的 TCP 协议以为数据包在网络中丢失,进而重传这些数据包,而路由器又会丢弃这些重传的数据包,如此以往,就会导致网络性能急剧下降,引起网络瘫痪。因此,TCP 需要控制数据包发送的数量来避免网络性能的下降。
有了TCP的滑动窗口控制,收发主机之间即使不再以一个“段”为单位,而是以一个“窗口”为单位发送确认应答信号,所以发送主机够连续发送大量数据包。然而,如果在通信刚开始的时候就发送大量的数据包,也有可能会导致网络的瘫痪。
在拥塞控制中,发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。
发送窗口取拥塞窗口和接收端窗口的最小值,避免发送接收端窗口还大的数据。
拥塞控制使用了两个重要的算法: 慢启动算法, 拥塞避免算法。
(一)慢启动算法:
慢启动算法的思路是,不要一开始就发送大量的数据,先试探一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。慢算法中,每个传输轮次后将 cwnd 加倍。
举个例子:一开始发送方设置cwnd=1(为方便理解,这里用报文段的个数作为窗口大小的单位),然后每经过一个传输轮次,cwnd都发加倍,比如1, 2, 4, 8...指数增长
所以,这里的慢启动,不是指拥塞窗口增长慢,而是相对于一开始就上来传输大窗口的数据要显得慢。
当然,cwnd 的大小肯定不可能一直以这种指数的方式增长下去,要不然很快就会增长到引起网络崩溃的程度了。所以,经过一定时间或条件,我们就要换成拥塞避免算法来发送数据。
(二)拥塞避免算法:
拥塞避免算法也是逐渐的增大 cwnd 的大小,只是采用的是线性增长 而不是像慢启动算法那样的指数增长。
具体来说就是每个传输轮次后将 cwnd 的大小加一(加法增大),如果发现出现网络拥塞的话就按照上面的方法重新设置ssthresh的大小(乘法减小,原来的二分之一)并从cwnd=1开始重新执行慢开始算法。
问题:在拥塞控制中, 慢启动算法 和 拥塞避免算法 是怎么配合使用的呢?
像上面所说,慢启动算法下的cwnd大小是指数增长,所以不能任 cwnd 任意增长,所以我们引入一个慢启动门限(ssthresh)的阈值来控制 cwnd 的增长。
ssthresh的作用是:
还有一个问题就是这个 ssthresh 是怎么设置的呢?
TCP/IP 中规定无论是在慢开始阶段还是在拥塞避免阶段,只要发现网络中出现拥塞(没有按时收到确认),就要把ssthresh设置为此时发送窗口的一半大小(不能小于2)。
如上图所示,拥塞控制的大致流程如下:
TCP 的可靠传输的原理就是超时重传机制,而重发机制有两种:超时重传机 和 快重传
TCP快重传的示意图如下:
如图,由于发送端不必等待每个数据段都确认才能继续发送,而是以一个窗口为单位发送数据,所以就算主机A发送的1001-2000序列号数据段丢失,主机A依然会继续发剩下的窗口大小数据,而此时主机B发现1001-2000数据丢失,它会每次收到其他序列号的数据包,都返回一个序列号2000的ACK,以此明确通知主机A,当主机A收到三次2000的ACK直到丢失了1001-2000数据包,就需要重传1001-2000的数据包了。
以此达到哪怕没到重发超时时间,都能快速重传的目的
快恢复算法是与快重传算法配合使用的一个算法。
快恢复主要是指,当快重传的时候,发送方快速收到了3个重复的确认,因此会认为网络不是拥塞状态,所以在乘法减小过程(设置sstresh为原来一半),会启动 “拥塞避免”,而不是TCP超时重发机制的重新启动的慢启动
这篇文章总结了TCP协议在传输可靠性的两个重要机制:
如果大家有所收获的话
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。