简单说说TCP(2) --- 数据传输


基本原理

TCP提供可靠的数据传输服务。建立连接后,应用程序将数据提交给TCP,TCP将数据放入自己的缓存。TCP会将数据按照MSS的大小进行分段,并为每个分段加上TCP头部后提交给网络层。


可靠性保障

  • 确认到达 + 超时重传:防止丢包
  • 分组序列号:确保数据包的顺序
  • 检验和:防止信道上的bit错误
  • 流量控制(滑动窗口):防止发送方太快而接收方太慢

传输效率

“发一个TCP分组,等待一个ACK,再发下一个”

这种工作方式效率太低了,带宽利用率也低。

为了提高效率,仿照流水线原理,TCP允许发送方发送多个分组而无需等待确认。

但是这样做,带来了2个问题:

  • 发多少分组合适?
  • 分组出现丢失、出错、超时等异常情况时,如何处理?

为了解决这2个问题,TCP退出了滑动窗口差错恢复机制。


滑动窗口

滑动窗口提高了传输效率,同时起到流量控制的作用。

具体过程如下图:
这里写图片描述

糊涂窗口

发送端应用进程产生数据很慢、或接收端应用进程处理接收缓冲区数据很慢,或二者都存在时,通过TCP连接传输的报文段会很小,这会导致有效载荷很小(IP头部 + TCP头部就40字节了)。

引发原因不同,糊涂窗口的应对策略也会不同。

  • 发送方引起的糊涂窗口
    解决办法是防止TCP发送过小的报文段。如果应用提交的数据较短,就等待足够的数据来组成一个较大的报文段再发送。为了防止长时间等待导致时延过大,可以加入一个等待时间限制,如果时间到期还没等到足够的数据就直接发送不再等待。

    例如:
    Nagle算法
    规则如下:

    • 如果包长度达到MSS,则允许发送;
    • 如果该包含有FIN,则允许发送;
    • 设置了TCP_NODELAY选项,则允许发送;
    • 未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
    • 上述条件都未满足,但发生了超时(一般为200ms),则立即发送;
    • 其他情况,一直组包,只要有ACK到达,就将组好的包发出去;

    打开TCP_NODELAY选项,可禁用该算法

    CORK算法
    设置TCP_CORK选项后,内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去,当然若一定时间后(一般为200ms,该值尚待确认),内核仍然没有组合成一个MTU时也必须发送现有的数据。

  • 接收方引起的糊涂窗口
    解决方法有2种:

    • Clark解决方法,只要有数据到达就发送确认,但通告的窗口大小为零,这个过程持续到缓存空间已能放入具有最大长度的报文段或者缓存空间的一半已经空了。
    • 延迟确认,接收方不立即确认收到的报文段,在确认收到的报文段之前一直等待,直到入缓存有足够的空间为止。该方法阻止了发送端滑动其窗口,当发送端发送完其数据后,它就停下来了。这样就防止了这种症状。延迟的确认还减少了通信量。接收端不需要确认每一个报文段。但它有可能使发送端重传其未被确认的报文段。可以给延迟的确认加一个时间限制来降低该方法缺点的影响。

差错恢复

TCP的流水线差错恢复采用了GBN + 选择重传相结合的方式。

TCP采用了累积确认的方式,这类似于GBN,即如果TCP发送了对某个序号N的确认,则表明在N之前的所有字节流都已经被正确接收。但是另一方面,TCP又不会像GBN协议那样简单丢弃失序到达的报文段,而是会将它们缓存起来,但是这些被缓存的报文段不会逐个被确认。当发生超时时,TCP只会重传发生超时的那一个报文段。TCP还允许接收方选择性的确认失序到达的分组,而不是累积的对最后一个确认。


超时与重传

超时值计算

RTT:报文段被发出到报文段的确认被收到的时间间隔。

TCP不会为每一个发动的报文段测量一个SampleRTT,而是仅为已发送但是未被确认的分组测量SampleRTT。这样做是为了产生一个近似于RTT的SampleRTT。TCP不会为重传的报文段测量SampleRTT。得到多个SampleRTT后,TCP会尝试使用这些信息来尽可能得到一个较为准确的RTT,为此TCP采用了经常被采用的收到即使用一个滤波器来对多个SampleRTT进行计算。

TCP使用如下的滤波器来计算一个EstimateRTT:

EstimateRTT= (1- α) * EstimateRTT  +α * SampleRTT

RFC2988给出的α参考值为1/8。EstimateRTT 是一个平滑后的RTT。

除此之外,TCP还将RTT的变化率也应该考虑在内,如果变化率过大,则通过以变化率为自变量的函数为主计算RTT(如果陡然增大,则取值为比较大的正数,如果陡然减小,则取值为比较小的负数,然后和平均值加权求和),反之如果变化率很小,则取测量平均值。TCP计算了一个DevRTT。它用于估量SampleRTT偏离EstimateRTT的程度。其公式为:

DevRTT= (1-β)* DevRTT +  β* |SampleRTT - EstimateRTT |

β的参考值为1/4。
之后重传定时器的值会被设置为EstimateRTT + 4 * DevRTT。

重传间隔

在发生超时重传时,TCP不是以固定的时间间隔来重传的,而是会再每次重传时都将下一次重传的间隔设置为上次重传间隔的2倍,因此重传间隔是倍数增加的。直到收到确认或者彻底失败。由于正常发送报文段时,重传定时器的超时值为EstimateRTT + 4 * DevRTT,因此第一重传时会将下一次的超时时间设置为2倍的该值,依次类推。

快速重传

倍数增加的重传间隔会增大端到端的时延,使得发送端可能不得不等待很长时间才能重传报文段。冗余ACK使得TCP可以得到分组丢失的线索。TCP基于冗余ACK提供了一种快速重传机制。其原理是:如果收到了对相同数据的三个冗余的ACK,发送端就认为跟在这个被确认了三次的报文段之后的报文段丢失了,因此重传它,而不是等待它的超时定时器到期。


拥塞控制

在广域网中,当传输时出现了瓶颈(比如说一定要经过一个slip低速链路),会出现大量数据堵塞问题。所以,TCP发送方需要确认连接双方的线路的数据最大吞吐量是多少,也就是确定所谓的拥塞窗口的大小。

拥塞窗口的原理很简单,TCP发送方首先发送一个数据报,然后等待对方的回应,得到回应后就把这个窗口的大小加倍,然后连续发送两个数据报,等到对方回应以后,再把这个窗口加倍(先是2的指数倍,到一定程度后就变成现行增长,这就是所谓的慢启动),发送更多的数据报,直到出现超时错误,这样,发送端就了解到了通信双方的线路承载能力,也就确定了拥塞窗口的大小,发送方就用这个拥塞窗口的大小发送数据。要观察这个现象是非常容易的,我们一般在下载数据的时候,速度都是慢慢“冲起来的”。

TCP连接的每一端都保证它所已经发送的未被确认的报文段的总大小不会超过拥塞窗口和对方通告的窗口大小中的较小的那一个。

拥塞控制的方法:

  • 慢开始(slow-start)
  • 拥塞避免(congestion avoidance)
  • 快重传(fast retransmit)
  • 快恢复(fast recovery)

TCP头部6个标志位之一 - URG

TCP提供了“紧急方式(urgentmode)”,它使连接的一端可以告诉另连接的一端有些 “紧急数据”已经被放置在数据流中。紧急数据的处理方式由接收方决定。

要发送紧急数据需要设置TCP首部中的两个字段。URG比特被置1,并且要将16bit的紧急指针设置为一个正的偏移量,该偏移量必须与TCP首部中的序号字段相加,以便得出紧急数据的最后一个字节的序号。

展开阅读全文

没有更多推荐了,返回首页