TCP协议采取了哪些机制来进行拥塞控制
最初的TCP协议只有基于窗口的流控制(flow control)机制而没有拥塞控制机制,流控制是一种局部控制机制,其参与者仅仅是发送方和接收方,它只考虑了接收端的接收能力,而没有考虑到网络的传输能力;而拥塞控制则注重于整体,其考虑的是整个网络的传输能力,是一种全局控制机制。拥塞控制机制使得TCP连接在网络发生拥塞时回退(back off),也就是说TCP源端会对网络发出的拥塞指示(congestion notification)(例如丢包、重复的ACK等)作出响应。针对TCP在控制网络拥塞方面的不足,后来又提出了“慢启动”(Slow Start)和“拥塞避免”(Congestion Avoidance)算法。TCP Reno版本增加了“快速重传 ”(Fast Retransmit)、“快速恢复”(Fast Recovery)算法,避免了网络拥塞不严重时采用“慢启动”算法而造成过大地减小发送窗口尺寸的现象,这样TCP的拥塞控制就由这4个核心部分组成。 近几年又出现TCP的改进版本如NewReno和选择性应答(selective acknowledgement,SACK)等。

TCP拥塞控制
我们看到TCP连接的双方都包含一个接收缓冲区,一个发送缓冲区和几个变量(LastByteRead,rwnd等)。 TCP拥塞控制机制运行在发送者对拥塞窗口的跟踪上。 拥塞窗口(表示为cwnd)对TCP发送方可以发送到网络的速率施加约束。具体而言,发送者的未确认数据量不得超过cwnd和rwnd之间的较小值:ssthresh 慢启动阈值(show start threshold)别被“慢启动”这个名字所迷惑了,实际上这是cwnd增长最快的阶段。在慢启动状态下,cwnd的值从1 MSS开始,并且当每个被传输的报文段第一次ACK时,cwnd都会+1MSS在进入拥塞避免状态时,cwnd的值大约是上次遇到拥塞时的值的一半在慢启动阶段每个RTT都会将cwnd值加倍,而在拥塞避免阶段TCP采用更保守的方法,并且每个RTT只增加cwnd一个MSS的值[RFC 5681]。 这可以通过几种方式实现。 一种常见的方法是TCP发送器在新的确认到达时通过MSS字节(MSS / cwnd)增加cwnd。 例如,如果MSS是1,460字节而cwnd是14,600字节,则在RTT内发送10个段。 每个到达的ACK(假设每个段一个ACK)将拥塞窗口大小增加1/10MSS,因此,当10个段都ACK后,cwnd才累计增加了一个MSS。在快速恢复中,对于导致TCP进入快速恢复状态的丢失段的每个重复ACK,cwnd的值增加1 MSS。 最终,当丢失的段的ACK到达时,TCP在放空cwnd后进入拥塞避免状态。 如果发生超时事件,则执行与慢启动和拥塞避免相同的操作后,快速恢复将转换为慢启动状态:cwnd的值设置为1 MSS,ssthresh的值设置为值的一半。快速恢复是TCP [RFC 5681]的推荐但不是必需的组件。 有趣的是,早期版本的TCP(称为TCP Tahoe)无条件地将其拥塞窗口切换为1 MSS,并在超时指示或三重复ACK指示丢失事件后进入慢启动阶段。 较新版本的TCP,TCP Reno,整合了快速恢复。TCP tahoe 无快速恢复TCP reno有快速恢复忽略连接开始时的初始慢启动时段并假设丢失由三次重复ACK而不是超时触发的,TCP的拥塞控制包括每个RTT 1个MSS的cwnd线性(附加)增加然后减半 (三次重复ACK事件)的cwnd的(乘法减少)。 出于这个原因,TCP拥塞控制通常被称为加法增加,乘法减少(AIMD)形式的拥塞控制。AIMD拥塞控制引起了“锯齿”行为,如图3.54所示,这也很好地说明了我们早期对TCP“探测”带宽的直觉 -TCP线性增加了它的拥塞窗口大小(以及它的传输速率),直到 发生三重复ACK事件。 然后它将拥塞窗口大小减少两倍,然后再次开始线性增加,探测是否有额外的可用带宽。如前所述,许多TCP实现使用Reno算法[Padhye 2001]。已经提出了Reno算法的许多变体[RFC 3782; RFC 2018]。 TCP Vegas算法[Brakmo 1995; Ahn 1995]试图在保持良好吞吐量的同时避免拥挤。 Vegas的基本思想是(1)在发生丢包之前检测源和目的地之间的路由器中的拥塞,以及(2)当检测到即将发生的丢包时,线性地降低速率。通过观察RTT预测即将发生的分组丢失。数据包的RTT越长,路由器的拥塞就越大。 Linux支持许多拥塞控制算法(包括TCP Reno和TCP Vegas),并允许系统管理员配置将使用哪个版本的TCP。 Linux版本2.6.18中的TCP的默认版本设置为CUBIC [Ha 2008],这是为高带宽应用程序开发的TCP版本。有关TCP的许多风格的最新调查,请参阅[Afanasyev 2010]。 TCP的AIMD算法是基于大量的工程洞察力和运营网络中的拥塞控制实验而开发的。

分析tcp协议原理
原理四个主要方面:一、tcp协议之连接建立、断开二、tcp协议之超时重传三、tcp协议之窗口管理四、tcp协议之拥塞控制TCP是一种面向有连接的协议,也就是说必须确认对方存在时才能发送数据而TCP通过检验和、序列号、确认应答、重发控制、连接管理、窗口控制等机制来实现可靠传输。1. 目的:TCP三次握手是客户端和服务器总共发三个数据包,通过三个数据包来确认主动发送能力和被动接收能力是否正常。2. 实质:通过指定的四元组(源地址、源端口、目标地址、目标端口)来建立TCP连接,同步双方各自发送序列号seq和确认号ACK,同时也会交换窗口大小信息三次握手过程的实现方式就是交换序列号seq。随便在网上找个地址,如果通过域名想看ip地址,可以ping下看连接。① 192.168.3.7发送[SYN]报文段至222.169.228.146,告知序列号x为0。② 222.169.228.146发送[SYN,ACK]报文段至192.168.3.7,告知序列号y为0,确认号ACK为x+1=1。③192.168.3.7发送[ACK]报文段至222.169.228.146,告知确认号ACK为y+1=1。报文段中的其他参数:MSS=1460:允许从对方接收到的最大报文段,图中为1460字节(指承载的数据,不包含报文段的头部)。win=8192:滑动窗口的大小为8192字节。SACK_PERM=1:开启选择确认。为什么会使用SACK:tcp确认方式不是一段报文段一确认,而是采用累积确认方式。服务器接收到的报文段无序所以序列号也是不连续,服务器的接收队列会出现空洞情况。为了解决空洞,提前了解当前空洞,应对丢失遗漏,采取重传。提前了解方式就是通过SACK选项信息,SACK信息包含接收方已经成功接收的数据块的序列号范围。而SACK_PERM字段为1表明,选择开启了SACK功能。网络层可能会出现丢失、重复、乱序的问题,tcp是提供可靠的数据传输服务的,为了保证数据的正确性,tcp协议会重传它认为的已经丢失的包。重传两种机制:一种基于时间重传,一种基于确认报文段提供的信息重传。RTT:数据完全发送完(完成最后一个比特推送到数据链路上)到收到确认信号的时间(往返时间)。RTO:重传超时时间(tcp发送数据时设置一个计时器,当计时器超时没有收到数据确认信息,引发超时而重传,判断的标准就是RTO)。思考:发送序列号为1、2、3、4这4个报文段,但是出现了序列号2报文段丢失,怎么办?发送端接收到seq1的确认报文(ACK=2)后,等待seq=2的确认报文。接收端当收到序列号为3的报文(2已丢失),发送ack为4的确认报文,发送端正等待ack为2的确认报文,面对跳跃的报文,那么发送端会一直等待,直到超出指定时间,重传报文2。为什么不跳跃确认呢?tcp是累积确认方式,如果确认报文3,那么意味着报文1和报文2都已经成功接收。超时处理方式:思考:上面计时器是以时间为标准重传,那么可以通过确认报文的次数来决定重传。发送端接收到seq1的确认报文(ACK=2)后,等待seq=2的确认报文。接收端收到报文3、4、5,但是没收到报文2,那么接收端发送三个ACK为2的确认报文,发送端收到这个三个确认报文,重传报文2。思考:如果快速重传中丢失包的地方很多(报文2,报文,7,报文9,报文30,报文300....),那么需要从头到尾都重传,这很蛋疼?思考:SACK重传对于接收到重复数据段怎样运作没有明确规定,通过DSACK重传可以让发送方知道哪些数据被重复接收了,而且明确是什么原因造成的。发送端没有收到100-199的ACK包,超过指定时间,重传报文。接收端都已经收到200-299的发送报文了,又来100-199是重复报文。再向发送端发送一个ACK报文,设置SACK 100-199,告知发送端,已经收到了100-199包,只是回应ACK包丢失。发送端发送包100-199,由于网络延迟,一直没有达到接收端。接收端连续发送三个ACK 200确认报文,触发快速重传,发送端收到了ACK 500的确认报文,表明之前的报文都已经交付成功。接收端又收到了延迟的报文100-199,再次向发送端发送一个SACK 100-199的ACK 500报文。发送端发现这是重复报文,判断为网络延迟造成的。计时器重传:根据超时,重传。快速重传:根据接收三次相同ACK报文,重传。选择确认重传:根据接收端提供的SACK信息,重传。DSACK重传:根据重复报文,明确丢失ACK报文还是网络延迟。Category1:已发送且已确认(已经收到ACK报文的数据)。Category2:已发送但未收到确认。Category3:即将发送。Category4:窗口移动前都不能发送。可用窗口:46-51字节。发送窗口:32-51字节。RCV.NXT:左边界RCV.WND:接收窗口RCV.NXT+RCV.WND:右边界接收端接收到序列号小于左边界,那么被认为重复数据而被丢弃。接收端接收到序列号大于右边界,那么被认为超出处理范围,丢弃。注意:tcp协议为累积ACK结构,只有当达到数据序列号等于左边界时,数据才不会被丢弃。如果窗口更新ACK丢失,对于发送端,窗口左边界右移,已发送数据得到ACK确认之后,左右边界距离减小,发送端窗口会减小,当左右边界相等时,称为零窗口。零窗口之后:接收端发送窗口更新能会发生窗口更新ACK丢失。<>解释:TCP是通过接收端的通告窗口来实现流量控制的,通告窗口指示了接收端可接收的数据量。当窗口值变为0时,可以有效阻止发送端继续发送,直到窗口大小恢复为非零值。当接收端重新获得可用空间时,会给发送端传输一个窗口更新告知其可继续发送数据。这样的窗口更新通常都不包含数据(纯ACK),接收端向发送端发送的窗口更新ACK可能丢失。结果双方处于等待状态,发生死锁。解决方案:发送端会采用一个持续计时器间歇性地查询接收端,看其窗口是否已增长。触发窗口探测,强制要求接收端返回ACK。发送几次探测,窗口大小还是0,那么断开连接。出现SWS的情况:① 接收端通告窗口太小。② 发送端发送的数据太小。解决方案:① 针对接收端:不应通告小窗口值[RFC1122]描述:在窗口可增至一个全长的报文段(接收端MSS)或者接收端缓存空间的一半(取两者中较小值)之前,不能通告比当前窗口更大的窗口值。标准:min(MSS , 缓存空间/2)。② 针对发送端:不应发送小的报文至少满足以下其一:(1)可以发送MSS字节的报文。window size >= MSS或者 数据大小>=MSS(2)数据段长度>=接收端通告过的最大窗口值的一半,才可以发送。收到之前发送的数据的ack回包,再发送数据,否则一直攒数据。(3) -1 没有未经确认的在传数据或者-2 连接禁用Nagle算法。tcp基于ACK数据包中的通告窗口大小字段实现了流量控制。当网络大规模通信负载而瘫痪,默认网络进入拥塞状态,减缓tcp的传输。发送方和接收方被要求承担超负荷的通信任务时,采取降低发送速率或者最终丢弃部分数据的方法。反映网络传输能力的变量称为拥塞窗口(cwnd)。通告窗口(awnd)。发送窗口swnd=min(cwnd,awnd)目的:tcp在用拥塞避免算法探寻更多可用带宽之前得到cwnd值,帮助tcp建立ACK时钟。[RFC5681] :在传输初始阶段,由于未知网络传输能力,需要缓慢探测可用传输资源,防止短时间内大量数据注入导致拥塞。慢启动算法针对这一问题而设计。在数据传输之初或者重传计时器检测到丢包后,需要执行慢启动。拥塞窗口值:每收到一个ACK值,cwnd扩充一倍。所以假设没有丢包且每个数据包都有相应ACK值,在k轮后swnd=,成指数增长。SMSS是发送方的最大段大小。慢启动阶段,cwnd会指数增长,很快,帮助确立一个慢启动阙值(ssthresh)。有了阙值,tcp会进入拥塞避免阶段,cwnd每次增长值近似于成功传输的数据段大小,成线性增长。实现公式:cwnd+=SMSS*SMSS/cwnd刚建立连接使用慢启动算法,初始窗口为4,收到一次ACK后,cwnd变为8,再收到一次ACK后,cwnd变为16,依次继续,32、64,达到阙值ssthresh为64。开始使用拥塞避免算法,设置ssthresh为ssthresh/2,值为32。重新从初始窗口4,线性递增到ssthresh=32。当cwnd < ssthresh时,使用慢启动算法当cwnd > ssthresh时,使用拥塞避免算法应用快速恢复算法时机:启动快速重传且正常未失序ACK段达到之前。启动快速恢复算法。实现过程:① 将ssthresh设置为1/2 cwnd,将cwnd设置为ssthresh+3*SMSS。② 每接收一个重复ACK,cwnd值暂时增加1 SMSS。③当接收到新数据ACK后,将cwnd设置为ssthresh。参考:<>

TCP 流量控制与拥塞控制
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的。为了通过IP数据报实现可靠性传输,需要考虑很多事情,侧如数据的破坏、丢包、重复以及分片顺序混乱等问题。如不能解决这些问题,也就无从谈起可靠传输。TCP通过校验和、序列号、确认应答、重发控制、连接管理、以及窗口控制等机制来实现可靠性传输。TCP建立连接的实质是,主机A和主机B告知彼此的第一个发送字节的初始序列号,建立连接后对每一个发送的字节都需要以初始序列号为基点进行编号,需要对方来确认每一个字节编号都已经成功接收,双方初始序列号是由操作系统动态生成的随机的值,一般每个TCP 会话都会有不一样的初始序列号,占四个字节。TCP通过肯定的确认应答(ACK) 实现可靠的数据传输。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。在一定时间内没有等到确认应答,发送端就可以认为数据已经丢失,并进行重发由此,即使产生了丢包,仍然能够保证数据能够到达对端,实现可靠传输。如果数据被重发之后若还是收不到确认应答,则进行再次发送。此时确认应答的时间将会以2倍、4信的指数函数延长。达到一定次数后,如果任没有任何确认应答返回,就会判断为网络发生异常,强制关闭连接,并且通知应用通信异常强行终止。发送端根据自己的实际情况发送数据。但是,接收端可能收到的是一个毫无关系的数据包又可能会在处理其他问题上花费一些时间。因此在为这个数据包做其他处理时会耗费一些时间,甚至在高负荷的情况下无法接收任何数据。如此一来,如果接收端将本应该接收的数据丢弃的话,就又会触发重发机制,从而导致网络流量的无端浪费。为了防止这种现象的发生,TCP 提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量。这就是所谓的流控制。它的具体操作是,接收端主机向发送端主机通知自己可以接收数据的大小,于是发送端会发送不超过这个限度的数据。该大小限度就被称作窗口大小。在前面6.4.6 节中所介绍的窗口大小的值就是由接收端主机决定的。TCP 首部中,专门有一个字段用来通知窗口大小。接收主机将自己可以接收的缓冲区大小放人这个字段中通知给发送端。这个字段的值越大,说明网络的吞吐量越高。不过,接收端的这个缓冲区一旦面临数据溢出时,窗口大小的值也会随之被设置为一个更小的值通知给发送端,从而控制数据发送量。也就是说,发送端主机会根据接收端主机的指示,对发送数据的量进行控制。这也就形成了一个完整的TCP 流控制(流量控制)。因为 TCP 的窗口控制,收发主机之间即使不再以一个数据段为单位发送确认应答,也能够连续发送大量数据包。然而,如果在通信刚开始时就发送大量数据,也可能会引发其他问题。一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。在网络出现拥堵时,如果突然发送一个较大量的数据,极有可能会导致整个网络的瘫痪。TCP 为了防止该问题的出现,在通信一开始时就会通过一个叫做慢启动的算法得出的数值,对发送数据量进行控制。首先,为了在发送端调节所要发送数据的量,定义了一个叫做“拥塞窗口”的概念。于是在慢启动的时候,将这个拥塞窗口的大小设置为1个数据段发送数据,之后每收到一次确认应答(ACK),拥塞窗口的值就加1MSS。在发送数据包时,将拥塞窗D的大小与接收端主机通知的窗口大小做比较,然后按照它们当中较小那个值,发送比其还要小的数据量。如果重发采用超时机制,那么拥塞窗口的初始值可以设置为1以后再进行慢启动修正。有了上述这些机制,就可以有效地减少通信开始时连续发包导致的网络拥堵,还可以避免网络拥塞情况的发生。慢启动算法的基本思想是当TCP开始在一个网络中传输数据或发现数据丢失并开始重发时,首先慢慢的对网路实际容量进行试探,避免由于发送了过量的数据而导致阻塞。主机发送了一个报文后就要停下来等待应答,每收到一个应答,拥塞窗口就增加一段长度,直至等于设定的阈值。比如我们可以先让发送方发一个包,等这个包被 ack 之后,我们再发 2 个包,这 2 个被 ack 之后再发 4 个包,以此类推,让一次所发的包数量慢慢增加,这就是慢启动。谈 TCP 离不开 窗口的概念,有 congestion window,receive window,sliding window 等等。window 是以 tcp segment 数量为单位,我们可以说当前 window 值由几个 tcp 包构成,而当我们说 window size 的时候,又是在说一个 window 所包含的字节数。window size 除了和 tcp segment 的数量有关之外,还和单个 tcp segment 的最大 size 有关,即 MSS 值。发送方的 Window 大小称之为 CWND(congestion window),接收方的 Window 大小称之为 RWND(receiver window,或 advertised window)。CWND 表示当前发送方可以发送多少个 TCP 包,而 RWND 表示当前接收方还能接收多少个 TCP 包。值得注意的是,CWND 是一个发送方本地的值,并不会在网络上传输。而 RWND 则是由接收方告知发送方的,是存在于 TCP 包的协议中,会通过网络传输。比如,A主机发送给B window 大小为8192,意思是:B主机最多可以连续发送8192 字节给A主机(一般来说,8192字节就是A主机的接收缓冲区大小),如果B主机不小心发送超过8192字节,如果application 没有及时取走,则超过 8192 自己数据可能会因为A主机的接收缓冲区满而被丢弃,所以B主机会严格遵守A的 RWND 的大小,如果A主机通告它的window大小为 0,则B主机一定不会发送数据。TCP首部中 Window Size 占两个byte,最大值为65535。MTU: Maximum Transmit Unit,最大传输单元,即物理接口(数据链路层)提供给其上层(通常是IP层)最大一次传输数据的大小;以普遍使用的以太网接口为例,缺省MTU=1500 Byte,这是以太网接口对IP层的约束,如果IP层有<=1500 byte 需要发送,只需要一个IP包就可以完成发送任务;如果IP层有> 1500 byte 数据需要发送,需要分片才能完成发送,这些分片有一个共同点,即IP Header ID相同。MSS:Maximum Segment Size ,TCP提交给IP层最大分段大小,不包含TCP Header和 TCP Option,只包含TCP Payload ,MSS是TCP用来限制application层最大的发送字节数。如果底层物理接口MTU= 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果application 有2000 byte发送,需要两个segment才可以完成发送,第一个TCP segment = 1460,第二个TCP segment = 540。Persist Timer: 用于周期探测对方receiver window size 是否依然为0的定时器。比如,A主机通告它的window大小为 0,则B一定不会发送数据。B主机也不会一直等下去,如果一直等下去则会发生死锁。为了防止这种情况的死锁发生,发送者使用了一个持续计时器(persiet timer)来周期性的询问接收者是否已增加了窗口。从发送者发出的这些段称为窗口探测(window probes)。在iOS设备上抓包比较方便,除了常用的,如:Charles、Paw 等软件外,我们还可以使用tcpdump。以下是抓包的步骤:(待续)

浅谈TCP(2):流量控制与拥塞控制
上文 浅谈TCP(1):状态机与重传机制 介绍了TCP的状态机与重传机制。本文介绍 流量控制 (Flow Control,简称流控)与 拥塞控制 (Congestion Control)。TCP依此保障网络的 QOS (Quality of Service)。根据前文对TCP超时重传机制的介绍,我们知道Timeout的设置对于重传非常重要:而且,这个超时时间在不同的网络环境下不同,必须动态设置。为此,TCP引入了 RTT (Round Trip Time,环回时间):一个数据包从发出去到回来的时间。这样,发送端就大约知道正常传输需要多少时间,据此计算 RTO (Retransmission TimeOut,超时重传时间)。 听起来似乎很简单:在发送方发包时记下t0,收到接收方的Ack时记一个t1,于是RTT = t1 – t0。然而,这只是一个采样,不能代表网络环境的普遍情况。RFC793 中定义了一个 经典算法 :经典算法描述了RTO计算的基本思路,但还有一个重要问题:RTT的采样取“第一次发Seq+收Ack的时间”,还是“重传Seq+收Ack的时间”?如图:问题的本质是:发送方无法区分收到的Ack对应第一次发的Seq还是重传的Seq(进入网络就都一样了)。针对该问题, Karn / Partridge 算法选择回避重传的问题: 忽略重传的样本,RTT的采样只取未产生重传的样本 。简单的忽略重传样本也有问题:假设当前的RTO很小,突然发生网络抖动,延时剧增导致要重传所有的包;由于忽略重传样本,RTO不会被更新,于是继续重传使网络更加拥堵;拥堵导致更多的重传,恶性循环直至网络瘫痪。Karn / Partridge算法用了一个取巧的办法: 只要一发生重传,就将现有的RTO值翻倍(指数回退策略),待网络恢复后再仿照经典算法逐渐平滑以降低RTO 。该算法已经做到可用,然而网络抖动对性能的影响比较大。前面两种算法均使用加权移动平均算法做平滑,这种方法的最大问题是:很难发现RTT值上的较大波动,因为被平滑掉了(1 - a比较小,即最新RTT的权重小)。针对该问题, Jacobson / Karels 算法引入了最新采样的RTT值和平滑过的SRTT值的差距做因子,即 DevRTT (Deviation RTT,RTT的偏离度),同时考虑SRTT带来的惯性和DevRTT带来的波动:Linux 2.6采用该算法计算RTO,默认取α = 0.125, β = 0.25, μ = 1, ∂ = 4(玄学调参,你懂的)。TCP使用 滑动窗口 (Sliding Window)做流量控制与 乱序重排 。乱序重排在TCP的重传机制中已经介绍,下面介绍流量控制。TCP头里有一个字段叫Window(或Advertised Window), 用于接收方通知发送方自己还有多少缓冲区可以接收数据 。发送方根据接收方的处理能力来发送数据,不会导致接收方处理不过来,是谓流量控制。暂且把Advertised Window当做滑动窗口,更容易理解滑动窗口如何完成流量控制,后面介绍拥塞控制时再说明二者的区别。观察TCP协议的发送缓冲区和接收缓冲区:假设位置序号从左向右增长(常见的读、写缓冲区设计),解释一下:据此在接收方计算 AdvertisedWindow ,在发送方计算 EffectiveWindow :AdvertisedWindow衡量接收方还能接收的数据量,发送方要根据AdvertisedWindow决定接下来发送的数据量上限,即EffectiveWindow(可能为0)。由于乱序问题的存在,LastByteRcvd可能指向Seq(LastByteSent),而Seq(LastByteAcked + 1)至Seq(LastByteSent - 1)都还在路上 ,即将到达接收方,最好的情况是不丢包(丢包后会重传), 则LastByteRcvd之后、接收缓冲区边界之前的空间就是发送方下一次发送数据的长度上限 (重传不属于下一次发送),因此, AdvertisedWindow = MaxRcvBuffer – (LastByteRcvd - LastByteRead) 。LastByteRcvd还可能指向Seq(LastByteAcked)(一个新包都没有收到) ,显然AdvertisedWindow的公式不变, 而Seq(LastByteAcked + 1)至Seq(LastByteSent)都还在路上 ,未来将到达接收方,进入接收缓冲区,则“还在路上的Seq(LastByteAcked + 1)至Seq(LastByteSent)”不应超过接收缓冲区的剩余空间AdvertisedWindow(目前等于MaxRcvBuffer),这要求的是上一次发送满足LastByteSent - LastByteAcked ≤ AdvertisedWindow, 那么LastByteSent之后、接收缓冲区剩余空间边界之前的空间就是发送方窗口内剩余可发送数据的长度上限 ,因此, EffectiveWindow = AdvertisedWindow - (LastByteSent - LastByteAcked) 。以下是一个发送缓冲区的滑动窗口:上图分为4个部分:其中, #2 + #3 组成了滑动窗口,总大小不超过AdvertisedWindow,二者比例受到接收方的处理速度与网络情况的影响(如果丢包严重或处理速度慢于发送速度,则 #2:#3 会越来越大)。以下是一个AdvertisedWindow的调整过程,EffectiveWindow随之变化:上图,我们可以看到一个处理缓慢的Server(接收端)是怎么把Client(发送端)的发送窗口size给降成0的。对于接收方来说,此时接收缓冲区确实已经满了,因此令发送方的发送窗口size降为0以暂时禁止发送是合理的。那么,等接收方的接收缓冲区再空出来,怎么通知发送方新的window size呢?针对这个问题,为TCP设计了ZWP技术(Zero Window Probe,零窗通告):发送方在窗口变成0后,会发ZWP的包给接收方,让接收方来Ack他的Window尺寸;ZWP的重传也遵循指数回退策略,默认重试3次;如果3次后window size还是0,则认为接收方出现异常,发RST重置连接(部分文章写的是重试到window size正常???)。注意:只要有等待的地方都可能出现DDoS攻击,Zero Window也不例外。一些攻击者会在和服务端建好连接发完GET请求后,就把Window设置为0,于是服务端就只能等待进行ZWP;然后攻击者再大量并发发送ZWP,把服务器端的资源耗尽。(客户端等待怎么耗服务端???)为什么要进行拥塞控制?假设网络已经出现拥塞,如果不处理拥塞,那么延时增加,出现更多丢包,触发发送方重传数据,加剧拥塞情况,继续恶性循环直至网络瘫痪。可知,拥塞控制与流量控制的适应场景和目的均不同。拥塞发生前,可避免流量过快增长拖垮网络;拥塞发生时,唯一的选择就是降低流量。主要使用4种算法完成拥塞控制:算法1、2适用于拥塞发生前,算法3适用于拥塞发生时,算法4适用于拥塞解决后(相当于拥塞发生前)。在正式介绍上述算法之前,先补充下 rwnd (Receiver Window,接收者窗口)与 cwnd (Congestion Window,拥塞窗口)的概念:介绍流量控制时,我们没有考虑cwnd,认为发送方的滑动窗口最大即为rwnd。实际上, 需要同时考虑流量控制与拥塞处理,则发送方窗口的大小不超过 min{rwnd, cwnd}。下述4种拥塞控制算法只涉及对cwnd的调整,同介绍流量控制时一样,暂且不考虑rwnd,假定滑动窗口最大为cwnd;但读者应明确rwnd、cwnd与发送方窗口大小的关系。慢启动算法 (Slow Start)作用在拥塞产生之前: 对于刚刚加入网络的连接,要一点一点的提速,不要妄图一步到位 。如下:因此,如果网速很快的话,Ack返回快,RTT短,那么,这个慢启动就一点也不慢。下图说明了这个过程:前面说过,当cwnd >= ssthresh(通常ssthresh = 65535)时,就会进入 拥塞避免算法 (Congestion Avoidance): 缓慢增长,小心翼翼的找到最优值 。如下:慢启动算法主要呈指数增长,粗犷型,速度快(“慢”是相对于一步到位而言的);而拥塞避免算法主要呈线性增长,精细型,速度慢,但更容易在不导致拥塞的情况下,找到网络环境的cwnd最优值。慢启动与拥塞避免算法作用在拥塞发生前,采取不同的策略增大cwnd;如果已经发生拥塞,则需要采取策略减小cwnd。那么,TCP如何判断当前网络拥塞了呢?很简单,如果发送方发现有Seq发送失败(表现为“丢包”),就认为网络拥塞了。丢包后,有两种重传方式,对应不同的网络情况,也就对应着两种拥塞发生时的控制算法:可以看到,不管是哪种重传方式,ssthresh都会变成cwnd的一半,仍然是 指数回退,待拥塞消失后再逐渐增长回到新的最优值 ,总体上在最优值(动态)附近震荡。回退后,根据不同的网络情况,可以选择不同的恢复算法。慢启动已经介绍过了,下面介绍快速恢复算法。如果触发了快速重传,即发送方收到至少3次相同的Ack,那么TCP认为网络情况不那么糟,也就没必要提心吊胆的,可以适当大胆的恢复。为此设计 快速恢复算法 (Fast Recovery),下面介绍TCP Reno中的实现。回顾一下,进入快速恢复之前,cwnd和sshthresh已被更新:然后,进入快速恢复算法:下面看一个简单的图示,感受拥塞控制过程中的cwnd变化:

本文由 在线网速测试 整理编辑,转载请注明出处,原文链接:https://www.wangsu123.cn/news/60446.html。