无连接运输的UDP、可靠数据传输原理、面向连接运输的TCP
由[RFC 768]定义的UDP只是做了运输协议能够做的最少工作。除了复用/分解功能极少量的差错检测外,它几乎没有对IP增加别的东西。如果应用程序开发人员选择UDP而不是TCP,则该应用程序差不多就是直接与IP打交道。UDP从应用程序进程得到数据,附加上用于多路复用/分解服务的源和目的端口号字段,以及两个其他小字段,然后形成的报文段交给网络层。网络层将运输层报文段封装到一个IP数据报中,然后尽力而为地尝试将此报文交付给接收主机。如果该报文段到达接收主机,UDP使用目的端口号将报文段中的数据交付给正确的应用程序进程。
值得注意的是,使用UDP时,在发送报文段之前发送方和接收方的运输层实体之间没有握手。正因为如此,UDP被称为时无连接的。
DNS是一个通常使用UDP的应用层协议的例子。当一台主机中的DNS应用程序想要进行一次查询时,它构造了一个DNS查询报文并将其交给DUP。无需执行任何与运行在目的端系统中的UDP实体之间的握手,主机端的UDP为此报文添加首部字段,然后将形成的报文段交给网络层。网络层将此UDP报文段封装进一个IP数据报中,然后将其发送给一个名字服务器。在查询主机中的DSN应用程序则等待对该查询的响应。如果它没有收到响应(可能是由于底层网络丢失了查询或响应),则要么试图向另一个名词服务器发送该查询,要么通知调用的应用程序它不能获得响应。
为什么开发人员宁愿在DNS目录服务器上使用UDP构建应用,而不选择TCP上构建呢?既然TCP提供了可靠数据传输服务,而UDP不能提供,那么TCP是否总是首选呢?答案是否定的,因为有许多应用更适合用UDP,原因主要有:
- 关于何时、发送什么数据的应用层控制更为精细。所以主要着重关注将数据及时发送出去的应用程序更适合采用UDP协议,因为TCP有握手的往返延时,还有阻塞控制机制,而且TCP不管交付时间需要用多久都会将数据发送出去且确认接收方是否接收到。而像时时通讯(视频/语音)这种应用并不需要TCP这样的特性。
- 无需建立连接。上面说过TCP传输数据之前需要握手会有往返延时,但是像Web应用传输数据就非常有必要采用TCP协议,特别时HTML这样的主文件。
- 无连接状态。不维护连接状态相比维护连接状态更节约网络资源,服务器可服务对象越多,一般UDP支持更多活跃用户的应用。
- 分组首部开销小。每个TCP报文段都有20字节的首部开销,而UDP仅8字节的开销。
所以像网络管理(SNMP)、路由选择协议(RIP)、名字转换(DNS)。括号对应的是应用层协议,这些应用程序都在运输层采用了UDP协议。接着来看UDP报文段的结构,在此之前有必要说明以下,UDP的应用是可以实现可靠数据传输的。可以通过应用程序自身建立可靠性机制来完成,将可靠性建立于应用程序中可以使其“左右逢源”,也就是说应用进程可以进行可靠通信,而无需受制于由TCP拥塞控制机制和传输速率限制。
一、UDP报文段结构
应用层数据占用UDP报文段的数据字段。UDP首部只有四个字段,每个字段有两个字节组成。通过端口号可以使目的主机将应用数据数据交给运行在目的端系统中的响应进程(即执行分解功能)。长度字段指示UDP报文段中的字节数(首部加数据,字节为单位)。因为数据字段的长度在一个UDP段中不同于在另一个段中,故需要一个明确的长度,接收方使用检验和来检查该报文段中是否出现差错。实际上,计算检验和时,除了UDP报文段以外还包括IP首部的一些字段,为了讨论检验和的计算,暂时忽略。
UDP检验和:
UDP检验和提供了差错检测功能。也就是说,检验和用于确定UDP报文段从源到达目的地移动时,其中的比特是否发生了改变。发送方的UDP对报文段中的所有16比特字的和进行反码运算,求和时遇到的任何溢出都被回卷。得到的结果被放在DUP报文段中的检验和字段。如何计算UDP/TCP检验和
虽然UDP提供差错检测,但它对差错恢复无能为力,UDP的某种实现只是丢弃受损的报文段,其他市县是将受损的报文段交给应用程序并给出警告。
二、可靠数据传输原理
考虑可靠数据传输的问题,不仅仅是在运输层,也会出现在链路层。可靠数据传输的框架,为上层提供的服务抽象是:数据可以通过一条可靠的信道进行传输。借助于可靠信道,传输数据比特就不会受到损坏或丢失,而且所有数据都按照其发送顺序进行交付。这恰好就是TCP向调用它的因特网应用所提供的服务模型。
TCP是在不可靠的(IP)端到端网络层之上实现的可靠数据传输协议,更一般的情况是,两个可靠通信端点的下层可能是由一条物理链路组成或是由一个全球互联网络组成。就可靠数据传输目的而言,可以将较低层直接视为不可靠的点对点信道。底层信道损坏比特或丢失整个分组时,需要什么样的协议机制,这贯穿我们讨论始终假设分组将以它们发送的次序进行交付,某些分组可能会丢失,这就是说,底层信道不会对分组重排序。
上图简单的模拟传输协议接口。(rdt表示可靠数据传输协议,_send指示rdt的发送端正在表调用。udt表示不可靠的数据传输)
rdt_send()函数表示可以调用传输协议的发送方;
udt_send()表示发送端和接收端发送分组给对方;
deliver_data()表示rdt向叫高层交付数据的方法;
rdt_rcv()表示rdt从底层信道接收一个分组;
2.1构造可靠传输协议
现在一步步地研究一系列协议,它们一个比一个复杂,最后得到一个无错、可靠的数据传输协议。首先,考虑最简单的情况,即底层信道是完全可靠的,将此协议定义为rdt1.0。
1.0 经完全可靠信道的可靠数据传输:rdt1.0
上图表示发送方和接收方的有限状态机(Finite- State Machine, FSM),上图(左)中的FSM定义了发送方的操作,上图(右)中FSM定义了接收方的操作。
上面的流程图描述了rdt1.0发送端的处理过程。
上面的流程图描述了rdt1.0接收端的处理过程。
虽然在rdt1.0中是基于一个理想中完全可靠信道来实现的数据传输,但是也描述了运输层的基本功能就是负责网络层与应用层的数据传输,我们将这个运输层协议先成为简单协议。在简单协议中,一个单元数据与一个分组没有差别,而且所有分组是从发送方流向接收方,有完全可靠信道,接收不需要任何反馈信息给发送方。注意,我们也假定了接收方与发送方的速率一样快。
2.0 经具有比特差错信道的可靠数据传输:rdt2.0
底层信道实际的模型是分组中的比特可能受损,分组的传输、传播或缓存的过程中,这些比特差错通常出现在网络物理部件中。在讨论比特差错信道的可靠数据传输时,我们假设所有发送的分组还是按照顺序接收的。相较完全可靠信道只有增加比特差错这一种情况。
从理论上来讲,只需要将比特差错的分组识别出来,然后将差错信息反馈给发送方,让发送方将差错分组重新发送一次。在计算机网络中,基于这样重传机制的可靠传输一些被称为自动重传请求协议(ARQ)。ARQ协议中还需要另外三种协议功能来处理存在比特差错的情况:
- 差错检测:基于检验和的比特差错识别。(在《计算机网络》第五章有详细的差错检测和纠错技术分析)
- 接收方反馈:当接收到正常分组时反馈“肯定确认”(ACK),当接收到比特差错分组时反馈“否定确认”(NAK),用0表示NAK,1表示ACK。
- 重传:接收方收到有比特差错的分组,发送方将重新传递该分组文。
理论上来说,好像有ARQ协议就可以实现比特差错信道的可靠数据传输。发送方每传输一个分组给接收方,等待接收方反馈回信息,如果返回的是ACK就继续传输下一个分组,如果返回的是NAK,就重新传输上一个分组,这种行为又被称为停等协议。到这里,好像rdt2.0就可以实现经具有比特差错信道的可靠数据传输了,别高兴太早,仔细看看反馈信息是什么?它本身也是被信道传输的比特,怎么才能保证反馈信息的比特不出错呢?这是不可能的,因为物理传输受损是必然的,那要怎么解决呢?
考虑处理受损ACK和NAK的三种可能性:
- 第一种方法(重复应答):当发送方收到的反馈信息比特受损无法识别时,发送方请求接收方再重复一次反馈。这种可能性有一个非常极端的情况就是如果重复发送的反馈还是丢失比特呢?又或者是发送方发送重复反馈请求时丢失比特呢?这就陷入了死循环。
- 第二种方法:增加足够的检验和比特,使发送方不仅可以检测差错,还可以修复差错。对于会产生差错但不丢失分组的信道,这就可以直接解决问题。
- 第三种方法:当发送方收到含糊不清的ACK或者NAK分组时,只需要重传当前数据分组即可。然而,这种方法在发送方到接收方的信道中引入了冗余分组。冗余分组的根本困难在于接收方不知道它上次发送的ACK或NAK是否被正确收到,因此它无法事先知道接收到分组是新的还是一次重复重传。
解决这个新问题(第三种方法中的无法确认分组问题)的方法非常简单,在数据分组中添加一个新字段,让发送方对其数据分组编号,即将发送数据分组的序号放到该字段。重传的分组序号与最近接收到的分组序号相同,新的分组序号会变化(使用模2“前向”移动:模2运算)。目前我们假定的是信道不会丢失分组,所以ACK/NAK分组本身不需要指明他们要确认的分组序号。发送方接收到ACK/NAK分组是为响应最近发送的数据分组而生成的。rdt2.1反映出目前正在发送的分组或希望接收的分组的序号是0还是1。rdt2.1使用了从接收方收到的肯定确认和否定确认。当接收方收到失序分组时,发送肯定确认。如果收到受损分组,则发送否定确认。如果不发送NAK,而是对正确接收到的分组发送一个ACK,那么也能得到与NAK一样的效果。发送方收到对同一个分组的两个ACK(接收到了冗余ACK)后,就可以知道接收方没有正确接收被确认两次的分组后面的分组。这也产生了协议rdt2.2。rdt2.2是在有比特差错信道上实现的一个无NAK的可靠传输协议,此时ACK报文就需明确所确认的分组序号。(这一段的具体实现不太明白《计算机网络》P141~143)。
3.0 经具有比特差错的丢包信道可靠数据传输:rdt:3.0
现在除了比特损坏外,底层信道还会丢包,这样的情况并不罕见。所以协议必须关注两个问题:怎么检测丢包以及发生丢包后该做些什么?
在rdt2.2中已经使用的技术有检验和、序号、ACK分组、和重传等。在这个基础上考虑结局丢包问题,丢包所造成的问题就是接收方无法响应,不会反馈信息给发送方,这里就有了一个突破口,就是延时时间,假设已知在不丢包的情况下接收方的反馈时间是n,那就可以采用倒计数定时器等待时间n后再次发送上次的分组,这同样会产生冗余数据分组,而这样的问题再rdt2.2中已经得到解决,所以丢包问题也就解决了。
3.1 流水线可靠数据传输协议 :rdt3.1
在rdt3.0中的以及2.2中都采用了停等协议,对信道的利用率非常的低(案例见《计算机网络》P144~146)。解决这个问题的简单方案就是:流水线可靠数据传输协议。流水线可靠数据传输协议采用发送多个分组(同样序号的一个分组发送多个)的方式来实现。
多个分组的运输原理是基于信道实际容量和单个分组传输的使用比率来实现的,也就是假设使用停等协议传输方式只是用了信道传输容量的33%,那多分组传输就可以一次连续传输三个相同序列的分组,这样信道利用率就提高了3被,也就提高了分组的传输成功率。这样产生的连锁反应就是成功率提升降低了重传,降低了重传就降低了反馈比特损坏和丢失率,整体性能就会提高很多。
流水线可靠数据传输协议除了多分组传输,当然就是采用流水线的传输方式连续传输,那什么是流水线传输呢?为什么要使用流水线传输呢?
采用多分组传输实现了非常贴近完全可靠传输,就不必要采用停等应答的低效方式来解决比特损坏和分组丢失,而是采用回退N步和选择重传的方式来解决比特损坏和分组丢失问题。在解析回退N步和选择重传的具体技术之前,先来看看流水线完全可靠协议的基本实现逻辑:
从上面的流水线可靠传输协议示图中可以看到每一个分组都会被发送多次,这是流水线可靠传输协议的核心,只要多个分组中有一个没有损坏就可以实现了可靠传输。但是,也可能会出现极端的情况就是整组损坏的情况,在示图中我标识了重传反馈,其实不正确,只是为了区分以下整组丢失、反馈比特损坏导致的超时,其实质上都是使用选择重传来解决。在解析选择重传之前需要先来说明以下回退N步(GBN)协议的原理,这是因为流水线可靠传输协议带来的下列影响造成的:
- 必须增加序号范围,因为每个输送中的分组(不计重传的)必须有一个唯一的序号,而且也许有多个在输送中未被确认的报文。
- 协议的发送方和接收方两端也许必须缓存多个分组。
- 所需序号范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏及延时过大的分组。解决流水线差错恢复的两种基本办法是:回退N步(Go-Back-N,GBN)和 选择重传(Selective Repeat,SR)。
3.1.1 回退N步
在回退N步(GBN)协议中,允许发送方发送多个分组而不需要等待确认,但是也受限于在流水线中未确认的分组数不能超过某个最大允许数N。在流水线可靠协议示图中可以看到GBN协议的序号范围。如果将基号(base)定义为最早的未确认分组序号,将下一个序号(nextseqnum)定义为最小的未使用序号,则可以将序号范围分为四段:在[0,base-1]段内的序号对应于已发送并被确认的分组;[base,nextseqnum-1]段内对应已发送但未被确认的分组;[nextseqnum,base+N-1]段内的序号用于可以被立即发送的分组。如果有数据来自上层的话,最后大于base+N的序号不能被使用,知道当前流水线中未被确认的分组已得到确认。
随着协议进行,该窗口序号空间向前滑动,因此N常被称为窗口长度,GBN协议也常被成为滑动窗口协议。在此我们限定的窗口长度N,而不是让分组数为无限大是有两个原因①流量控制②TCP拥塞控制。如果分组序号字段的比特数为k,那么序号范围就是[0,2^k-1],在一个有限的序号范围内,所有涉及序号的运算必须使用模2^k运算。
GBN发送方必须响应三种类型的事件:
- 上层的调用:当上层窗口调用发送时,发送方首先检查发送窗口是否已满(即是否有N个已发送但未被确认)。如果窗口未满,则产生一个分组并将其发送,并更行响应变量。如果窗口已满,发送方只需要将数据返回给上层,隐式指示上层该窗口已满。然后上层可能会等一会儿再试。实际实现中,发送方可能会缓存这些数据,或者使用同步机制允许上层在仅当窗口不满时才调用发送方法。
- 收到一个ACK:在GBN协议中,对序号为n的分组的确认采用积累确认的方式,表明接收方以正确接收到序号n的以前且包括n在内的所有分组。
- 超时事件:回退N步来源于出现丢失和延时过长时的处理行为,就像停等协议中那样,如果接收方的反馈时间超出发送方的定时器时间(分组丢失损坏,反馈比特损坏,网络延时长),发送方就会重新发送已发送当未被确认的分组,这就是回退N步的来源。
下图表示了GBN的运行模式:
GBN协议中综合了可靠数据传输协议构件的所有技术,这些技术包括使用序号、积累确认、检验和超时/重传操作。采用GBN这种协议看似好像很合理很实用,但是却有一个非常大的闭端,随着协议的运行,重传的分组就会积累的越多,看到上面的示图就一目了然,后面的重传分组和正常分组都一样多了,而且随着时间推移,重传会更加频繁,所以后面就有了选择重传来解决这个问题。
3.1.2 选择重传
GBN也存在着一些性能问题,单个的分组出现差错就会引起GBN重传大量不必要重传的分组。在信道差错率很高时,流水线可能会被不必要重传的分组所充斥。可操作此处SR Java小程序查看SR运作流程。
SR协议通过让发送方仅重传那些它怀疑在接受方出错(丢失或受损)的分组而避免不必要的重传。
下图(来自《计算机网络 自定向下方法》)显示了SR协议中发送方和接收方的序号范围。
在SR协议中发送方和接收方所采取得动作可见下文描述。
SR发送方的事件与动作
-
从上层接收到数据
从上层接收到数据后,SR发送方检查下一个可用于该分组的序号。如果该序号位于发送方的窗口内,则将数据打包并发送;否则就像再GBN中一样,要么将数据换组,要么返回给上层以便以后传输。 -
超时
定时器在此用来防止丢失分组。但是,SR中每个分组都要有自己的逻辑定时器。 -
收到ACK
如果收到ACK,倘若该分组序号在窗口内,SR发送方就将那个被确认的分组标记为已接收。
如果该分组的序号等于send_base,则窗口基序号向前移动到具有最小序号的未被确认分组处。
如果窗口移动了并且有序号落在窗口内的未发送分组,则发送这些分组。
SR接收方的事件与动作
SR接收方将确认一个正确接收的分组而不管其是否按序。失序的分组将被缓存,知道所有丢失分组皆被收到为止,这时才将一批分组按序交付给上层。
-
序号在[rcv_base,rcv_base+N-1]内的分组被正确接收
在此情况下,收到的分组落在接收方的窗口内,一个选择ACK被回送给发送方。
如果该分组以前没有被接收到过在,则缓存该分组。
如果该分组的序号等于基序号,则该分组以及以前缓存的序号连续的分组交付给上层。
然后,接收窗口按向前移动分组的编号向上交付这些分组。 - 序号在[rcv_base-N,rcv_base-1]内的分组被正确收到
在此情况下,必须产生一个ACK,即使该分组时接收方以前已确认的分组。
要意识到这是非常重要的,假设接收方以前就接收了send_base分组,现在接收到重传的不回送ACK,那么发送方窗口就永远也不会向前滑动! -
其他情况,忽略该分组
SR协议中发送方窗口和接收方窗口并不总是一致的。这会引出关于序号范围的一个问题。
在有限的序号范围的实现里,如果SR接收方窗口太大并且发送方和接收方窗口间的不同步,便会造成发送方这边新分组序号与旧分组序号重复使用。导致无法辨析该序号代表的是一个新分组还是旧分组。
SR的窗口长度应限制在小于等于序号空间大小的一半。
小结一下可靠数据传输机制中使用到的技术:
检验和、定时器、序号、确认、否定确认、窗口/流水线
三、面向连接的运输:TCP
TCP被称为面向连接的协议,这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互“握手”,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。作为TCP连接建立的一部分,连接的双方都将初始化与TCP连接相关的许多TCP状态变量。这些变量将会是控制实际数据传输的重要逻辑变量,在介绍TCP实际运输逻辑之前,先来看一下TCP协议执行发送和接收的基本结构(缓存)示图:
看到上面这个图你一定会很惊讶,为什么与博客的第二部分“可靠数据传输协议”差别那么大?这是一个很关键的问题,在第二部分中说的可靠数据传输协议都是已分组传输为传输基本单元,但是需要注意的是,在TCP协议中传输的基本单元是用数据流来描述的,虽然本质上都是报文段,但是这两个描述差别将是TCP协议一切的起源(个人理解),从分组到数据流其主要的差别就是分组是从应用层直接调用运输层的接口实现,然后就直接被用来传输,在第二部分中出现的冗余是直接采用丢弃的方式来解决,甚至当分组出现错序的后面所有分组都被丢弃,这种粗暴的做法必然带来的就是增加了网络传输压力,浪费传输资源,结果就是传输效率低。而TCP协议采用了缓存的方式来解决这个问题,当然可靠数据传输的其他技术:检验和、定时器、序号、确认、否认确认、窗口/流水线、重传机制都被TCP作为基础而应用。实质上,TCP协议就是在写基础技术进一步优化,以达到可靠数据传输的最佳应用方案。
TCP提供的是全全双工服务(full-duplex-service),并且TCP连接也是点对点(piont-to-piont)的,这就说明TCP连接是单个发送方与单个接收方之间的连接。在一次发送操作中,从一个发送方将数据传给多个接收方,即“多播”操作对TCP来说是不可能的。
先就示图的基本逻辑来分析TCP从连接到数据传输的过程:
- 客户首先发起一个特殊的TCP报文段,服务器另一端特殊的TCP报文段响应,最后,客户再用第三个特殊报文段作为响应。前两个报文段不承载“有效负荷”,也就是不包含应用层数据;而第三个报文段承载有效载荷。这个连接过程也叫做三次握手。
- 当数据被应用层通过套字节传递到运输层,TCP协议运行控制数据流,将数据引导到该链接的发送缓存里。发送缓存是在三次握手初期设置的缓存之一。
- 接这TCP从缓存中取出有最大限制长度的数据(MSS)加上报文首部合成报文发送给接收方。
最大限制长度的数据(MSS):通常根据最初确定的由本地发送主机发送的最大链路层帧长度(最大传输单元)来设置。设置最大传输单元主要依据以太网和PPP链路层的最大链路层帧决定。假设链路层最大链路帧是1500字节的MTU,而TCP报文首部长度通常是40字节,因此MSS的典型值为1460字节。
3.1 TCP报文段结构
TCP报文段的结构与UDP一样,首部包括端口号和目标端口号,它被用于多路复用与多路分解或用来将数据送到上层的应用层。报文的数据部分就不解释了,就是上层应用层的报文,这里重点来关注TCP的首部结构。
数据偏移:TCP中数据的开始处距离TCP报文段的起始位置有多远 == TCP报文段的首部长度。表示长度以32位比特为单位,因此最大可以表示60字节(15*4)的首部。保留占位6以后使用。
标志字段 | 含义 |
---|---|
URG | URG=1,用来指示报文段里存在着被发送端的上层实体置为“紧急”的数据。此时紧急指针有效 |
ACK | 当ACK=1时,确认号字段有效,表示对已被成功接收的报文段的确认 |
PSH | 当PSH=1时,指示接收方应立即将数据交付给上层,不用等接收缓存满了才交付 |
RST | RST和下面的SYN、FIN用于TCP建立连接和释放连接。 RST用于①RST=1,TCP连接初出现严重差错,必须释放连接然后重新建立运输连接 ②拒绝一个非法报文段或者拒绝打开一个连接 |
SYN | 在TCP连接建立时用来同步序号 |
FIN | FIN=1,释放TCP连接 |
序号与确认号:
在开始介绍TCP时我就重点的描述了TCP的缓存和字节流,TCP把数据看成无结构的、有序的字节流。从TCP的序号上就可以看出这一点,序号是建立在字节流上,而不是建立在报文字段的序列之上,报文的序号是该报文首字节的字节流编号。假设数据流由一个包含500000字节的文件组成,其MSS为1000字节,数据流的首字节编号是0,TCP将该数据流构建500个报文段,给第一个报文段分配序号0,第二个报文段分配序号1000,第三个报文段分配序号2000,以此类推。
将字节编号作为序号有什么好处呢?接着来看确认号,假设由A、B两个主机,A向B请求了一个报文段编号为0~1000的所有字节,但是在发送的过程中由于网络层和链路层的问题,A只接收到了0~536序号字段和900~1000的序号字段,这就出现了乱序的问题,通常解决这类问题有两个基本选择:1、接收方立即丢弃失序字段2、接收方保存失序字段,并等待缺少的字节以填补间隔。显然第二种方法更有效,而这第二种方法就需要确认号来完成这样精确的问题,TCP会先将1~536序号的字段添加到字节流中,然后将900~1000的序号字段暂时缓存,并将536序号作为确认好反馈给B,这时候B就知道了应该从发送方的缓存中取出537字节开始的字节流给接收方发送,直到发送发送到899字节流序号的报文,接收方将900~1000的字节流合并到字节流中,然后反馈确认号1000表示数据全部接收完毕。
3.2 往返时间的估计与超时
在第二部分的可靠数据传输协议中介绍了超时/重传机制,同样,TCP协议也应用了同样的机制,但是在第二部分并没有就超时/重传的具体时间做任何讨论,TCP作为一个具体的可靠数据传输协议当然就必须对超时/重传的时间设定有一个明确的解决方案。RTT作为往返时间,超时设定值可能就要比RTT大。TCP的实现仅在某一时刻测量一次往返时间(SampleRTT),而且不会测量重传报文的往返时间([Kan 1987])。
由于路由器的拥塞和端系统负载的变化,这些报文段的SampleRTT值会随之波动,所以并非一个典型值。为了估计一个典型的RTT,TCP协议采用了对SampleRTT取平均值的办法。一旦获得一个新SampleRTT时,TCP就会根据下列公式来更新Esti-matedRTT:
EstimatedRTT = (1 - a ) * EstimatedRTT + a * SampleRTT
EstimatedRTT的新值是由以前的Esti-matedRTT的值与SampleRTT新值加权组合而成的。在[RFC 6298]中给出的a参考值是a = 0.125(即:1/8),这时上面的公式变为:
EstimatedRTT = 0.875 * EstimatedRTT + 0.125 * SampleRTT
通过加权平均是为了得到一个更能反映网络当前的拥塞情况,统计学的观点讲,这种平均被称为指数加权移动平均。在TCP中除了估算RTT外,测量RTT的变化也是有价值的。[RFC 6298]定义了RTT偏差DevRTT,用于估算SampleRTT一般会偏离EstimatedRTT的程度:
DevRTT = (1 - b) * DevRTT + b * | SampleRTT - EstimatedRTT |
注意DevRTT是一个SampleRTT与EstimatedRTT之间差值的EWMA。如果SampleRTT值波动较小,那么DevRTT就会很小。反之,如果波动很大,那么DevRTT的值就会很大。b的推荐值为0.25。
设置和管理重传超时间隔:
假设已经给出了EstimatedRTT值和DevRTT值,那么TCP超时间隔应该用什么值呢?超时间隔应该大于等于EstimatedRTT,否则,将造成不必要的重传,但是超时间隔也不能比EstimatedRTT大太多,否则当报文段丢失时,TCP不能很快地重传该报文段,导致传输延时过大。而且当SampleRTT值波动较大时,这个余量应该大些。所以DevRTT就有用武之地了,计算重传超时间隔的公式:
TimeoutInterval = EstimatedRTT + 4 * DevRTT
在[RFC 6298]中推荐TimeoutInterval初始值为1秒,同样当出现超时后,TimeoutInterval值将加倍,以免即将被确认的后继报文段过早出现超时,一旦报文段收到更新的EstimatedRTT后,TimeoutInterval就又使用上述公式计算。
3.3 可靠数据传输
TCP在IP不可靠的情况下尽力为服务创建一个可靠数据传输服务。TCP的可靠数据传输服务确保一个进程从其接收缓存中读出的数据流是无损坏、无间隔、非冗余和按序的数据流,即该字节流与连接的另一方端系统发送出的字节流是完全相同的。TCP提供可靠数据传输的方法在博客的第二部分有详细的技术功能描述。
TCP具体是如何提供可靠数据传输的其有三个主要事件:从上层应用程序接收数据、定时器、收到ACK。实际上其基本的技术都是建立博客第二部分描述的可靠数据传输协议的技术上,但是TCP相对第二部描述的技术应该进一步优化,下面来一段简单的TCP可靠数据传输处理流程和多种情况:
情况一:假设有A、B两个主机,A主机向B主机发送一个报文,报文段的序号是92,且含8个字节的数据。在发送该报文段之后,主机B准确的接收到了该报文的所有数据并给A主机发送了确认报文序号100。但是,确认报文在传输途中丢失,引发了A主机重发这段报文,并且也顺利的传输到了B主机,但是B主机原本就已经有了这段报文,所以直接丢弃重传的报文段。
情况二:A主机在给B主机发送第一段报文后,由于窗口长度允许继续发送后续报文,所以A主机又给B主机发送了第二段报文,序号是100,包含20个字节的数据。假设这两个报文都完好的被B主机接收,但是B主机给A主机发送的第一段确认报文丢失,导致A主机启动重发,并刷新定时器,如果第二段确认报文顺利的在定时器刷新后的间隔事件内被A主机收到,A主机就不会发送第二段报文了,这就是TCP的接收缓存的作用。
情况三:同样是情况二的数据传输,并且第一段确认报文丢失,但是第二段确认报文在第一段报文间隔时间之前就被A收到了,A也启动第一段报文的重发机制了。
超时间隔加倍:
超时间隔加倍这种机制为了控制拥塞,这种方式不只是存在TCP协议中,在以太网的CSMA/CD中也同样采用这种方式控制网络拥塞。这里还不详细介绍TCP的拥塞控制,后面会有详细TCP拥塞控制机制分析。但是在上面的TCP重传中已经引述出了超时间隔加倍,为了方便理解,就在这里阐述一下超时间隔加倍的原理机制。
比如在上面的例子中提到反馈报文丢失,TimeoutInterval就不是用EstimatedRTT和DevRTT的值来推算了,而是直接在原来的TimeoutInterval上加倍,并且如果第一次超时加倍后还是没有收到反馈报文,发送方在超时后重传报文段并且在第一次加倍的基础上再次加倍。(初始值:0.75秒,第一次超时:1.5秒;第二次超时:3秒)。这样就有效的防止了过多的分组被传入从源到目的端之间的路劲上,导致更大的网络延迟。
快速重传:
超时触发重传存在的问题是超时周期可能相对较长,在这个时间里接收方可能会将超时报文段前一个确认报文重复反馈给发送方多次,这时就会出现ACK冗余现象,如果在超时周期之内一个报文段的ACK冗余数量得到三个,发送方就不会等超时间隔来触发重传了,而是提前实现重传。这就是快速重传。([RFC 5681])
TCP是回退N步还是选择重传
因为TCP的缓存机制,不需要回退N步全部重传,而是将超时或触发快速重传的报文段进行重传操作,重触发机制上来看很像GBN协议,但是并没有像GBN那样回退N步,而是有选择的重传操作,所以也可以说是SR协议,但是SR协议会向窗口传入疑似丢包或比特损坏的报文段,这与快速重传非常类似,但是又并行存在超时重传的GBN机制。TCP协议获得了SR协议与GBN协议的优势,所以可以说是GBN协议与SR协议的混合体。
3.4 流量控制
这里所谓的流量控制并不是控制网络流量MSS,也不是拥塞机制,而是控制TCP缓存的流浪,TCP的缓存空间不能溢出。这里需要关注的几个值:
LastByteRead:接收方的应用程序从TCP缓存中读出的最后一个字节符。
LastByteRcvd:接收方从网络中接收并放入缓存的最后一个字节符。
LastByteSent :发送方传入网络的最后一个字节符。
LastByteAcked:发送方发送的字节符但未被接收方确认放入缓存中的最小字节符。
RcvBuffer:接收方的缓存空间。
rwnd:接收方空闲的缓存空间。
所以得出以下公式:
LastByteRcvd - LastByteRead <= RcvBuffer ------TCP不允许已分配的缓存溢出。
rwnd = RcvBuffer - [ LastByteRcvd - LastByteRead] ------- 接收方空闲的缓存空间等于分配缓存空间 - 已被占用的缓存空间(已被占用的缓存空间等于已读最后一个字节符减去已放入缓存的最后一个字节符)
LastByteSent - LastByteAcked <= rwnd ------ 发送方最后发送的最后一个字节符减去未被接收方放入缓存的最小字节符要小于接收方的可用缓存空间
3.5 TCP连接管理
关于TCP连接其实关于连接部分在前面已经多次出现了,指示解除连接没有介绍过,但是其本质上是确认机制实现的线程释放。也没有太多东西,直接上图:
三次握手:客户端向服务端请求资源。
第一步:客户端向服务端发送一段特殊报文,请求连接。(标志位SYN设置为1;)
第二部:服务端接收到请求连接报文后,返回一段特殊报文给客户端,建立连接。(SYN被设置为1;确认字段被加一:client_isn + 1;)
第三步:客户端收到服务端的连接建立确认报文后,客户端带着请求资源必须要的数据(也可以不带数据)发送一段特殊报文给服务端。(确认字段被加一:client_isn + 1;(在前面的基础之上加一))
上面三个步骤就是三次握手的过程,正式开始传输数据的时候SYN会被设置为0,接着来看四次挥手过程,也就是释放连接的过程:
第一步:当客服端请求的数据全部接收到以后,客户端就会向服务端发送一个释放连接的报文段。报文中的FIN被设置为1。
第二部:服务端收到客户端的释放连接报文段后,向客户端发送一个确认报文段ACK。
第三步:服务端紧接着有向服务端发送一个释放连接的报文段,并且报文中的FIN也会被设置为1。
第四步:客户端接收到服务端的释放报文段后,又向服务端发送一个确认报文端ACK,告诉服务端收到了释放连接的确认报文。
四、拥塞控制原理
4.1 出现拥塞
网络拥塞通俗的说就是网络传输需求大于网络传输能力,网络拥塞的三个一般性情况:
情况一、假设路由有无限大的缓存空间,发送端的发送速率好像就可以无限扩大,但是并非如此,链路完全中的排队分组也就会随着发送端的发送速率无限扩大,由于排队分组达到链路层的容量时,分组就会产生很大的排队延时,从而导致网络拥塞。
情况二、假设路由的容量是有限的,也就是当路由的缓存已满后就会丢弃后续被传入的分组,由于丢弃分组就会导致运输层重传,这就会进入一个恶性循环,丢弃越多,重传就会越多,延时就会越长,延时越长就会触发更多的重传,这种网络拥塞也可以说是超出供给载荷。
情况三、多个发送方在多个路由器之间的多跳路径,当其中一个路由其中了大量的传输容量时,途径这个路由的所有传输都会被拥塞。
4.2 拥塞控制方法
端到端拥塞控制:在这个方法中,网络层没有为运输层拥塞控制提供显式支持,即使网络中存在拥塞,端系统也必须通过对网络行为的观察来推断网络是否阻塞。TCP必须通过端到端的方法解决拥塞控制,因为IP层不会向系统提供有关网络拥塞的反馈信息。TCP报文的丢失(通过超时或3次冗余确认而得知)被认定是网络拥塞的一个迹象,TCP会相应的减少其窗口长度。在TCP拥塞控制的一些最新建议中,即使用增加往返时延值作为网络拥塞程度增加的指示。
网络辅助的拥塞控制:在网络辅助的拥塞控制中,网络层构件(即路由器)向发送方提供有关网络中拥塞状态的显示反馈信息。拥塞信息从网络反馈到发送方通常有两种方式:①直接反馈信息由网络路由器发给发送方,这种通知方式常采用了一种阻塞分组(choke packet)的形式(含义为:“我阻塞了”)②显示拥塞通知(Explicit Congestion Notification,ECN)。第二种是路由器标记或更新熊发送方流向接收方的分组中的某个字段来指示拥塞的产生。当接收方接收到这样的分后后,就会向发送方发送网络拥塞的通知。这种方式需要至少需要一个完整的RTT。使用网络辅助的拥塞控制例子可参见ATM ABR拥塞控制。
4.3 TCP拥塞控制
在TCP的拥塞控制机制上,TCP使用的是端到端的拥塞控制。即让每个发送方根据感受到的网络拥塞程度限制其能向其连接发送流量的速率。由此引发三个问题:1.TCP发送方如何限制它向其连接发送流量的速率。2.TCP发送方如何感知它到目的地之间的路径出现了拥塞。3.当发送方感受到了端到端的时延使用何种算法来改善其发送速率。
4.3.1 TCP发送方如何限制它向其连接发送流量的速率?
TCP连接的每一个端都有一个接收缓存、一个发送缓存、和几个变量组成。运行在发送方的TCP拥塞控制机制跟踪一个额外的变量,即拥塞窗口(cwnd)。TCP发送方通过cwnd来控制发送流量速率,也就是未被确认的数据流量不能超过cwnd,所以也必然不会超过rwnd(接收方空闲缓存空间)。
LastByteSent - LastByteAcked <= min{cwnd, rwnd}
上面这个公式就是通过拥塞窗口控制发送方的发送流量速率的基本原理,假设rwnd无限容量,但是发送流量速率受到已发送发送未确认分组(已发送的最大字节符序号减去未被确认的最小字节符序号)的控制,也就间接的限制了发送流量速率。拥塞窗口长度校验算法
所以发送方的发送流量速率可以估算为:在不考虑丢包和发送时延的情况下,发送速率大概是cwnd/RTT(字节/秒)。
4.3.2 TCP发送方如何感知它到目的地的路径出现了拥塞?
假设将一个TCP发送方的“丢包事件”定义为:要么出现超时,要么收到来自接收方的3个冗余ACK。当出现过度的拥塞时,沿着这条路径上的一台路由缓存就会溢出,引发数据包被丢弃,丢弃数据包引发的丢包事件要么超时或收到3个上一个报文段的冗余ACK。所以发送方就认为在发送方到接收方的路径上出现了拥塞。
从判断窗拥塞中可以反向思考,如果发送方传输的报文段都正常被接收方接收到,也就是说都能正常的接收到接收方反馈的确认报文ACK,这样的情况下就可以被判定为时网络传输通畅,发送方就会增加拥塞窗口的长度,从而达到提高传输速率的效果,而却会根据反馈确认报文的ACK的RTT来判断网络通畅程度,如果确认已相当慢的速率到达就,速率增长也同样会以相当慢速度增长。反之,如果确认相对快的速率到达,拥塞窗口就会瞬间增大窗口长度。TCP把这种机制叫做自计时。
4.3.3 当发送方感受到了端到端的时延使用何种算法来改善其发送速率?
概述了TCP拥塞控制后,现在就需要采用对应的方法来改善发送速率,广受赞誉的TCP拥塞控制算法[Jacobson 1988]。该算法包含了三个重要的部分:1、启动慢;2、拥塞避免;3、快速恢复。慢启动和拥塞控制是TCP的强制部分,两者的差异在于对收到的ACK做出反应时增加cwnd长度的方式。慢启动比拥塞控制能更快的增加cwnd的长度。快速恢复是推荐部分,对TCP发送方并非必须的。
4.3.3.1 慢启动
在慢启动状态,cwnd的值以一个MSS开始并当传输的报文首次被确认就增加一个MSS。如下图所示,开始发送一个报文段,收到确认后拥塞窗口增加1。然后传输2个报文段,收到2个确认后增加拥塞窗口变成了4个MSS。这样没经过一个RTT,发送速率就会翻番。于是,TCP发送的起始速率慢,但是在慢启动阶段会以指数增长。
但是这样会的增长何时终止呢?慢启动对这个问题提供了几种答案。
-
第一种: 如果出现一个有超时引起的丢包事件(即网络中出现了拥塞),TCP发送方将cwnd设置为1并重新开始慢启动过程。它还会将第二个状态变量ssthresh(“慢启动阈值”)设置为cwnd/2。
-
第二种: 与ssthresh相关。当增加到cwnd=ssthresh时,结束慢启动并开始拥塞避免。
-
第三种: 如果检测到3个冗余ACK,这时TCP执行快速重传进入快速恢复状态。
4.3.3.2 拥塞避免
进入拥塞避免状态后,cwnd的值大约是上次遇到拥塞时的值的一般,即距离拥塞可能并不遥远。TCP可定不能再像慢启动那样对cwnd的值翻番,而是采用了另一种方式更新cwnd的值。当TCP发送方无论何时达到一个新的确认,就将cwnd增加一个MSS字节(MSS/cwnd)。例如,如果MSS是1460个字节并且cwnd也是1460字节,则在一个RTT内发送10个报文段。每个到达ACK增加1/10MSS的拥塞窗口长度,因此,在收到所有10个报文端的确认后,拥塞窗口的值将增加一个MSS。
那拥塞避免的线性增长在什么时候停止呢?跟启动慢一样,再出现丢包事件后cwnd的值同样会被更新为cwnd值的一半。如果丢包事件是由三个冗余ACK事件触发,在这种情况TCP会进入快速恢复状态。
4.3.3.3 快速恢复
在前面的TCP可靠数据传输部分就有提到快速重传,相当于是快速恢复的开始。在快速恢复中,对引起TCP进入快速恢复状态的缺失报文段,对收到的每个冗余的ACK,cwnd的值增加一个MSS。最终,当丢失报文段到达时,TCP再降低cwnd后进入拥塞避免状态。如果出现超时事件,cwnd置为1个MSS,并且ssthresh置为cwnd的一半,迁移到慢启动。
快速恢复是TCP推荐部件而不是必需。一种早期的TCP版本TCP Tahoe,不管是超时而引起的丢包还是3个冗余ACK引起的丢包事件,都会将cwnd置为1个MSS,并进入慢启动阶段。TCP较新的版本TCP Reno综合了快速恢复算法。