概述
运输层协议为运行在不同主机上的应用进程提供了逻辑通信。最上面两层协议栈的服务都应用在端系统中,应用层的报文要么是往下发送给运输层,要么是从运输层接收解析后的报文,所以逻辑上运输层给应用层提供了逻辑通信服务,但是运输层只是在整个通讯过程中(如下图中画粗线的通信链路)扮演了其中一个角色。

因特网为应用层提供了两种运输层协议,一种是UDP,它为应用程序提供一种不可靠、无连接服务;TCP则为应用程序提供一种可靠的、面向连接的服务。
运输层提供的服务
任何服务都是基于用户进行讨论的,因为网络不能基于具体的用户进行设计,所以在计算机网络的五层架构中,我们把用户抽象成架构的上级层次,运输层的上层即应用层,在这我们把应用层对运输层服务的要求大体分为四类:可靠数据传输、吞吐量、定时和安全性。
可靠数据传输
确保由应用程序的一端发送的数据正确、完全地交付给该应用程序的另一端。若过一个协议提供了这样的确保数据交付服务,就认为提供了可靠数据传输。一般的金融类应用都需要有这样的要求,例如A用户转账给B用户,如果报文在传输过程中丢失了,且没有重传措施,转账的这笔钱就会凭空消失,给客户造成不必要的损失。
吞吐量
在沿一条网络路径上的两个路径之间的通信会话场景中,可用吞吐量就是发送进程能够向接收进程交付比特的速率。因为其他会话将共享沿着该网络路径的带宽,并且因为这些会话将会到达和离开,该可用吞吐量将随时间波动。保证一定吞吐量的服务对一些应用是很有吸引力的,因为某些应用的正常使用必须保证单位时间内最低限度的完整数据,例如大型网络游戏,在低于一定带宽时会掉线,不能正常进行游戏。
定时
一个保证定时的例子如:发送方注入进套接字中的每个比特到达接收方的套接字不迟于100ms。这种服务对对实时性要求较高的应用有较高的吸引力。
安全性
保证应用层数据不被篡改、监听等恶意行为的服务就是安全服务。运输层协议一般可以通过在发送端加密并在接收端解密实现,但是安全性问题多种多样,并没有万金油解法,不同的场景的解决方式也是不尽相同。
运输层和网络层的关系
我们知道了运输层给主机中的应用进程提供了逻辑通讯服务,作为运输层的直接下层,网络层则是提供了主机间的逻辑通讯服务。为了描述这种细微的差别,我们给出一个合理地比喻:考虑有两个富人家庭,一家位于中国东北,一家位于中国上海,两个家庭都有20个孩子,每周这两个家庭中的每一个孩子都会给另一个家庭中的所有孩子每人写一封信;两个家庭中分别安排了一个孩子每周收集兄弟姐妹的信交给快递员或将收到的信件分发给兄弟姐妹们,东北这家由维辑小朋友负责,而上海这家则是由李维负责。
在这个例子中邮政服务为两个家庭提供逻辑通信,邮政服务将信件从一个家庭送往另一个家庭,另一方面,李维和维辑负责将从邮递员送来的信件分发给兄弟姐妹们,他们提供了两个家庭孩子们的逻辑通信。仅在两个家庭的信件交互中,我们可以做一下类比:
应用程序进程 <==> 兄弟姐妹
主机 <==> 家庭
应用层报文 <==> 信件
运输层协议 <==> 李维和维辑
网络层协议 <==> 邮政服务
运输层提供的服务
运输层提供的服务可以分为两类,一类是基于底层协议提供的,一类是运输层自己实现的,本节我们主要对运输层提供的多个服务做原理性的介绍。运输层实现的服务包括多路复用与多路分解(TCP和UDP)、可靠传输服务(TCP)、流量控制(TCP)和拥塞控制(TCP),其中多路复用和多路分解是运输层提供的服务,而TCP则实现了可靠传输服务、流量控制和拥塞控制等服务。
多路复用与多路分解
将主机间交付扩展到进程间交付被称为运输层的多路复用和多路分解。

网络应用程序的进程有一个或多个套接字,它相当于从网络向进程传递数据和从进程向网络传递数据的门户。因此,运输层和应用层进程间不是直接进行数据交互的,而要通过套接字进行间接交互。比如在接收主机中的运输层实际上并没有直接将数据交付给进程,而是将数据交给了一个中间的套接字。又由于在任一时刻,在接收主机上可能不只有一个套接字,所以每个套接字都需要一个唯一的标志符。而TCP和UDP有不同的标志符格式。
将运输层报文段中的数据交付到正确的套接字的工作称为多路分解。
在源主机从不同套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层,所有这些工作称为多路复用。
要实现以上两点我们需要要做到以下两点:
- 套接字有唯一标志符
- 每个报文段有特殊的字段来指示该报文段索要交付到的套接字
我们将这个唯一的标志符叫做端口号,而每一个套接字能够分配一个端口号,在报文段中存在源端口号和目的端口号这两个特殊字段,用来标志发送端应用和接收端应用的套接字。
我们可以简单模拟一下在运输层发送和接收数据的过程:在发送端主机,应用程序将生成报文,然后发给套接字进行编码,运输层将套接字中的数据块收集起来,并为每个数据块封装上首部信息(包括源端口号和目的端口号),从而生成报文段,并扔给网络层;在接收端,运输层拿到网络层解析出的报文段,检查其中的目的端口号,并将其定向到相应的套接字。
一个UDP套接字是由一个二元组全面标识的,该二元组包含一个目的IP地址和一个目的端口号,即UDP的套接字标志符是目的IP地址和目的端口号的组合。
一个TCP套接字是由一个四元组来标识的,该四元组包含源IP地址、源端口号、目的IP地址和目的端口号。
当接收端接收到报文段后,可以将源IP地址和源端口号与目的IP地址和目的端口号交换来确定响应报文段的这四个首部字段。这一点UDP和TCP是一样的。
通过比较后会发现,基于UDP协议,可以同时存在多个发送端进程给一个接收端套接字发送数据的情况,但是基于TCP协议,在建立连接后,则是同时只能有一个发送端进程给一个接收端套接字发送数据。同样将主机比作家庭,并将进程比作家庭成员,在此之上我们将发送端套接字和接收端套接字比作一男一女的话,则可以将UDP比喻成能一妻多夫的法律规定,将TCP比喻成能只能一夫一妻的法律规定。



探究一个问题:一个应用中TCP连接的上限是否是端口号的上限65536?
首先清楚以下几点:
- 将运输层报文段中的数据交付到正确的套接字的工作成为多路分解
- 每个套接字都有唯一标志符
- TCP标志符为四元组:源IP地址、源端口号、目的端口地址和目的端口号
- 端口号用于标识进程
- 套接字和进程间或端口间不是一对一关系,一个套接字只能分配给一个端口,但是一个端口可以和多个套接字关联
- TCP首部有一个特定“连接建立位”标志位用来区分“连接建立”请求和其他请求
以web应用为例,当发送方发送方发送连接请求时,接收方在80端口接收到请求报文段后,通过判断“连接建立”标志位,判断出当前是一个“连接建立“的请求,它就定位服务器进程,该进程会创建一个新的套接字用来处理当前连接的请求(同样与80端口关联);建立连接后,当发送端发送资源请求时,运输层会根据四元组将报文段分解给对应的套接字,这样就能进行基于连接的数据交互了。
综上,TCP中一个新的连接对应一个新的套接字,但是新建的套接字并不会重新随机分配新的端口,仍然是跟原有端口相关联,所以理论上一个基于TCP的应用的服务器端中的最大连接是源IP的上限*源端口号的上限,远远不止65536。
可靠数据传输
可靠数据传输是为上层实体提供了服务抽象:数据可以通过一条可靠的信道进行传输。基于可靠信道,传输数据比特就不会受到损坏(由0变1)或丢失,并且所有数据都是按照其发送顺序进行交付。这恰好就是TCP向调用它的因特网应用提供的服务模型。

实现可靠数据传输的是可靠数据传输协议。我们将从最简单的数据传输场景出发,逐步还原出一般的真实场景,并给出在不同场景下实现可靠传输的协议实现。
首先我们给出背景前提:
- 下层协议不一定提供可靠传输
- 假设分组将以它们发送的次序进行交付,也就是说,底层信道将不会对分组重排序
我们用有限状态机表示事件流程如何响应,并用时序图来补充说明。
经完全可靠信道的可靠数据传输:rdt1.0
最简单的情况:底层信道提供可靠数据传输。此时的有限状态机表示的协议如下图所示。因为底层提供可靠数据传输,所以发送方只需要发送数据就行了,不用担心数据是否正确传输或丢失的情况,全权由底层协议负责。而接收方也只用接收分组就足够了。


经具有比特差错信道的可靠数据传输:rdt2.0
该场景下,假设底层信道中的分组可能受损。因为不像上一个场景数据传输必定会成功,为了让发送方了解到发送结果,接收方需要进行反馈,即通过响应分组的形式,而这时数据传输有两种结果,一种是没有比特差错,一种是存在比特差错(先假设通过校验和checksum进行差错检测),根据是否有比特差错,我们把响应分组分为”否定确认”(NAK)和”肯定确认”(ACK)。而当发送方接收到否定确认时,发送方将重传该分组。


上面的rdt2.0协议忽略了一个问题,即ACK和NAK分组也可能受损,所以在ACK和NAK分组中也要加入校验和用于判断分组是否受损,但是问题在于在接收方发送的ACK或NAK受损后,发送方无法得知接收方是否收到了自己发送的分组。这里有三种解决方式:
- 发送端发送一个询问分组用以询问接收端是否接收到了分组。此时如果这个询问报文再次出现比特差错,则接收端本能的会猜测发送端可能存在的两种意图:一种是发送端正常接收到自己的响应分组后,继续发送的后续分组;第二种是发送端收到的响应分组受损,而后发送端发送了询问分组,于是,这时就出现了两种分支情况,后续应答将变得模糊。
- 增加足够的检验和比特,使发送方不仅可以检测差错,还可以恢复差错。对于会产生差错但不丢失分组的信道,这就可以直接解决问题。
- 当发送方收到含糊不清的ACK或NAK分组时,只需重传当前数据分组即可。这种方法的副作用是在发送方到接收方的信道中引入了冗余分组。冗余分组的问题在于接收方现在会接收到两种分组,一种是新分组,一种是冗余分组,因为接收方并不知道自己发送的ACK或NAK是否被发送方接受到,所以无法判断接收到的分组是新分组还是冗余分组。
我们现在采用第三种方式作为ACK或NAK受损后通信的解决方案,并在协议中加入分组序号字段用于存储分组的序号,以此解决接收方的分组辨识问题。


协议rdt2.1使用了从接收方到发送方的肯定确认和否定确认。当接收到时序的分组时,接收方对所接收的分组发送一个肯定确认。如果收到受损的分组,则接收方将发送一个否定确认。如果不发送NAK,而是对上次正确接收的分组发送一个ACK,我们也能实现与NAK一样的效果。发送方接收到对同一个分组的两个ACK(即接收冗余ACK)后,就知道接收方没有正确接收到跟在被确认两次的分组后面的分组。rdt2.2是在有比特差错信道上时效内的一个无NAK的可靠数据传输协议。


经具有比特差错和丢包信道的可靠数据传输:rdt3.0
该场景下,底层信道不光可能会产生比特差错,还可能会产生丢包。现在协议必须解决两个问题,一个是检测丢包,再一个是在发生丢包后该做些什么进行处理。
我们让发送方负责检测和恢复丢包。丢包可以大体分为两种情况一种是发送方的发送分组丢失,一种是接收方响应的分组丢失,不管哪种情况都会造成发送方无法进行后续应答的后果,我们如何解决这个问题呢?比较普遍的解决方式就是通过等待足够长的时间来确定分组已经丢失,并在确定丢失后重传分组。基于这种解决方式,我们在发送方数据传输时加入定时器进行计时,当计时时间到时,如果没收到响应分组则会进行重传。
注意一点,有可能会出现发送方收到响应分组的时延长于定时时延的情况,这种情况下,发送方接收到响应分组前,定时器到时之后,仍会发送重传分组,这时就会产生冗余分组,这里冗余分组的问题可以沿用rdt2.2的冗余分组解决方案进行有效处理。


我们可以通过时序图观察到协议3.0在一般情况下、有分组丢包情况和过早超时情况下的运作情况。




流水线可靠数据传输:rdt4.0
rdt3.0是一个功能正确的协议,但是性能上并不能令人满意,原因在于它是一个停等协议。停等协议顾名思义,会在发送完一个分组后停下来等待,直到确认接收方收到分组后才会发送后续的分组。因为发送一个分组后需要等待一个往返时延,信道利用率低,所以我们改用流水线的方式发送分组。而采用流水线方式进行通讯让我们不得不对之前的协议做一些改进:
- 增加序号范围。因为每个输送中的分组必须有一个唯一的序号,让分组的接收和确认不至于产生紊乱。
- 协议的发送方和接收方两端也许不得不缓存多个分组。
- 所需序号范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏及延时过大的分组。解决流水线的差错恢复有两种基本方法是:回退N步和选择重传
首先介绍一下流水线分组发送的基本形式。如下图所示为发送方看到的流水线的分组序号,其中一个长方形代表一个分组序号。我们一般可以将其为四个部分,第一个部分为已发送并已确认的分组序号,如图中绿色部分所示;第二个部分为已发送但未确认的分组序号,如图中黄色部分所示;第三部分为滑动窗口内的未发送但将要发送的分组序号,如图中蓝色部分所示,如果有数据来自上层,将立即发送;第四部分为不能使用的序号,直到滑动窗口移动到这些序号为止。所谓滑动窗口,即那些已发送或即将发送分组的许可序号范围,它可被看成是一个在序号范围内长度为N的窗口。随着协议的运行,该窗口在序号控件向前滑动。因此,N常被称为窗口长度,GBN协议也常被称为滑动窗口协议。我们可以在后面的小节中看到为什么要设定滑动窗口长度N,主要是用于流量控制和TCP拥塞控制。

回退N步
通过下列图我们将看到GBN是如何运作的。

在这滑动窗口只会在基序号的分组发送后开始基序号的计时器,滑动窗口的后续分组可以有序发送,但是不会单独计时。当确认基序号收到ACK响应时,当前序号停止计时,滑动窗口往后移动,并基于移动后的基序号进行计时。当发送方向接收方发送分组过程中出现丢包或者接收方收到有比特差错的分组时,发送方的计时器会出现超时,这时发送方会重新发送超时的分组,并重新计时,直到该分组被确认就收到为止。
选择重传
GBN在某些情况下存在性能问题,当滑动窗口很大时,单个分组差错会导致大量的分组重传,而当分组差错概率变高时,很多分组将被重传复数次,而重复分组的传输将会拖慢整体分组的传输效率。如何解决?分析以上问题,不难得出问题出在反复重传的分组上,在GBN中,即使在发送方正确地接收到了后续分组的ACK响应报文,在基序号的计时器超时之后,基序号以后的分组依然难逃重传的命运。而选择重传协议有效解决了这个问题,它通过给每一个分组序号一个定时器,缓存正确接收的分组,并在超时后只重传超时序号的分组来实现之。具体的运行过程如下图所示。

流量控制
流量控制服务的目的在于平衡发送方的发送速率和接收方的读取速率。
拥塞控制
拥塞控制原因与代价
情况1:两个发送方和一台具有无穷大缓存的路由器。这种情况下存在一种网络拥塞的代价,即当分组的到达速率接近链路容量时,分组经历巨大的排队时延。
情况2:两个发送方和一台具有有限缓存的路由器。这种情况下会出现另一种网络拥塞代价:发送方必须执行重传以补偿因为缓存溢出而丢弃的分组。
情况3:4个发送方和具有有限缓存的多台路由器及多跳路径:此时,出现的另一种拥塞控制代价是:当一个分组沿一条路径被丢弃时,每个上游路由器用于转发该分组到丢弃该分组而使用的传输容量最终被浪费掉了。
拥塞控制方法
我们可以通过网络层是否为运输层拥塞控制提供了显示帮助,来区分拥塞控制方法。
- 端到端拥塞控制。在端到端拥塞控制方法中,网络层没有为运输层拥塞控制提供显示支持。即使网络中存在拥塞,端系统也必须通过对网络行为的观察来推断之。
- 网络辅助的拥塞控制。在网络辅助的拥塞控制中,路由器向发送方提供关于网络中拥塞状态的显式反馈信息。
运输层协议
无连接运输层协议:UDP
UDP相较于TCP有以下特点:
- 关于发送什么数据以及何时发送的应用层控制更为精细。采用UDP时,只要应用进程将数据传递给UDP,UDP就会将此数据打包进UDP报文段并立即将其传递给网络层。
- 无需建立连接。TCP在传输数据之前需要经过三次握手。UDP则不需要任何准备即可进行数据传输。因此UDP不会引入建立连接的时延。
- 无连接状态。TCP需要在端系统中维护连接状态。此链接状态包括接收和发送缓存、拥塞控制参数以及序号与确认号的参数。另一方面,UDP不维护连接状态,也不跟踪这些参数。因此,某些专门用于某种特定应用的服务器当应用程序运行在UDP之上而不是TCP之上时,一般都能支持更多的活跃用户。
- 分组首部开销小。每个TCP报文段都有20字节的首部开销,而UDP仅有8字节的开销

UDP报文结构

面向连接的运输:TCP
TCP报文段结构

- 源端口号和目的端口号。被用于多路复用或分解来自或方法送给上层应用的数据。
- 32比特序号字段和32比特的确认号字段。这些字段被TCP发送方和接收方用来实现可靠数据传输服务。
- 4比特的首部长度字段。该字段指示了以32比特的字为单位的TCP首部长度。
- 8比特的标志字段。ACK比特用于指示确认字段中的值是有效的,即该报文段包括一个对已被成功接收报文段的确认。RST、SYN和FIN比特用于连接建立和拆除。拥塞控制中使用了CWR和ECE。当PSH比特被置位时,就只是接收方应立即将数据交给上层。最后,URG比特用来指示报文段里存在着被发送端的上层实体置为“紧急”的数据。
- 检验和字段。用于判断是否存在比特差错。
- 16比特的接收窗口字段,该字段用于流量控制。
TCP连接
TCP连接的组成包括:一台主机上的缓存、变量和与进程连接的套接字、以及另一台主机上的另一个缓存、变量和与进程连接的套接字。
TCP连接有一下特点:
- TCP连接是一条逻辑连接,而不是一条物理连接。之所以称为连接是因为它在一段时间内维系了发送方和接收方之间特定进程间的通信,并且是通过保留在两个通信端系统中的共享状态字段来实现的,所以是逻辑上的。
- TCP连接提供全双工服务。允许两个方向上同时传输,即可以在发送方传输数据给接收方的同时,接收方传输数据给发送方。
- TCP连接是点对点的。
在整个TCP连接中我们一般会经历三个阶段,分别是:TCP连接建立阶段,TCP连接数据通信阶段,TCP连接拆除阶段。
TCP连接建立:三次握手
在通过TCP连接传输数据之前,需要进行TCP连接的建立,而建立连接的过程也就是我们常说的“三次握手”,主要包括以下几个步骤:
- 第一步:发送连接请求。客户端的TCP会向服务端的TCP发送一个特殊报文,该报文的SYN标志位被置为1,不包含应用层数据,所以被称为SYN报文段。另外客户会随机地选择一个初始序号,并将其放在TCP的序号字段中。
- 第二步:接收方响应连接请求。当接收方正确接收到客户发送的连接请求报文段时,会提取出报文段字段数据,并为该TCP连接分配TCP缓存和变量,并向客户发送允许连接的报文段。在此报文段中,SYN标志位依然被置为1,且确认号字段被赋值为client_isn+1,并且服务器TCP会随机选择一个初始序号放在TCP的序号字段中,且仍然不包含应用层数据。该报文被称为SYNACK报文段。
- 第三部:客户端端响应。在收到SYNACK报文段后,客户也要给该连接分配缓存和变量。客户主机向服务器发送一个报文段,以确认其收到SYNACK报文段。因为连接已经建立了,所以此报文段中SYN被置为0,且可以携带应用层负载。
可以将三次握手的目的简化成确认对方能听到自己发送的消息,可以将三次握手的过程比喻成这样一个过程。当你在网上交友软件中看到了自己中意的异性,彼此有了一定了解后你决定通过电话进行更直接的交流,假设此时的电话信号不那么尽如人意,首先你会需要打电话给她,现在电话接通了,“喂,你好,听得到吗?”(第一次握手),“你好,我能听到你说话,你能听到我说话吗?”(第二次握手),“可以的,我可以听到你说话”(第三次握手),在通过神奇的三次握手后你们就能愉快的交流了。
TCP连接后的数据通信

一旦建立连接,两个应用进程间就能互相发送数据了。我们考虑客户和服务器数据交互的场景。首先客户会通过套接字将数据传递给客户端的TCP,TCP将这些数据引导到该连接的发送缓存(三次握手中初始化的)中,当要发送数据时,TCP会为每块客户数据配上一个TCP首部,从而形成多个TCP报文段。报文段会交由网络层进行后续包装和发送。当接收方收到TCP报文段后,会将报文段中的数据缓存在该TCP连接的接收缓存中,应用程序从此缓存中读取数据流。该连接的每一端都有格子的发送缓存和接收缓存。
TCP连接关闭:四次挥手
四次挥手相对于三次握手更为简单。当客户要关闭连接时,它会发送一个FIN比特置位为1的终止请求报文段给服务端,并同时关闭客户端向服务端的数据传输,当服务端收到FIN报文段后,会返回一个确认报文。同样服务端也会发送一个自己的终止报文段给客户,FIN比特置位为1,并同时关闭服务端向客户的数据传送,客户收到服务端的终止报文后会返回一个确认报文,此时,这两台主机上用于该连接的所有资源都被释放了。
TCP流量控制
一条TCP连接的每一侧都为该连接设置了接收缓存。当TCP连接收到正确按序的字节后,它就将数据放入接收缓存。但是只有当上层应用处于等待接收缓存的状态时才能读取缓存,如果上层应用处于忙碌状态,则会推迟缓存的读取时间。因此,当发送方发送速率过快,接收方读取速率减缓时,接收缓存中的数据将会越来越多,并最终占满,这时仍有数据发送过来则会让缓存溢出,导致丢包事件,增加了重传分组的数量,变相增加了每一个分组的平均交互次数,增加了传输信道的负载。所以为了避免这样的事情发生,增加流量控制服务,TCP通过让发送方维护一个称为接受窗口的变量来提供流量控制(接受窗口用于告诉发送方接收方还有多少可用的缓存空间),而只要确保lastseqnum-base<=rwnd,就能保证接受窗口不会溢出,所以最后在发送端控制发送窗口的大小即lastseqnum-base<=rwnd,我们就能实现TCP流量控制。
TCP拥塞控制
TCP使用的是端到端拥塞控制。TCP让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率,以此来达到端到端拥塞控制的目的。但是使用此方法存在三个问题:
- 一个TCP发送方如何限制其向连接的流量发送速率
- 发送方如何感知连接链路上的网络拥塞
- 当出现拥塞时,采用何种算法改变其发送速率
一个TCP发送方如何限制其向连接的流量发送速率
我们知道每一端都有一个发送缓存和接收缓存,且发送方有一个拥塞窗口,同一时间内待确认的分组数不会超过拥塞控制窗口和接收窗口的大小,现假设接收窗口为无限大,并忽略发送时延和丢包,同一时间发送一个拥塞窗口大小的数据需要等待一个往返时延的时间(RTT),因此,该发送方的发送速率大概是cwnd/RTT字节/秒。可以通过调节cwnd的值,发送方因此能调整它向连接发送数据的速率。
发送方如何感知连接链路上的网络拥塞
出现丢包事件即表示存在网络拥塞。
当出现拥塞时,采用何种算法改变其发送速率
我们想要确定这样的发送速率,它既使得网络不会拥塞,又要能充分利用所有可用的带宽。又因为TCP使用的是端到端拥塞控制,所以只能通过接收方的响应来推断网络的拥塞情况。基于以上前提,TCP通过以下指导性原则来设置发送速率:
- 一个丢失的报文段意味着拥塞,因此当丢失报文段时应当降低TCP发送速率。
- 一个确认报文段表示该网络正确地交付了数据,该网络此时能成功交付报文段,所以没有拥塞。因此当收到确认报文段时应当增加发送速率。
- 带宽探测。ACK确认报文表示无拥塞,出现丢包表示有拥塞,为了探测出刚好不出现拥塞时的发送速率,在未出现拥塞时,TCP会持续加快发送速率,直到出现拥塞,然后基于出现拥塞时的发送速率进行降速,降到一定程度,再进行加速,试探下一个拥塞出现的发送速率,循环往复。因为每时每刻都有新的连接建立或断开,并且网络中的每一个连接都处于变化中,所以每一个连接可能存在的刚好不出现拥塞的”最佳速率”是动态变化的,所以TCP发送速率的探测也是一个周而复始的过程不存在一个确定最正确的发送速率使其运行在最佳状态。
了解了以上指导性原则后,将更利于我们理解TCP拥塞控制算法。
拥塞控制算法分为三个阶段:
慢启动
ssthresh为慢启动阈值,MSS为最大报文段长度,cwnd为拥塞窗口,为方便起见,我们也把它作为拥塞窗口长度来表示。
cwnd初始设置为1个MSS,每当首次收到一个报文段的确认报文,cwnd就增加一个MSS,以RTT即一个往返时延为单位,在没有丢包事件的情况下,我们可以观察到cwnd最开始是1个MSS,然后是2个MSS,然后是4个MSS…依次类推我们可以得出慢启动阶段cwnd是呈指数增长的。
如果出现丢包,这时我们将cwnd设置为1MSS,并继续让cwnd指数增长,同时将ssthresh设置为当前cwnd的一半。
cwnd如果成功增长到慢启动阈值,之后将进入拥塞避免阶段。
拥塞避免
进入拥塞避免阶段,一个RTTcwnd将只增加一个MSS。这时如果出现丢包,cwnd将变为1个MSS,并开始慢启动,同时将ssthresh设置为当前cwnd的一半。
快速恢复
TCP早期版本TCP Tahoe没有快速恢复阶段,而TCP的较新版本TCP Reno则综合了快速恢复。我们知道多种情况都能被认定为发生了丢包事件,一种是超时,即最常见的触发丢包事件的触发条件,这种情况下新老版本TCP的行为是相同的,cwnd会从1MSS开始慢启动。但是当触发丢包事件的是3个冗余ACK,新版本TCP的算法则不同,此时ssthresh仍设置为cwnd的一半,之后cwnd则变为ssthresh+3(3个冗余ACK要加上3个MSS),并在此基础上线性增长。这就是快速恢复。

发表回复