1.概述
传输层主要负责主机与目标主机之间提供端到端的数据传输服务,负责数据的分段和重组、错误控制和流量控制,以确保数据的可靠传输。主要的通信协议有:TCP、UDP
网络层提供的是端到端的服务,而运输层提供的是进程到进程之间的服务。所以,运输层服务的协议是在端系统而不是在路由器实现的
本章的主要学习内容如下:
- 传输层的工作原理
- 多路复用/解复用
- 可靠数据传输
- 流量控制
- 拥塞控制
- 学习Internet的传输层协议
- UDP:无连接传输
- TCP:面向连接的可靠传输
- TCP的拥塞控制
考虑这样的一个例子来理解运输层协议:
考虑两个A小区和B小区,A小区和B小区关系不错,所以两个小区的住户之间经常进行邮件通信,可想而知,其中的邮件量该有多么的大!如果让快递员把快件送到每家每户门口他会累死的!所以小区成立了快递站点,负责接受所有送到本小区的邮件,并把邮件分发到每个住户手上。
其中
主机 = 小区
进程 = 小区住户
应用层报文 = 包裹上的地址
运输层协议 = 小区快递站点(TCP / UDP)
网络层协议 = 邮政服务(IP)
而这是一家粗心的邮政公司,他只能尽力的把信件送到,但还是会出现丢包、信纸顺序错乱或被丢弃等情况,而TCP站点公司是一件以质量著称的公司,他会把IP邮政提供的服务做各种优化以达可靠,而UDP站点公司是一家以速度著称公司,他会直接使用IP邮政公司的服务,所有跟IP公司一样不可靠。
但是,无论是TCP公司还是UDP公司提供的服务都是基于IP公司的服务,也就是他们无法保证邮件的送达时间,也就是传输层协议无法为应用岑提供时延和带宽保证
TCP提供:
- 多路复用、解复用
- 拥塞控制
- 流量控制
- 建立连接
UDP提供:
- 多路复用、解复用
- no more
2.多路复用/多路解复用
多路复用/解复用就是上一个例子中快递站干的事情——把整个小区的快递一起发送出去/把收到的快递分发到每个住户。也就是把IP提供的主机到主机之间的服务,细化为进程到进程之间的服务(现在可能是线程之间了,不过先统一叫进程)。每一个进程都与一个套接字(socket)相捆绑,类似于每个住户门口的邮箱。
无连接的情况(UDP)
UDP的数据报报文格式如上,但是,虽然UDP报文头部包含了源端口和目标端口(源IP和目标IP在IP报文首部),但是UDP的socket是只由目标IP和目标端口这个二元组唯一确定的,也就是说,如果有两个不同的源IP或端口号的进程向同一个IP的同一个端口发送UDP数据报,那么会被定位到相同的进程。
面向连接的情况(TCP)
与UDP不同的是,TCP的socket是由(源IP,源端口,目标IP,目标端口)这个四元组确定的,任何一个不同都会被定位到不同的进程。
周知端口号
端口号是一个16bit的数,也就是说它的范围是0~65525之间。但是,0~1023范围内的端口号被称为周知端口号,例如,HTTP使用80号端口,FTP使用21号端口。
一般来说,进程会被自动分配一个1024~65535的端口号,但是,如果你实现了一个“周知协议”的服务器端,就必须为他分配一个周知端口号
采用周知端口号有以下意义:
-
标识特定服务:周知端口通常与特定的网络服务或协议关联。比如,HTTP通常使用80端口,HTTPS通常使用443端口,FTP通常使用20和21端口,而SSH通常使用22端口。通过这些周知端口,客户端可以很容易地找到并连接到提供特定服务的服务器。
-
简化网络通信:由于周知端口与特定服务关联,因此客户端无需每次都询问服务器某项服务的端口号,只需直接连接到对应的周知端口即可。这大大简化了网络通信的过程。
-
提供一致性:不同的操作系统和软件可能会有自己的默认端口设置,但是周知端口提供了一种跨平台、跨软件的统一标准,使得不同系统和软件之间的通信变得更加容易。
3.无连接传输:UDP
UDP的特点用两个字就可以概括:快和不可靠。
UDP在IP的基础上尽可能地不为用户提供更多的服务,它只提供了一个知道存在的复用与解复用符合和差错校验服务(只能校验,不能恢复),虽然看着很不靠谱,但是他的用处还是很大的。
UDP的特性
1.UDP的报文格式
UDP的报文格式如上,头部统共只有八个字节,相较于TCP头部的20个字节,UDP报文在数据传输的过程中开销十分小。
2.无建立连接
不同于TCP协议需要在数据传中之前建立链接,UDP一声招呼都不打,直接发送。所有UDP没有建立链接带来的时延,非常的快。(事实上,应用层传来的数据在UDP这里知识加了一个小头部而已,基本没有什么耗时,相当于直接送到IP那里。)
3.无连接状态
因为没有建立链接,所以UDP也没有维护所谓的”连接状态“。TCP需要维护这些连接状态来实现拥塞控制,但是在UDP上,你发多快,UDP就发多块,拥塞控制啥的,我不到啊。
UDP的常见应用场景
DNS
DNS是一个著名的使用UDP的例子。当主机的DNS程序想要进行一次查询时,无须与名字服务器“握手”,把DNS数据报加上UDP的头部就发出去了。在传输过程中,数据报可能会丢失,此时,DNS程序会向其他名字服务器继续发送查询或者直接告诉应用进程他无法得到响应。
QUIC
虽然我UDP不提供更多服务,只有快,但是我可以在应用层实现呀!谷歌的Chrome浏览器就是这么想的。大多是HTTP协议使用的都是TCP协议,因为在web中可靠性是很重要的,但是Google的Chrome浏览器使用QUIC协议(快速UDP因特网协议),将UCP作为运输层协议,并在UDP之上的应用层协议中实现可靠性。
多媒体应用
如互联网电话、实时视频会议等大部分用的都是UDP协议以保证实时性,但是由于UDP不提供拥塞控制,会导致线路进入拥塞状态,可能会导致TCP会话速率大大降低,所以正在改善。
UDP校验和
UDP提供了一个简单的差错校验功能,用于确定报文在运输过程中是否发生了变化。
还是这个报文格式图,它先把除了校验和的三个16位的数据进行相加,如果有溢出就回卷(进位加在最低位),相加的结果取反码即为校验和。最终,接受方验证保活校验和在内的四个16位数相加是否全为一(原码加反码全为一)进行校验。
这种校验知识简单的校验,还是会发生错误的结果通过校验的情况,如101,和010传过去变成100和011,任然能通过校验。
并且,UDP只提供校验服务,不提供恢复服务。
4.可靠数据传输原理
在介绍TCP之前,我们先介绍可靠数据传输原理。可靠数据不一定在传输层实现,它在各个层级都有可能实现,所以这里说明的是更一般性的原理。你知道的,在不可靠的下层基础上实现可靠的本层是比较困难的,所以这一部分既是重点也是难点。
停等协议
我们将从无到有构建一个可靠数据传输协议(rdt)。我们先假设一切都是理想的,然后逐步加入实际中的问题,然后完善我们的协议。
我们将采用FSM(Finite-State Machine,有限状态机)的形式来可视化发送方和接收方的状态。
rdt1.0——下层完全可靠
如果下层是完全可靠的,那么本层需要做的事情就只有发送和接收而已了。
发送方FSM:
圆圈内的是发送方的某种状态,在当前条件下,发送方只有一种状态。箭头指向的是初始状态(状态多的时候会显得有用一点)。弧形箭头指的是状态的转移,横线上面的是发生转移的条件,下面的是条件发生时的操作。
但是,上面的方法每次放松一个数据分组就要等待接收方的回应才能开始下一步,所以被称为“停等协议”,资源利用率很低,下面我们将介绍流水线协议来提高信道的利用率。
流水线协议
回退N步(GBN)
回退N步(Go-Back-N,GBN)是一种流水线协议,允许一次发送多个分组但接收方一次只等待一个。书中配套的动画很直观地为我们展示了这个协议,建议结合阅读Go-Back Protocolhttps://media.pearsoncmg.com/ph/esm/ecs_kurose_compnetwork_8/cw/content/interactiveanimations/go-back-n-protocol/index.html
发送方对于需要发送的数据分组,有一个长度为N的窗口(不是无限长是为了流量控制),窗口内的数据都是“可发送”的(图中0~4号),如果已经发送出去,那么它是“等待响应”状态(图中1~3号),如果还未发送,那么就是“等待发送状态”(4~5号)。0号为已发送并成功接收状态。剩余的为无法发送状态。
发送方发送时必须按顺序发送,所以前面的一定比后面的先到,除非是重发。
不同于发送方处于可发送状态的有多个分组,接收方处于可接收状态的只有一个分组(图中为分组1),只有接收到这个分组,才能接受后面的分组(按序),图中接收到1才能接收分组2,接收到2才能接收3……
也就是说,如果1丢包了,那么接收到2和3也没用,如图:
![](https://i-blog.csdnimg.cn/blog_migrate/0800cf897b3b9b6600fa929fd85ee03a.png)
![放回成功接收的最后一个分组的ACK](https://i-blog.csdnimg.cn/blog_migrate/96b62c7e3bdc8c5e78a3fd8c7f3b01fa.png)
接收方会缓存或丢弃已接收到的2、3分组,然后返回给发送方已接受的最后一个编号的ACK(图中为返回ACK0)。由于发送方没有接收到对应的ACK,所以直到发送方的定时器超时,发送方会把这次发送的几个分组都再发送一遍
![](https://i-blog.csdnimg.cn/blog_migrate/ac4def259935fa49de5e64b10391ba84.png)
如果正确接收到对应分组,接收方返回对应的ACK
然后发送方收到ACK之后,已成功接收的分组被剔除出滑动窗口,滑动窗口右移。
如果ACK发生了丢失现象,那么以最后一个接收的ACK为准。也就是说,如果1、2都丢失了,但收到了ACK3,那么1、2、3都会被判定为成功接收。以为由于上面的规则(一次只接受一个),如果1、2未被正确接收,那么ACK3是无法发送出来的。
选择重传(SR)
选择重传(SR,selective Repeat)
老规矩,先上链接!
GBN具有的问题是每次重传都必须重传当此发送的所有分组,如果错误概率高那么这样的重传无疑是一种极大的浪费,所以SR采用选择性的重传,只重传没有收到的。
并且,发送方和接收方都有相同长度的窗口。
接收方收到数据分组时会回应正确的分组ACK,如果接收方已经存在的分组小端是有序的,那么接收方滑动窗口右移,并把有序的分组数据上传。
对于那些数据丢包或者ACK丢包的,发送方将在超时之后重传,
需要注意的是,接收方在接收到的数据有序时就会将滑动窗口右移,而发送方需要接受到的ACK有序是才能将窗口右移,所以ACK的丢包会导致发送方和接收方的滑动窗口不一致。
![](https://i-blog.csdnimg.cn/blog_migrate/dac8430fe2cf344f919e364accac199a.png)
由于SR不同于GBN一样有顺序保证,所以每次发送方发送数据接受方都需要回以正确的ACK,如图,就算数据组1已经成功接收了,下次发送方重发的时候还是要回应,因为发送方等着你的ACK1呢!不然滑动窗口移动不了啊!
这又导致了另一个问题,由于采用的是循环序号(如图所示的话就是说当前的数据组19之后的数据组序号又是0,因为序号总不可能无限长嘛!),所以如果滑动窗口过大,那么我需要重发的数据1和新编好的数据1接受方无法区分啊!所以滑动窗口长度一定要保证小于等于序号长度的一半.
5.面向传输的链接——TCP
TCP协议可谓是计网当中重磅的重磅,内容很多,都是重点!
TCP概述
TCP协议是面向链接(connection-oriented)的协议,两个进程之间必须先进行握手才能传输数据,并且,连接的数据均保存在主机端上而不是路由等网络核心之间。
一个主机所有进程想要通过TCP协议发送的数据均有主机的TCP站维护,每个进程要发送的数据通过socket发送到TCP发送缓存之中,再有TCP发送缓存按照合适的速度通过下层向各个目标主机发去。各个目标主机也有TCP接收缓存,进程从TCP接收缓存之中读取数据。并且,TCP是全双工的,也就是说每个TCP协议的双方都既能发送也能接收数据。
TCP从发送缓存中一次发出的数据受限于最大报文发送长度(Maximum Segment Size,SSM),而SSM受限于最大链路层帧长度(Maximun Transmission Unit,MTU),通常来说,SSM+TCP首部=MTU
TCP报文结构
TCP的首部结构如上图所示,一般为20个字节(比UDP多12字节),各个部分的解释如下:
源端口/目标端口
用于多路复用和解复用,TCPsocket通过(源IP,源端口,目标IP,目标端口)四元组来标识进程。
校验和字段
与UDP的作用一致
序号/确认号
都为32比特,用于实现可靠数据传输。
TCP采用字节流的方式传输数据,也就是说它并不按照应用层给它的分割好的数据报为一个单位传输,因为他要尽量传输多的数据,也就是将SSM塞满。他为每一个字节进行编号,并选定本次连接的初始编号(不一定为0,用于防止多个连接一样的号导致混淆),本次传输的第i个字节的编号为初始编号+i。对于每个数据报,他的编号是当前数据报第一个字节的编号。
TCP首部的序号即为当前传输的数据报的编号。
而由于TCP是全双工的,A传输给B数据的同时也会从B接收数据,确认号即为当前进程期望从对方进程接收的下一个数据报的编号。其作用与ACK类似,因为TCP必须保证按序传输,并且其采取累计确认方式,所以可以通过确认号的形式告诉对方自己的数据接收到哪了。因为是按需的,所以确认号是已经接受的数据的下一个字节的编号,即使发生了中间丢包的情况。
TCP的超时重传
有上一节讨论的可靠数据传输协议,TCP同样需要超时重传机制。那么,超时多少再重传呢?如果太长了,会导致效率低下;如果太短了会导致发送冗余。并且,这个时间一定不能小于数据在两进程之间的往返时间。但是,网络的情况时时在变化,所以往返时间需要进行估计。
1.往返时间估计
往返时间——RTT
TCP采用指数加权平均(EWMA)的方法对往返时间进行估计。记平均为EstimatedRTT
在每个时刻对一个进行进行往返时间测量,即为SampleRTT,每次测量都会更新EsRTT:
EsRTT = (1 - α)* EsRTT + α * SampleRTT
称为指数加权平均的原因是每个RTT的值在随时间的变化中权值指数型衰减,更新的Sample反映更近的网络情况,所以权值更大,通常α建议为0.125
除了平均之外,还会估算Sample与Es之间的偏离程度,定义为DevRTT:
DevRTT = (1 - β)* DevRTT + β * | Sample - Es |
β一般为0.25
2.设置重传时间间隔
重传时间一个尽可能小但是要比Es大,当Sample与Es差距较大时,间隔时间应该大点:
TimeoutInterval = EsRTT + DevRTT * 4
但是,超时之后,间隔时间会翻倍。这是因为超时很可能是由于网络拥塞引起的,即太多的分组到达源与目标之中的某几台路由器之中,造成分组丢失或长时间排队。此时,如果持续重传分组,会导致拥塞更加严重。不过,在收到上层应用数据或者收到ACK之后,间隔之间会恢复原来的计算方式。
3.计时器的个数
不同于上一节中讨论的可靠数据传输协议中的对每一个进程都有一个时钟进行计时,这样花费实在是太大了!TCP只开启一个时钟进行计时,可以想象定时器与最早的未被确认的报文相关联。
TCP时钟操作的伪代码如下:
//NextSeq : 下一个顺序发送的序号
//SendBase : 最前的未被确认的序号
while(1){
switch(case){
case : 从应用层收到数据data
生成具有NextSeq的TCP报文段
if(定时器未运行){
开启定时器
}
向IP发送报文段
NextSeq = NextSeq + length(NextSeq);//更新NextSeq
break;
case : 定时器超时
重传未应答的最小序号报文段;
重启定时器;
break;
case : 收到ACK(y)
if(y > Sendbase){
Sendbase = y ; //因为采用累积确认,所以后面的ACk到了前面的数据报一定都到了
if(还有未确认的报文){
重启定时器
}
}
}
}
}
4.快速重传
超时触发重传具有的问题是超时周期可能过长,此时引入的新的制度——冗余ACK快速重传
冗余ACK指的是已收到报文i的ACK之后再次收到报文i的ACK,此时的ACK称为冗余ACK。
读到这里希望你已经了解了累计重传的机制,如果还不熟悉的话请返回GBN流水线传输方式复习。简单来说,累积确认确保了数据必须按序接收,当后面的数据被确认时前面的数据一定被收到了。但是,如果目标数据报未被送达而后面的数据报被送达时,接收方只会返回目标数据报前一个数据报的ACK。也就是说此时发送方已被确认的最后一个数据报会被反复确认,产生冗余ACK。
还有一种生成冗余ACK的原因是原数据报未被丢失而是因为网络延时导致超时重传,此时有多份相同的数据报被送到接收方,接收方会产生冗余ACK。
根据实践中获得的经验,当出现三次冗余ACK时,立即发送下一个数据报有利于加快传输速度。
TCP传输与GBN方式的区别
因为都采用的是累积确认机制,所以真的很像。
考虑到数据报队列1~N,存在数据报n<N,当数据报n发生丢失时,GBN会重传n~N的所有数据报,而TCP只会重传数据报n。
因为TCP假设接收方会把所以已经收到的数据报缓存,当它收到重传的n时,返回N的ACK就可以了。但是GBN假设接收方将所有的已接收但失序的数据报丢弃,所以需要重传所有并且返回所有ACK。
流量控制
由于发送方和接收方对数据的传输和读取速度不同,所以TCP需要采取流量控制服务(flow-control service)。对这一部分也有相应的动画展示:
考虑这样一个模型:
发送方和接收方均存在一个缓存区,就像最开始概述时说的那样。
对于接收方:
设RcvBuffer为缓存区大小,LastRead为最后一个读取的字节,LastRcv为最后写入的字节。
则LastRcv - LastRead即为尚未读取的字节。
设可用空间为rwnd,rwnd = RcvBuffer - (LastRcv - LastRead)
对于发送方:
设LastSend为最后一个发送的字节,LastACK为最后一个确认接受的字节。
所有LastSend - LastACK为接收方应该接收到的字节。
显然发送方需要保证LastSend - LastACK <= rwnd,即发送中的字节小于等于接收方缓冲区剩余空间。为了保证这一点,接收方每次的ACK确认报都会包含它的缓冲区剩余空间,让发送方悠着点!
这就出现了一个问题,当某条数据报发过之后,接收方剩余空间rwnd为0了,那么之后发送方便不会向接收方发送数据,那么接收方也就没有ACK回复,这就尴尬了!死锁了!所有引入一个机制来改变这种情况,就是当接收方发送剩余空间为0的报文给发送方之后,发送还会发送1比特的数据给接收方,让接收方可以返回报文(相信我,1比特的空间很快会清理出来的)。
TCP管理链接
本节我们将讨论TCP建立连接的细节,众所周知,TCP建立连接需要进行“三次握手”,那么三次握手具体是什么呢?
建立TCP连接
当客户端向服务端请求建立TCP连接时,会进行以下三步:
第一步:客户端发送SYN报文段。客户端向服务器发送一个不包含应用层数据的特殊TCP数据报,其中数据报首部的SYN位被置为1,表示请求建立TCP连接。并且会随机地选择一个初始序号(client_isn)放置在序号字段中。
第二步:服务器发送SYNACK报文。当服务器的SYN报到达服务器之后,服务器会为本次连接分配TCP缓存和变量(注意伏笔!),之后,服务器也会为客户端发送报文,同样不含数据,同样SYN位置为1,然后确认号字段为client_isn+1,并选择自己的初始序号(server_isn),这个报文称为SYNACK报文。
第三步:客户端收到SYNACK报文之后,为本次TCP连接分配缓存和变量,并且本次传输的报文可以携带数据并且SYN为为0。本次连接主要是为了确认双方均具有收发能力。
终止TCP连接:
客户端(也可以是服务器)向对方发送一个首部FIN位为1的特殊报文报文并等待ACK,收到之后等待对方也发送一个FIN报文并且本方返还一个ACK,连接终止。
SYN洪泛攻击
如上的伏笔所说,服务器在连接正式完全建立之前分配了缓存,那么你很容易就想到一个攻击方式:疯狂请求建立,但就是不完成握手,浪费服务器的内存。
这种攻击的抵御方式是为每个IP和端口映射一个整数(cookie),SYNACK报文的初始序号就是cookie,如果第三次握手的确认序号是cookie+1才为此次连接分配缓存。简单来说就是确认安全后才分配。
TCP拥塞控制
TCP拥塞控制主要是由控制发送方的可发送窗口大小实现的。即为cwnd。
在拥塞控制方面,发送端具有三种状态——慢启动、拥塞避免和快速恢复。其中快速恢复是推荐的而不是必须的。
慢启动(slow-start)
慢启动,顾名思义是对窗口大小进行启动。初始时处于慢启动状态,并设这cwnd为一个MSS,当一个RTT周期内未发生丢包现象和冗余ACK现象时,cwnd翻倍。细化来说就是每收到一个确认ACk就增加一个MSS。
初始发一个,收到一个,所以增加一个,然后发两个,收到两个所以增加两个,然后发四个……
可以发现慢启动状态cwnd指数级增加,增加速度非常快,所以必然会导致拥塞丢包的产生。当拥塞发生时,记ssthresh = cwnd/2,即拥塞发生时的一半,也就是拥塞未发生时的极限状态。(此标记是用于记录发送窗口最大能到多少,当下次慢启动状态cwnd到达此大小时,进入拥塞避免状态。)然后重置cwnd = SSM。
void init() {
cwnd = MSS; //窗口大小
ssthresh = 0; //预估极限传输的大小
dupAck = 0; //冗余ACK的数量
}
void slow_start() {
if (收到新的未确认的ACK) {
cwnd += MSS; //指数增长
}
else if (超时) {
cwnd = MSS;
dupACK = 0;
ssthresh = cwnd / 2; //预估为上次的极限
}
else if (收到冗余ACK) {
dupACK++;
if (dupAck >= 3) {
ssthresh = cwnd / 2;
cwnd = ssthresh + 3 * MSS;
quick_recover();
}
}
else if (cwnd >= ssthresh) {
//当前窗口大于预期极限,进入拥塞避免
CA();
}
}
拥塞避免
每个RTT周期只增加一个MSS,即每收到一次非冗余ACK确认时,cwnd + MSS/cwnd。超时时,讲cwnd置为一个MSS,更新ssthresh = cwnd/2,并进入慢启动状态。
void CA() {
if (收到新的未确认的ACK) {
cwnd += MSS / cwnd; //线性增长
}
else if (超时) {
cwnd = MSS;
dupACK = 0;
ssthresh = cwnd / 2; //预估为上次的极限
show_start();
}
else if (收到冗余ACK) {
dupACK++;
if (dupAck >= 3) {
ssthresh = cwnd / 2;
cwnd = ssthresh + 3 * MSS;
quick_recover();
}
}
}
快速恢复
如果说丢包的产生很明确地说明了网络是拥塞的,那么冗余ACK只能说是暗示了网络可能拥塞了。快速恢复状态就是为了在这种暗示的网络拥塞情况下,更快的恢复传输速度。
当慢启动状态或者拥塞避免状态发生三个冗余ACK的产生时,进入快速恢复状态,
ssthresh = cwnd/2 ,cwnd = ssthresh+3*MSS
当再次收到冗余ACK时,cwnd += MSS
收到新的ACK : 进入拥塞避免状态,cwnd = ssthresh
超时 : 进入慢启动,cwnd = MSS,ssthresh = cwnd/2
void quick_recover() {
if (收到冗余ACK) {
cwnd += MSS; //指数增长
}
else if (超时) {
cwnd = MSS;
dupACK = 0;
ssthresh = cwnd / 2; //预估为上次的极限
show_start();
}
else if (收到新的未确认的ACK) {
cwnd = ssthresh;
dupACk = 0;
CA();
}
}
宏观
TCP发送方的发送速度同时取决于用于拥塞控制的cwnd和用于流量控制的rwnd,取二者之中较小的。
因为慢启动和快速恢复状态是指数级增长,所以持续的时间很短,TCP大部分时间都处于拥塞避免状态,从宏观上的图像来看呈现锯齿形(下图仅为示意,最高速度实时变化):线性增长到极限然后降为一半,然后接着线性增长。也称这种特性为“加性增,乘性减”
![](https://i-blog.csdnimg.cn/blog_migrate/8922eb97ac1f2c5c75de8203e1399e46.png)
经过一系列推到得出理想化平均吞吐量为 0.75 * W / RTT,W为平均窗口长度。
新的TCP协议——TCP CUBIC
前面我们介绍的是最经典的TCP Reno协议。
近期,人们考虑到加性增,乘性减的方式可能过于谨慎了,因为每次都下降到一半然后缓慢的增加,人们仍未我们应该在里极限远的时候加的快一点,近的时候时候慢一点,而不是线性地增加。
所以TCP CUBIC仅与TCP Reno在拥塞避免状态的增长方式不同。如下图,蓝色线为Reno的线性增长,红色线为CUBIC的快速接近增长。