TCP/IP(三)TCP数据传输过程

内容概述

1. TCP的交互数据流 - 以Telnet、Rlogin交互程序传输数据流为例

  1. TCP通信量研究发现按照分组数量计算,TCP报文段包含成块数据:若按交互数据,按分组数量计算,各占50%,若按字节计算则比例为9:1,因为成块数据的报文段基本上都是满长度(full-sized)的(通常为512字节的用户数据),而交互数据则小得多(上述研究表明Telnet和Rlogin分组中通常约90%左右的用户数据小于10个字节)。
  2. TCP延时确认、Nagle算法减少小分组的数目
  3. TCP延迟确认:TCP延迟确认是传输控制协议的一些实现所使用的技术,以努力改善网络性能。实质上,可以将几个ACK响应组合在一起成为单个响应,从而减少协议开销。但是,在某些情况下,该技术可能会降低应用程序性能(见后文)。
    • 交互式输入,通常每一个交互按键都会产生一个数据分组,通过延时ACK,可以有效减少网络的负载
    • 当Rologin键入5个字符date\n时的数据流,产生了多个数据包交互
    • 通常TCP在接收到数据时并不立即发送ACK;相反,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(有时称这种现象为数据捎带ACK)。绝大多数实现采用的时延为200 ms,也就是说,TCP将以最大200 ms的时延等待是否有数据一起发送。
    • TCP使用了一个200 ms的定时器,实现定时发送
    • 主机可以延迟发送ACK响应最多500毫秒,此外,对于全尺寸传入段的流,必须为每个第二段发送ACK响应。
  4. Nagle算法:
    • 在一个Rlogin连接上客户一般每次发送一个字节到服务器,这就产生了一些41字节长的分组:20字节的IP首部、20字节的TCP首部和1个字节的数据(小包问题),在局域网上,这些小分组(被称为微小分组(tinygram))通常不会引起麻烦,因为局域网一般不会出现拥塞,但在广域网上,这些小分组则会增加拥塞出现的可能。
    • Nagle算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。
    • TCP收集这些少量的分组,并在确认到来时以一个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送得越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组。
    • 在局域网环境下两个主机之间发送数据时很少使用这个算法。
    • 关闭Nagle算法:必须无时延地发送应用,比如X窗口系统服务器,Socket API用户可以使用TCP_NODELAY选项来关闭Nagle算法。
  5. 每当TCP接收到一个超出期望序号的失序数据时,它总是发送一个确认序号为其期望序号的确认
  6. 交互数据总是以小于最大报文段长度的分组发送。在Rlogin中通常只有一个字节从客户发送到服务器。Te lnet允许一次发送一行输入数据,但是目前大多数实现仍然发送一个字节
  7. 对于这些小的报文段,接收方使用经受时延的确认方法来判断确认是否可被推迟发送,以便与回送数据一起发送。这样通常会减少报文段的数目,尤其是对于需要回显用户输入字符的Rlogin会话。
  8. 在较慢的广域网环境中,通常使用Nagle算法来减少这些小报文段的数目。这个算法限制发送者任何时候只能有一个发送的小报文段未被确认。
  9. Nagle算法与TCP延迟确认相互作用问题:延迟ACK引入的额外等待时间在与某些应用程序和配置交互时可能导致进一步的延迟
    • 该算法与TCP延迟确认会有不好的相互作用,例如当程序发送端进行两次连续的小段写再跟着读时,接收端接收到第一次写后因TCP延迟确认(接收端稍后有数据要发送),而等待第二次写后一并发送ACK,发送端则因第二次写数据长度小于MSS而等待第一次写的ACK,最终将导致两对端都进入等待直到ACK延迟超时。
    • 因为这个原因,TCP实现通常为应用程序提供一个禁用Nagle算法的接口(通常称为TCP_NODELAY选项),用户级解决方案是避免套接字上的写-写-读 序列。写-读-读写-写-写都是没问题的。但 写-写-读 则是性能杀手。
    • 所以,如果可以的话,缓冲对TCP的小段写,然后一次发送它们,在每次读之前使用标准的UNIX I/O包并冲刷写缓存通常能起作用。
  10. Nagle算法描述:
// MSS = 最大分段大小
if有新資料要傳送
    if訊窗大小>= MSS and可傳送的資料>= MSS
     立刻傳送完整MSS大小的segment
else
    if管線中有尚未確認的資料
      在下一個確認(ACK)封包收到前,將資料排進緩衝區佇列
    else
      立即傳送資料

2. TCP的成块数据流

  1. 滑动窗口:TCP基于滑动窗口协议进行流量控制,该协议允许发送方在停止并等待确认前可以连续发送多个分组,以加速数据的传输。
  2. 慢启动、成块数据流的吞吐量
  3. 网络IP数据包到达后的一些事项处理流程:
    1. 硬中断:当一个分组到达时,它首先被设备中断例程进行处理,网卡驱动将其放入放置到IP的输入队列中
    2. IP将按同样顺序将它们交给TCP模块,当TCP处理报文段时,连接有可能被标记为产生一个经受时延的确认(还有数据要发,延迟ACK响应),当TCP有多个未完成的报文段需要确认,可以通过产生一个已接收序号的的ACK响应,同时清除该连接产生经受时延的确认标志
  4. 滑动窗口协议:
    • 使用TCP的滑动窗口协议时,接收方不必确认每一个收到的分组。在TCP中,ACK是累积的—它们表示接收方已经正确收到了一直到确认序号减1的所有字节。
  5. 网络传输的复杂性:发送方TCP的实现、接收方TCP的实现、接收进程读取数据(依赖于操作系统的调度)和网络的动态性(如以太网的冲突和退避等),没有一种单一的、正确的方法来交换给定数量的数据。
  6. 快的发送方和慢的接收方:当发送方发送很快,但接收方的TCP缓冲区满时候(因为应用程序还没有机会读取这些数据),则接收方会发送一个窗口大小为0的ACK响应包(win 0)来描述此类情况;当接收方应用程序从TCP缓冲区获取了所有的数据后,会再发一个更新的窗口ACK包给到发送方,表明自己可以开始接收了(此类情况称为窗口更新
  7. 滑动窗口图示,窗口大小是与确认序号相对应的,发送方计算它的可用窗口,该窗口表明多少数据可以立即被发送,当接收方确认数据后,这个滑动窗口不时地向右移动。
  8. 三个术语来描述窗口左右边沿的运动:
    • 窗口合拢:窗口左边沿向右边沿靠近,发生在数据被发送和确认时
    • 窗口张开:窗口右边沿向右移动时将允许发送更多的数据,发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存,
    • 窗口收缩:窗口右边沿向左移动时,RFC强烈建议不要使用这种方式,但TCP必须能够在某一端产生这种情况时进行处理
  9. 接收方向发送方通告其窗口为0(将使发送方停止),当窗口张开时,需要接收方发送另一个窗口非0的ACK来使发送方重新启动(后续通告窗口和滑动窗口可理解为同样概念)
  10. 如果接收到一个指示窗口左边沿向左移动的ACK,则它被认为是一个重复ACK,则网卡会丢弃该包,另外关于滑动窗口几点注意:
    • MSS(maximum segment size)最大分段长度,在TCP连接建立阶段确定
    • 发送方不必发送一个全窗口大小的数据
    • 来自接收方的一个报文段确认数据并把窗口向右边滑动,这是因为窗口的大小是相对于确认序号的。
    • 窗口的大小可以减小,但是窗口的右边沿却不能够向左移动
    • 接收方在发送一个ACK前不必等待窗口被填满
  11. 窗口大小可修改:由接收方提供的窗口的大小通常可以由接收进程控制,这将影响TCP的性能。4.2BSD默认设置发送和接收缓冲区的大小为2048个字节。在4.3BSD中双方被增加为4096个字节。如Solaris 2.2、4.4BSD和AIX3.2则使用更大的默认缓存大小,如8192或16384等。
  12. PUSH标志:发送方使用该标志通知接收方将所收到的数据全部提交给接收进程,这里的数据包括与PUSH一起传送的数据以及接收方TCP已经为接收进程收到的其他数据(接收缓存区内的数据)。即当服务器的TCP接收到一个设置了PUSH标志的报文段时,它需要立即将这些数据递交给服务器进程而不能等待判断是否还会有额外的数据到达。伯克利的实现一般从不将接收到的数据推迟交付给应用程序(立即交付),因此它们忽略所接收的PUSH标志。
  13. 若发送方TCP知道它有4个可立即发送的报文段(比如接收方告知可以发送4个大小的报文段),可以只设置了最后一个报文段(13)的PUSH标志。
  14. 慢启动:
    • 如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,一些中间路由器必须缓存分组,并有可能耗尽存储器的空间,TCP需要支持一种被称为“慢启动(slow start)”的算法。该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作。
    • 慢启动为发送方的TCP增加了另一个窗口:拥塞窗口(congestion window),记为cwnd。
  15. 拥塞窗口:由发送者维护,拥塞窗口是一种阻止发送方和接收方之间的链路流量过载的手段,它是通过估计链路上的拥塞程度来计算的,是一个涵盖链路上通信设备的全局过程,其目的是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载,同时也希望尽可能提高传输速率。
    • 当发送方与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小 - 通告窗口)。
    • 每当发送方收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。
    • 发送方取拥塞窗口与通告窗口中的最小值作为发送上限。
    • 拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。
  16. 拥塞控制原则:只要网络没有出现拥塞,拥塞窗口(cwnd)就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。
  17. 拥塞控制方法:
    • 慢开始( slow-start ):在不清楚网络负荷情况下,逐步适应链路网络,由小到大逐渐增大分组数量(按指数增加1,2,4,8..),为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量,超过阈值门,则采用线性增加
    • 拥塞避免( congestion avoidance ):让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是指数增加,这样拥塞窗口cwnd按线性规律缓慢增长
    • 快重传( fast retransmit )
    • 快恢复( fast recovery )。
  18. 拥塞窗口与拥塞控制图示:
  19. 成块数据的吞吐量:
    • 窗口大小、窗口流量控制(滑动长款)以及慢启动,都对传输成块数据的TCP连接的吞吐量有影响作用
    • 通常发送一个分组的时间取决于两个因素:传播时延(由光的有限速率、传输设备的等待时间等引起)和一个取决于媒体速率(即媒体每秒可传输的比特数)的发送时延
    • 对于一个给定的两个接点之间的通路,传播时延一般是固定的,而发送时延则取决于分组的大小。在速率较慢的情况下发送时延起主要作用,而在千兆比特速率下传播时延则占主要地位
    • 发送方和接收方之间的管道(pipe)被填满。此时不论拥塞窗口和通告窗口是多少,它都不能再容纳更多的数据。每当接收方在某一个时间单位从网络上移去一个报文段,发送方就再发送一个报文段到网络上。但是不管有多少报文段填充了这个管道,返回路径上总是具有相同数目的ACK。这就是连接的理想稳定状态。
  20. 带宽时延乘积:窗口应该设置为多大的问题,通道容量计算:
    • capacity (bit) = bandwidth (b/s) × round-trip time (s)
  21. 拥塞:
    • 大管道向小管道发送报文的情况:当数据到达一个大的管道(如一个快速局域网)并向一个较小的管道(如一个较慢的广域网)发送时便会发生拥塞。
    • 路由器过载:当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞。
  22. 紧急方式 - URG: 得出紧急数据的最后一个字节的序号 = 紧急URG包SEQ+URG偏移量
    • TCP提供了“紧急方式(urgent mode)”,它使一端可以告诉另一端有些具有某种方式的“紧急数据”已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流中,由接收方决定如何处理。
    • 可以通过设置TCP首部中的两个字段来发出这种从一端到另一端的紧急数据已经被放置在数据流中的通知。URG比特被置1,并且一个16bit的紧急指针被置为一个正的偏移量,该偏移量必须与TCP首部中的序号字段相加,以便得出紧急数据的最后一个字节的序号。
  23. 紧急方式有什么作用:交互用户键入中断键时,可以放弃文件的传输
  24. 没有一种单一的方法可以使用TCP进行成块数据的交换,这是一个依赖于许多因素的动态处理过程,有些因素我们可以控制(如发送和接收缓存的大小),而另一些我们则没有办法控制(如网络拥塞、与实现有关的特性等),进行成块数据有效传输的最重要的方法是TCP的滑动窗口协议。我们考察了TCP为使发送方和接收方之间的管道充满来获得最可能快的传输速度而采用的方法。我们用带宽时延乘积衡量管道的容量,并分析了该乘积与窗口大小之间的关系

3. TCP的超时与重传

  1. TCP相关实际算法:慢启动、拥塞避免、快速重传和快速恢复
  2. 慢启动算法:
    • 慢启动算法是在一个连接上发起数据流的方法,但有时我们会达到中间路由器的极限,此时分组将被丢弃。
    • 慢启动算法初始设置cwnd为1个报文段,此后每收到一个确认让窗口按指数方式增长:发送1个报文段,然后是2个,接着是4个……
  3. 拥塞避免算法:
    • 拥塞避免算法是一种处理丢失分组的方法。该算法假定由于分组受到损坏引起的丢失是非常少的(远小于1%),因此分组丢失就意味着在源主机和目的主机之间的某处网络上发生了拥塞。有两种分组丢失的指示:发生超时和接收到重复的确认,如果使用超时作为拥塞指示,则需要使用一个好的RTT算法
    • 拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希望降低分组进入网络的传输速率,于是可以调用慢启动来作到这一点。在实际中这两个算法通常在一起实现。
    • 拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh
      • 对一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节
      • TCP输出例程的输出不能超过cwnd和接收方通告窗口(滑动窗口)的大小,拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制
      • 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动)。
      • 当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处位置的半时候才停止(因为我们记录了在步骤2中给我们制造麻烦的窗口大小的一半),然后转为执行拥塞避免。
  4. 术语“慢启动”并不完全正确。它只是采用了比引起拥塞更慢些的分组传输速率,但在慢启动期间进入网络的分组数增加的速率仍然是在增加的。只有在达到ssthresh拥塞避免算法起作用时,这种增加的速率才会慢下来。
  5. 快重传算法:
    • 在收到一个失序的报文段时,TCP立即需要产生一个ACK(一个重复的ACK)。这个重复的ACK不应该被迟延。该重复的ACK的在于让对方知道收到一个失序的报文段,并告诉对方自己希望收到的序号。由于我们不知道一个重复的ACK是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们等待少量重复的ACK到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1~2个重复的ACK。如果一连串收到3个或3个以上的重复ACK,就非常可能是一个报文段丢失了。
    • 于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出。这就是快速重传算法。接下来执行的不是慢启动算法而是拥塞避免算法,这就是快速恢复算法(快速恢复:流量过载,数据包丢失,通过拥塞避免将发送端分包入网速度降下来,没有直接使用慢启动,是因为考虑不希望让数据流突然慢下来)。
  6. 快速恢复:
    • 当收到第3个重复的ACK时,将ssthresh设置为当前拥塞窗口cwnd的一半。重传丢失的报文段。设置cwnd为ssthresh加上3倍的报文段大小。
    • 每次收到另一个重复的ACK时,cwnd增加1个报文段大小并发送1个分组(如果新的cwnd允许发送)。

4. TCP的定时器

  1. TCP通过让接收方指明希望从发送方接收的数据字节数(即滑动窗口大小)来进行流量控制,这将有效地阻止发送方传送数据,直到窗口变为非0为止。
  2. 死锁情况:如果一个确认丢失了,则双方就有可能因为等待对方而使连接终止:接收方等待接收数据(因为它已经向发送方通告了一个非0的窗口),而发送方在等待允许它继续发送数据的窗口更新。
  3. 窗口探查(window probe):发送方使用一个坚持定时器(persist timer)来周期性(计算坚持定时器时使用了普通的TCP指数退避)地向接收方发送ACK查询报文,以便发现窗口是否已增大,从而避免死锁情况
  4. TCP指数退避,发送端的查询退避这个过程将持续到或者窗口被打开,或者应用进程使用的连接被终止(TCP连接超时)
  5. 糊涂窗口综合征

5. TCP的保活定时器

  1. 我们可以启动一个客户与服务器建立一个连接,然后离去数小时、数天、数个星期或者数月,而连接依然保持。中间路由器可以崩溃和重启,电话线可以被挂断再连通,但是只要两端的主机没有被重启,则连接依然保持建立。这意味着两个应用进程—客户进程或服务器进程—都没有使用应用级的定时器来检测非活动状态,而这种非活动状态可以导致应用进程中的任何一个终止其活动,比如通过独立于TCP的保活定时器之外的应用定时器。
  2. 许多时候一个服务器希望知道客户主机是否崩溃并关机或者崩溃又重新启动,许多实现提供的保活定时器可以提供这种能力,但注意保活并不是TCP规范中的一部分(导致连接断开、带宽浪费、资金浪费), 许多人认为如果需要,TCP连接保活的这个功能,不应该在TCP中提供,而应该由应用程序来完成。
  3. 实例:当个人计算机用户使用TCP/IP向一个使用Telnet的主机注册时,客户端未通过注销而是直接通过关闭电源,导致服务器上留下一个半开放连接,在等待来自客户的数据,则服务器将永远等待下去,保活功能就是试图在服务器端检测到这种半开放的连接。