传输数据
在TCP的数据传送状态。非常多重要的机制保证了TCP的可靠性和强壮性。它们包括:使用序号。对收到的TCP报文段进行排序以及检測反复的数据;使用校验和来检測报文段的错误。使用确认和计时器来检測和纠正丢包或延时。
在TCP的连接创建状态,两个主机的TCP层间要交换初始序号(ISN:initial sequence number)。这些序号用于标识字节流中的数据,而且还是相应用层的数据字节进行记数的整数。通常在每个TCP报文段中都有一对序号和确认号。TCP报文发送者觉得自己的字节编号为序号,而觉得接收者的字节编号为确认号。TCP报文的接收者为了确保可靠性,在接收到一定数量的连续字节流后才发送确认。这是对TCP的一种扩展,通常称为选择确认(Selective Acknowledgement)。
选择确认使得TCP接收者能够对乱序到达的数据块进行确认。每个字节传输过后,ISN号都会递增1。
通过使用序号和确认号。TCP层能够把收到的报文段中的字节按正确的顺序交付给应用层。序号是32位的无符号数。在它增大到2^32-1时,便会回绕到0。对于ISN的选择是TCP中关键的一个操作,它能够确保强壮性和安全性。
来看个样例:
1)发送方首先发送第一个包括序列号为1(可变化)和1460字节数据的TCP报文段给接收方。
接收方以一个没有数据的TCP报文段来回复(仅仅含报头)。用确认号1461来表示已全然收到并请求下一个报文段。
2)发送方然后发送第二个包括序列号为1461和1460字节数据的TCP报文段给接收方。正常情况下,接收方以一个没有数据的TCP报文段来回复,用确认号2921(1461+1460)来表示已全然收到并请求下一个报文段。
发送接收这样继续下去。
3)然而当这些数据包都是相连的情况下,接收方没有必要每一次都回应。
比方,他收到第1到5条TCP报文段,仅仅需回应第五条即可了。在样例中第3条TCP报文段被丢失了。所以尽管他收到了第4和5条,然而他仅仅能回应第2条。
4)发送方在发送了第三条以后,没能收到回应,因此当时钟(timer)过时(expire)时,他重发第三条。(每次发送者发送一条TCP报文段后,都会再次启动一次时钟:RTT)。
5)这次第三条被成功接收,接收方能够直接确认第5条,由于4,5两条已收到。
Acknowledgment Number Out = Sequence Number In + Bytes of Data Received
1.TCP重传
报文重传是TCP最基本的错误恢复功能,它的目的是防止报文丢失。
报文丢失的可能因素有非常多种,包括应用故障,路由设备过载,或临时的服务宕机。
报文级别速度是非常高的。而通常报文丢失是临时的,因此TCP能够发现和恢复报文丢失显得尤为重要。
重传机制在实现数据可靠传输功能的同一时候,也引起了相应的性能问题:何时进行数据重传?如何保证较高的传输效率?
重传时间过短:在网络由于拥塞引起丢包时,频繁的重传会进一步加剧网络拥塞。引起丢包,恶化网络传输性能。
重传时间过长:接收方长时间无法完毕数据接收。引起长时间占用连接线路造成资源损耗、传输效率较低等问题。
针对上述问题,TCP中设计了超时重传机制。该机制规定当发送方A向B发送数据包P1时,开启时长为RTO(Retransmission Timeout)的重传定时器,假设A在RTO内未收到B对P1的确认报文,则觉得P1在网络中丢失。此时又一次发送P1。由此。引出RTO大小的设定问题。
决定报文是否有必要重传的主要机制是重传计时器(retransmission timer),它的主要功能是维护重传超时(RTO)值。当报文使用TCP传输时。重传计时器启动,收到ACK时计时器停止。报文发送至接收到ACK的时间称为往返时间(RTT)。
对若干次时间取平均值。该值用于确定终于RTO值。在终于RTO值确定之前,确定每一次报文传输是否有丢包发生使用重传计时器,下图说明了TCP重传过程。
当报文发送之后,但接收方尚未发送TCP ACK报文,发送方假设源报文丢失并将其重传。
重传之后。RTO值加倍;假设在2倍RTO值到达之前还是没有收到ACK报文,就再次重传。假设仍然没有收到ACK,那么RTO值再次加倍。
如此持续下去,每次重传RTO都翻倍,直到收到ACK报文或发送方达到配置的最大重传次数。
最大重传次数取决于发送操作系统的配置值。
默认情况下。Windows主机默认重传5次。大多数Linux系统默认最大15次。两种操作系统都可配置。
1)超时重传
超时重传机制用来保证TCP传输的可靠性。
每次发送数据包时,发送的数据报都有seq号,接收端收到数据后,会回复ack进行确认。表示某一seq号数据已经收到。发送方在发送了某个seq包后。等待一段时间,假设没有收到相应的ack回复,就会觉得报文丢失,会重传这个数据包。
2)高速重传
接受数据一方发现有数据包丢掉了(并非所期望的值。
这意味着报文在传送中丢失。接收端注意到报文乱序,而且在第三个报文中发送反复ACK)。就会发送反复ACK报文告诉发送端重传丢失的报文。
当重传主机从发送端接收到3个反复ACK时。它会假设此报文确实在传送中丢失,而且马上发送一个高速重传。
一旦触发了高速重传,全部正在传输的其它报文都被放入队列中,直到高速重传报文发送为止。
步骤例如以下图所看到的:
比較超时重传和高速重传,能够发现超时重传是发送端在傻等超时,然后触发重传;而高速重传则是接收端主动告诉发送端数据没收到,然后触发发送端重传。
由此可看出,高速重传机制在一定程度上弥补了超时重传机制,使得重传更加及时。
2.流量控制
这里主要说TCP滑动窗体流量控制。
滑动窗体(Sliding window )是一种流量控制技术。早期的网络通信中,通信两方不会考虑网络的拥挤情况直接发送数据。
由于大家不知道网络拥塞状况。一起发送数据,导致中间结点堵塞掉包,谁也发不了数据。所以就有了滑动窗体机制来解决此问题。
为了理解TCP的窗体大小是怎么样变化的,我们先须要理解它的含义。最简单的方式就是觉得窗体大小”意味着接收方能接收数据的大小”,这也是说接收端设备再应用程序读取buffer中数据之前。能从对端连接处理多少数据。比方说server端窗体大小是360,那么就意味着server端一次仅仅能从client接收不超过360bytes的数据。当server端收到数据,它会将数据放到buffer里,然后server端必须对这份数据做两件事:
1)server端必须发送一个 ACK 到client端来确认数据已经收到
2)server端必须处理这份数据。把它交给相应的应用程序
要区分上面两件事对理解窗体非常重要。接收方收到数据后会确认,可是数据并不一定是里面就是从buffer里取出的,这是受应用层逻辑控制的。
所以非常有可能假设接收数据过快。而取出数据更慢。就会导致buffer满。一旦这样的情况发生。窗体大小就開始调整来防止接收方负载过高。
TCP头里有一个字段叫Window,又叫Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区能够接收数据。于是发送端就能够依据这个接收端的处理能力来发送数据,而不会导致接收端处理只是来。
Window是一个16bit位字段。它代表的是窗体的字节容量,也就是TCP的标准窗体最大为2^16-1=65535个字节。
另外在TCP的选项字段中还包括了一个TCP窗体扩大因子,option-kind为3。option-length为3个字节,option-data取值范围0-14。窗体扩大因子用来扩大TCP窗体。可把原来16bit的窗体,扩大为31bit。
1)对于TCP会话的发送方,不论什么时候在其发送缓存内的数据都能够分为4类,“已经发送并得到对端ACK的”,“已经发送但还未收到对端ACK的”,“未发送但对端同意发送的”,“未发送且对端不同意发送”。“已经发送但还未收到对端ACK的”和“未发送但对端同意发送的”这两部分数据称之为发送窗体。
当收到接收方新的ACK对于发送窗体中兴许字节的确认是,窗体滑动,滑动原理例如以下图:
一个样例:
滑动窗体协议
1)比特滑动窗体协议
当发送窗体和接收窗体的大小固定为1时。滑动窗体协议退化为停等协议(stop-and-wait)。
该协议规定发送方每发送一帧后就要停下来,等待接收方已正确接收的确认(acknowledgement)返回后才干继续发送下一帧。由于接收方须要推断接收到的帧是新发的帧还是又一次发送的帧,因此发送方要为每个帧加一个序号。
由于停等协议规定仅仅有一帧全然发送成功后才干发送新的帧。因而仅仅用一比特来编号就够了。
2)后退n协议
由于停等协议要为每个帧进行确认后才继续发送下一帧,大大减少了信道利用率,因此又提出了后退n协议。后退n协议中。发送方在发完一个数据帧后,不停下来等待应答帧。而是连续发送若干个数据帧,即使在连续发送过程中收到了接收方发来的应答帧。也能够继续发送。
且发送方在每发送完一个数据帧时都要设置超时定时器。
仅仅要在所设置的超时时间内仍未收到确认帧,就要重发相应的数据帧。如:当发送方发送了N个帧后,若发现该N帧的前一个帧在计时器超时后仍未返回其确认信息。则该帧被判为出错或丢失。此时发送方就不得不又一次发送出错帧及其后的N帧。
来看以下的样例,这里假设n=9:
首先发送方一口气发送10个数据帧。前面两个帧正确返回了。数据帧2出现了错误,这时发送方*又一次发送2-8这7个帧。接受方也必须丢弃之前接受的3-8这几个帧。
从这里不难看出。后退n协议一方面因连续发送数据帧而提高了效率,但还有一方面,在重传时又必须把原来已正确传送过的数据帧进行重传(仅因这些数据帧之前有一个数据帧出了错)。这样的做法又使传送效率减少。
由此可见。若传输信道的传输质量非常差因而误码率较大时,连续測协议不一定优于停止等待协议。此协议中的发送窗体的大小为k,接收窗体仍是1。
3)选择重传协议
在后退n协议中。接收*发现错误帧就不再接收兴许的帧,即使是正确到达的帧。这显然是一种浪费。
还有一种效率更高的策略是当接收方发现某帧出错后,其后继续送来的正确的帧尽管不能马上递交给接收方的高层,但接收方仍可收下来。存放在一个缓冲区中,同一时候要求发送方又一次传送出错的那一帧。
可是必须强调一点,接收方永远不会把分组失序地交给应用层.在他们被交付给应用层之前,先要等待那些更早发出来的分组到达。一旦收到又一次传来的帧后,就能够原已存于缓冲区中的其余帧一并按正确的顺序递交高层。
这样的方法称为选择重发(SELECTICE REPEAT),其工作过程如图所看到的。显然,选择重发减少了浪费,但要求接收方有足够大的缓冲区空间。
4)零窗体问题
某些情况下,服务器无法再处理从client发送的数据。可能是由于内存不足,处理能力不够,或其它原因。这可能会造成数据被丢弃以及传输暂停,但接收窗体能够帮助减小负面影响。
当上述情况发生时,服务器会发送窗体为0的报文。当client接收到此报文时。它会暂停全部传输数据,但会保持与服务器的连接以传输探測(keep-alive Zero Window Probe)报文。探測报文在client以稳定间隙发送,以查看服务器接收窗体状态。一旦服务器能够再次处理数据,将会返回非零值窗体大小,传输会恢复。下图演示样例了零窗体通知过程。
5)Silly Window Syndrome(糊涂窗体综合症)
Silly window syndrome定义为一次仅发送少量的TCP负载数据,就像用一个飞机仅仅运送你一个人(你又不是总统,哼),这样的情况下带宽利用率非常低,一般尽量避免。
对接收端来说,window size小于某个值,能够直接ack(0)回sender,这样就把window给关闭了,也阻止了sender再发数据过来。当接收端size又一次达到MSS或者接收端缓存区的一半.
对于发送端来说。呵呵。不是有Nagle’s algorithm嘛。这个算法的思路也是延时处理,两个基本的条件1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)等待时间或是超时200ms,满足这两个条件之中的一个再发送,否则就是就接着攒数据。
3.拥塞控制
滑动窗用来做流量控制。流量控制仅仅关注发送端和接受端自身的状况。而没有考虑整个网络的通信情况。拥塞控制,则是基于整个网络来考虑的。考虑一下这样的场景:某一时刻网络上的延时突然添加,那么。TCP对这个事做出的应对仅仅有重传数据。可是。重传会导致网络的负担更重,于是会导致更大的延迟以及很多其它的丢包,于是,这个情况就会进入恶性循环被不断地放大。试想一下,假设一个网络内有成千上万的TCP连接都这么行事,那么马上就会形成“网络风暴”,TCP这个协议就会拖垮整个网络。
为此。TCP引入了拥塞控制策略。拥塞策略算法主要包括:慢启动。拥塞避免,拥塞发生。高速恢复。
对于拥塞现象,我们能够进一步用图1来描写叙述。当网络负载较小时。吞吐量基本上随着负载的增长而增长。呈线性关系。响应时间增长缓慢。当负载达到网络容量时。吞吐量呈现出缓慢增长。而响应时间急剧添加,这一点称为Knee。假如负载继续添加,路由器開始丢包,当负载超过一定量时。吞吐量開始急剧下降,这一点称为Cliff。
拥塞控制机制实际上包括拥塞避免(congestion avoidance)和拥塞控制(congestion control)两种策略。前者的目的是使网络执行在Knee四周,避免拥塞的发生;而后者则是使得网络执行在Cliff的左側区域。
前者是一种“预防”措施。维持网络的高吞吐量、低延迟状态,避免进入拥塞;后者是一种“恢复”措施。使网络从拥塞中恢复过来,进入正常的执行状态。
拥塞发生的主要原因在于网络能够提供的资源不足以满足用户的需求,这些资源包括缓存空间、链路带宽容量和中间节点的处理能力。
由于互联网的设计机制导致其缺乏“接纳控制”能力,因此在网络资源不足时不能限制用户数量。而仅仅能靠减少服务质量来继续为用户服务。也就是“尽力而为”的服务。
拥塞尽管是由于网络资源的稀缺引起的,但单纯添加资源并不能避免拥塞的发生。比如添加缓存空间到一定程度时,仅仅会加重拥塞,而不是减轻拥塞。这是由于当数据包经过长时间排队完毕转发时,它们非常可能早已超时。从而引起源端超时重发,而这些数据包还会继续传输到下一路由器。从而浪费网络资源,加重网络拥塞。同样假设单纯添加链路带宽。假设中间路由来不及优点理,就会造成大量丢包,引发拥塞。
单纯地添加网络资源之所以不能解决拥塞问题。是由于拥塞本身是一个动态问题,它不可能仅仅靠静态的方案来解决。而须要协议能够在网络出现拥塞时保护网络的正常执行。眼下对互联网进行的拥塞控制主要是依靠在源端执行的基于窗体的TCP拥塞控制机制。
网络本身对拥塞控制所起的作用较小。
拥塞控制假设分组的丢失都是由网络繁忙造成的。拥塞控制有三种动作,分别相应主机感受到的情况:
收到一条新确认。这非常好,表明当前的单次发送量小于网络的承载量。
收到三条对同一分组的确认。即三条反复的确认。单次发送量往往大于3,比如发送序号为0、10、20、30、40的5条长度为10字节的分组,当中序号20的丢了,则返回的确认是10、20、20、20。3个20就是反复的确认。
对某一条分组的确认迟迟未到,即超时。比如发送序号为0、10、20、30、40的5条长度为10字节的分组。当中序号30的丢了,则返回的确认是10、20、30、30。这才仅仅有两条反复确认。然而刚刚说过。单次发送量往往大于3。所以超时更可能是由于不止一条分组或确认丢失而引起的,这说明网络比上一情况中的更加繁忙。
上面提到拥塞控制主要是四个算法:1)慢启动,2)拥塞避免,3)拥塞发生,4)高速恢复。
1988年,TCP-Tahoe 提出了1)慢启动,2)拥塞避免,3)拥塞发生时的高速重传
1990年,TCP Reno 在Tahoe的基础上添加了4)高速恢复
1)慢热启动算法(Slow Start)
TCP拥塞控制所使用的一种算法称为慢性启动(slow start),这样的算法是基于这样的想法,它在開始时设置拥塞窗体大小(cwnd)
为一个最长段长度(MSS),每次接到一个确认时,窗体的大小就添加一个MSS值。窗体是慢速启动的。可是按指数规则增长。
開始 —> cwnd = 1
经过1个RTT后 —> cwnd = 2*1 = 2
经过2个RTT后 —> cwnd = 2*2= 4
经过3个RTT后 —> cwnd = 4*2 = 8
假设带宽为W,那么经过RTT*log2W时间就能够占满带宽。
当然,慢速启动不能一直继续下去,到达某个值必须停止该阶段。
发送方保存一个称为ssthresh(慢速启动阈值)变量,当拥塞窗体中的字节达到这个阈值时。慢速启动阶段结束而下一个阶段開始。在大多数实现中,ssthresh值是65536个字节。
2)拥塞避免:加法添加
以慢速启动算法開始,则拥塞窗体大小按指数规则增长,这样增长太快了。为了在拥塞发生之前避免拥塞,必须减少指数增长的速度。
拥塞避免的主要思想是加法增大。也就是cwnd的值不再指数级往上升,開始加法添加。
此时当窗体中全部的报文段都被确认时,cwnd的大小加1。cwnd的值就随着RTT開始线性添加,这样就能够避免增长过快导致网络拥塞,慢慢的添加调整到网络的最佳值。
3)拥塞发生算法:乘性减少
上面讨论的两个机制都是没有检測到拥塞的情况下的行为,那么当发现拥塞了cwnd又该如何去调整呢?
之前提到过。重传是在两种情况下发生:
1)假设RTO超时,那么存在非常严重的拥塞的可能性;包可能已在网络中丢失。
在这样的情况下,TCP做出强烈的反应。
a.设置阈值为cwnd的一半。
b.又一次设置cwnd为1。
c.启动慢速启动阶段。
2)假设收到3个同样的ACK,那么存在着轻度拥塞的可能性。
TCP在收到乱序到达包时就会马上发送ACK。TCP利用3个同样的ACK来判定数据包的丢失。此时进行高速重传。
在这样的情况下,TCP做出轻度的反应。
a.设置阈值为cwnd的一半。
b.设置cwnd为阈值(有些实现是阈值加上3)
c.启动拥塞避免阶段。
4)高速恢复
“高速恢复”算法是在“高速重传”算法后加入的。当收到3个反复ACK时。TCP最后进入的不是拥塞避免阶段,而是高速恢复阶段。高速重传和高速恢复算法一般同一时候使用。高速恢复的思想是“数据包守恒”原则,即同一个时刻在网络中的数据包数量是恒定的,仅仅有当“老”数据包离开了网络后,才干向网络中发送一个“新”的数据包,假设发送方收到一个反复的ACK。那么依据TCP的ACK机制就表明有一个数据包离开了网络,于是cwnd加1。假设能够严格依照该原则那么网络中非常少会发生拥塞,其实拥塞控制的目的也就在修正违反该原则的地方。
高速恢复状态是一种介于慢启动和拥塞避免之间的状态。它像慢启动,当中cwnd以指数增长,可是cwnd以ssthresh加3MSS(而不是1)開始。当TCP进入高速恢复阶段。可能发生三种主要事件。
假设反复ACK继续到达。那么TCP保持这样的状态,可是cwnd呈指数增长。
假设发生超时。TCP假设网络中有真实的拥塞,并进入慢启动状态。
假设一个新的(非反复)ACK到达,TCP进入拥塞避免阶段。可是将cwnd的大小减小到ssthresh值。好像三次反复ACK没有发生过一样,而且转换是从慢启动状态到拥塞避免状态。
详细来说高速恢复的主要步骤是:
1.当收到3个反复ACK时,把ssthresh设置为cwnd的一半。把cwnd设置为ssthresh的值加3,然后重传丢失的报文段。加3的原因是由于收到3个反复的ACK,表明有3个“老”的数据包离开了网络。
2.再收到反复的ACK时,拥塞窗体添加1。
3.当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是由于该ACK确认了新的数据,说明从反复ACK时的数据都已收到。该恢复过程已经结束。能够回到恢复之前的状态了,也即再次进入拥塞避免状态。
注意,假设在此过程出现超时,则又一次进入慢启动阶段。
好了。讲了这么多。全然能够一图概之:
參考:
http://baike.baidu.com/view/1199185.htm
http://www.knowsky.com/383954.html
http://blog.jobbole.com/71427/
http://blog.****.net/todd911/article/details/10026441
http://www.netis.com.cn/flows/2012/08/tcp-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E7%AE%80%E4%BB%8B/
http://www.firefoxbug.com/index.php/archives/2798/
http://coolshell.cn/articles/11609.html
http://www.ccs-labs.org/teaching/rn/animations/gbn_sr/
http://www.cnblogs.com/fll/archive/2008/06/10/1217013.html
http://blog.****.net/todd911/article/details/10026441
注)本文有些图片未找到出处。仅作学习用。
$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('\n').length;
var $numbering = $('
$(this).addClass('has-numbering').parent().append($numbering);
for (i = 1; i ').text(i));
};
$numbering.fadeIn(1700);
});
});