TCP/IP详解11-传输层:TCP的交互数据流、成块数据流

TCP/IP详解11-传输层:TCP的交互数据流、成块数据流

目前建立在TCP协议上的网络协议特别多,有telnet,ssh,有ftp,有http等等。这些协议又可以根据数据吞吐量来大致分成两大类:

  • (1)交互数据类型,例如telnet,ssh,这种类型的协议在大多数情况下只是做小流量的数据交换,比如说按一下键盘,回显一些文字等等。
  • (2)成块数据类型,例如ftp,这种类型的协议要求TCP能尽量的运载数据,把数据的吞吐量做到最大,并尽可能的提高效率。针对这两种情况,TCP给出了两种不同的策略来进行数据传输。

1. TCP的交互数据流

对于交互性要求比较高的应用,TCP给出两个策略来提高发送效率和减低网络负担:

  • (1)经受时延的 ACK。
  • (2)Nagle算法(一次尽量多的发数据)。

Snip20180730_1

远程交互按键回显会产生4个报文段:

  • (1)来自客户的交互按键;
  • (2)来自服务器的按键确认;
  • (3)来自服务器的按键回显;
  • (4)来自客户的按键回显确认。

一般可以将报文段 2 和 3 进行合并—按键确认与按键回显一起发送。

1.1 经受时延的 ACK

这个策略是说,当主机收到远程主机的TCP数据报之后,通常不马上发送ACK数据报,而是等上一个短暂的时间,如果这段时间里面主机还有发送到远程主机的TCP数据报,那么就把这个ACK数据报“捎带”着发送出去,把本来两个TCP数据报整合成一个发送。一般的,这个时间是200ms。可以明显地看到这个策略可以把TCP数据报的利用率提高很多。

Snip20180730_6

  • 从上图中可以看出,在bsdi端,连个红框之间的时间差大约是200ms,这就是经受时延的 ACK。
    • 如果观察bsdi接收到数据和发送ACK之间的时间差,就会发现它们似乎是随机的:123.5、65.6、109.0、132.2、42.0、140.3和195.8 ms。相反,观察到发送ACK的实际时间(从0开始)为:139.9、539.3、940.1、1339.9、1739.9、1940.1 和 2140.1 ms(在图红框)。 这些时间之间的差则是 200 ms的整数倍,这里所发生的情况是因为 TCP 使用了一个 200 ms 的定时器,该定时器以相对于内核引导的 200 ms固定时间溢出。由于将要确认的数据是随机到达的(在时刻 16.4, 474.3, 831.1等),TCP 在内核的 200 ms 定时器的下一次溢出时得到通知。这有可能是将来1~200 ms中的任何一刻。
  • 如果观察 svr4 为产生所收到的每个字符的回显所使用的时间,则这些时间分别为 16.5、16.3、16.5、16.4和17.3 ms。由于这个时间小于200 ms,因此我们在另一端从来没有观察到一个经受时延的ACK。在经受时延的定时器溢出前总是有数据需要发送(如果有一个约为 16 ms 等待时间越过了内核的 200 ms时钟滴答的边界,则仍可以看到一个经受时延的 ACK。在本例中我们一个也没有看到)。

1.2 Nagle算法

Nagle算法是说,当主机 A 给主机 B 发送了一个 TCP 数据报并进入等待主机 B 的 ACK 数据报的状态时,TCP 的输出缓冲区里面只能有一个 TCP 数据报,并且,这个数据报不断地收集后来的数据,整合成一个大的数据报,等到 B 主机的 ACK 包一到,就把这些数据“一股脑”的发送出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送得越快。

在编写插口程序的时候,可以通过 TCP_NODELAY 来关闭这个算法。并且,使用这个算法看情况的,比如基于 TCP 的 X 窗口协议,如果处理鼠标事件时还是用这个算法,那么“延迟”可就非常大了。

Snip20180730_7

  • 比较图 19-4 与图 19-3,我们首先注意到从 slip 到 vangogh 不存在经受时延的ACK。这是因为在时延定时器溢出之前总是有数据等待发送。
  • 其次,注意到从左到右待发数据的长度是不同的,分别为:1、1、2、1、2、2、3、1 和 3 个字节。这是因为客户只有收到前一个数据的确认后才发送已经收集的数据。通过使用 Nagle 算法,为发送 16 个字节的数据客户只需要使用 9 个报文段,而不再是 16 个。

流程:

  • (1)发送端 TCP 将从应用进程接收到的第一数据块立即发送,不管其大小,哪怕只有一个字节。
  • (2)发送端输出第一块数据后开始收集数据,并等待确认。
  • (3)确认未达到时,若收集数据达到窗口的一半或一个 MSS 段,立即发送。
  • (4)确认到达后,把缓冲区中的数据组成一个 TCP 段,然后发送。

2. TCP的成块数据流

对于 FTP 这样对于数据吞吐量有较高要求的要求,将总是希望每次尽量多的发送数据到对方主机,就算是有点“延迟”也无所谓。TCP 也提供了一整套的策略来支持这样的需求。TCP 协议中有 16 个 bit 表示“窗口”的大小,这是这些策略的核心。

2.1 正常数据流

在解释滑动窗口前,需要看看 ACK 的应答策略,一般来说,发送端发送一个TCP数据报,那么接收端就应该发送一个 ACK 数据报。但是事实上却不是这样,发送端将会连续发送数据尽量填满接受方的缓冲区,而接受方对这些数据只要发送一个 ACK 报文来回应就可以了,这就是 ACK 的累积特性(在 TCP 中,ACK 表示接收方已经正确收到了一直到确认序号减 1 的所有字节),这个特性大大减少了发送端和接收端的负担。

例子:

Snip20180731_11

2.2 TCP的流量控制—滑动窗口

TCP 提供了流量控制服务以消除发送方使接收方缓存区溢出的可能性,因此可以说流量控制是一个速度匹配服务(匹配发送方的发送速率与接收方的读取速率)。

在通信过程中,接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小,这就是接收窗口rwnd,即调整TCP报文段首部中的“窗口”字段值,来限制发送方向网络注入报文的速率。同时,发送方根据其对当前网络拥塞程序的估计而确定的窗口值,称为拥塞窗口cwnd,其大小与网络的带宽和时延密切相关。

2.2.1 滑动窗口

滑动窗口本质上是描述接收方的 TCP 数据报缓冲区大小的数据,发送方根据这个数据来计算自己最多能发送多长的数据。如果发送方收到接收方的窗口大小为 0 的 TCP 数据报,那么发送方将停止发送数据,等到接收方发送窗口大小不为 0 的数据报的到来。

Snip20180801_14

在这个图中,我们将字节从 1 至 11 进行标号。接收方通告的窗口称为提出的窗口( offered window),它覆盖了从第 4 字节到第 9 字节的区域,表明接收方已经确认了包括第 3 字节在内的数据,且通告窗口大小为 6。

我们使用三个术语来描述窗口左右边沿的运动:

  • 1) 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据被发送和确认时。
  • 2) 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发生在另一端的接收进程读取已经确认的数据并释放了 TCP 的接收缓存时。
  • 3) 当右边沿向左移动时,我们称之为窗口收缩。Host Requirements RFC 强烈建议不要使用这种方式。

如果左边沿到达右边沿,则称其为一个零窗口,此时发送方不能够发送任何数据。

Snip20180801_13

2.2.2 例子:

Snip20180801_15

  • 1) 发送方不必发送一个全窗口大小的数据
  • 2) 来自接收方的一个报文段确认数据并把窗口向右边滑动。这是因为窗口的大小是相对于确认序号的
  • 3) 正如从报文段 7 到报文段 8 中变化的那样,窗口的大小可以减小,但是窗口的右边沿却不能够向左移动。
  • 4) 接收方在发送一个 ACK 前不必等待窗口被填满。在前面我们看到许多实现每收到两个报文段就会发送一个 ACK。

发送端窗口随时间滑动图(不考虑重传)例如下所示:

这里写图片描述

  • 1)我们一共需要发送900字节数据。可发送数据为1-500字节,尚未发送数据。假设首先发送400字节的数据。
  • 2)发送了400字节后,对端返回一个ack表示收到200序号之内的数据且窗口通告为500。于是如图示,窗口向前滑动了200字节。当前已发送未确认字节序号为200-400,可发送字节序号为401-700,假设在此尚未发送数据。
  • 3)对端返回一个ack表示收到400序号内的数据且窗口通告为400。于是如图示,窗口向前滑动了200字节。已确认数据序号为1-400,可发送数据为401-800。

接收端窗口通告

snd_wnd此字段主要由接收端的窗口通告决定,接收端窗口通告由当前接收端剩余多少空闲的剩余缓存决定。如下图所示:

这里写图片描述

  • 1)发送端:写入2KB的数据[seq=0]。
  • 2)接收端:收到数据,初始化接收端缓冲区4K,写入后还剩2K,于是通告ack[seq=2048,win=2048]。
  • 3)发送端:接收到窗口通告为2048,于是最多只能写入2K的数据,将2K数据写入[seq=2048]。
  • 4)接收端:应用层尚未消费缓冲区。接收到2K数据后,缓冲区满。于是通告窗口为0,返回ack[seq=4096,win=0]。
  • 5)发送端:由于发送窗口为0,不能发送任何数据。此时发送端就需要定时的发送1字节的数据去探测接收端窗口。所需的定时器即为持续定时器(TCPT_PERSIST)。
  • 6)发送端:发送0字节的探测数据。
  • 7)接收端:缓冲区满,窗口通告为0,ack[seq=4096,win=0]。
  • 8)发送端:继续发送0字节的探测数据。
  • 9)接收端:缓冲区被应用层消费了2K,缓冲区可用字节为2K,通告窗口为2048,ack[seq=4096,win=2048]。
  • 10)发送端:继续写入1K的数据。
2.2.3 窗口大小

窗口的大小是可以通过 socket 来制定的,4096 并不是最理想的窗口大小,而16384 则可以使吞吐量大大的增加。

2.2.4 PUSH标志

发送方使用该标志通知接收方将所收到的数据全部提交给接收进程。

在最初的TCP规范中,一般假定编程接口允许发送进程告诉它的 TCP何时设置PUSH标志。通过允许客户应用程序通知其 TCP 设置 PUSH 标志,客户进程通知 TCP 在向服务器发送一个报文段时不要因等待额外数据而使已提交数据在缓存中滞留。类似地,当服务器的 TCP 接收到一个设置了 PUSH 标志的报文段时,它需要立即将这些数据递交给服务器进程而不能等待判断是否还会有额外的数据到达。

然而,目前大多数的 API 没有向应用程序提供通知其 TCP 设置 PUSH 标志的方法。

2.2.5 滑动窗口协议与停止等待协议的区别

滑动窗口协议中,允许发送方发送多个分组(当有多个分组可用时), 而不需等待确认,但它受限于在流水线中未确认的分组数不能超过某个最大允许数 N。

滑动窗口协议是 TCP 使用的一种控制流量的方法, 此协议能够加速数据的传输。 只有在接收窗口向前滑动时(与此同时也发送了确认), 发送窗口才有可能向前滑动。收发两端的窗口按照以上规律不断地向前滑动, 因此这种协议称为滑动窗口协议

当发送窗口和接收窗口的大小都等于1时,就是停止等待协议

2.3 TCP 重新发送

2.3.1 TCP 片段丢失
2.3.2 超时重新发送

我们之前已经简单介绍过重新发送的机制:当发送方送出一个TCP片段后,将开始计时,等待该TCP片段的ACK回复。如果接收方正确接收到符合次序的片段,接收方会利用ACK片段回复发送方。发送方得到ACK回复后,继续移动窗口,发送接下来的TCP片段。如果直到计时完成,发送方还是没有收到ACK回复,那么发送方推断之前发送的TCP片段丢失,因此重新发送之前的TCP片段。这个计时等待的时间叫做重新发送超时时间(RTO, retransmission timeout)

发送方应该在等待多长时间之后重新发送呢?这是重新发送的核心问题。上述过程实际上有往返两个方向:1. 发送片段从发送方到接收方的传输,2. ACK片段从接收方到发送方的传输。整个过程实际耗费的时间称做往返时间(RTT, round trip time)。如果RTT是固定的,比如1秒,那么我们可以让RTO等于RTT。但实际上,RTT的上下浮动很大。

要求 RTO 精确的原因有两个:(1)如果设置的 RTO 太长会造成网络利用率不高。(2)RTO 太短会造成多次重传,使得网络阻塞。所以,书中给出了一套经验公式,和其他的保证计时器准确的措施。

最早的TCP曾经用了一个非常简单的公式来估计当前网络的状况,如下

RαR+(1α)MRTO=Rβ R ← α R + ( 1 − α ) M R T O = R β

α α 是一个推荐值为 0.9 的平滑因子, β β 通常为 2。注意,这是经验,没有推导过程,这个数值是可以被修改的。这个公式是说用旧的 RTT(R) 和新的 RTT(M) 综合到一起来考虑新的 RTT(R) 的大小。但是,我们又看到,这种估计在网络变化很大的情况下完全不能做出“灵敏的反应”,于是就有下面的修正公式:

Err=MAAA+gErrDD+h(|Err|D)RTO=A+4D E r r = M − A A ← A + g E r r D ← D + h ( | E r r | − D ) R T O = A + 4 D

上式中, A A 是被平滑的 RTT(均值的估计器)而 D 则是被平滑的均值偏差。 Err E r r 是刚得到的测量结果与当前的 RTT 估计器之差。 A A D 均被用于计算下一个重传时间(RTO)。增量 g g 起平均作用,取为 1/80.125。偏差的增益是 h h ,取值为 0.25。

注意,这两组公式更新,都是在数据成功传输的情况下才进行,在发生数据重新传输的情况下,并不使用上面的公式进行网络估计,理由很简单,因为程序已经不在正常状态下了,估计出来的数据也是没有意义的。

RTO的初始化

RTO的初始化是由公式决定的,例如最初的公式,初始的值应该是1。而修正公式,初始 RTO 应该是A+4D

RTO的更新

当数据正常传输的情况下,我们就会用上面的公式来更新各个数据,并重开定时器,来保证下一个数据被顺利传输。要注意的是:重传的情况下,RTO 不用上面的公式计算,而采用一种叫做“指数退避”的方式。例如:当 RTO 为 1S 的情况下,发生了数据重传,我们就用 RTO=2S 的定时器来重新传输数据,下一次用 4S。一直增加到 64S 为止。

RTT 估计器的初始化
RTT 估计器的更新

和上面的讨论差不多,就是在正常情况下,用上面的公式计算,在重传的情况下,不更新估计器的各种参数。原因还是因为估计不准确。

Karn算法

这不算是一个算法,这应该是一个策略,说的就是更新RTO和估计器的值的时机选择问题。当一个超时和重传发生时, 在重传数据的确认最后到达之前,不能更新 RTT 估计器,因为我们并不知道 ACK 对应哪次传输。

RTO 的使用

两句话:

  • 一个连接中,有且仅有一个测量定时器被使用。也就是说,如果 TCP 连续发出 3 组数据,只有一组数据会被测量。
  • ACK 数据报不会被测量,原因很简单,没有 ACK 的 ACK 回应可以供结束定时器测量。

2.4 TCP 拥塞控制

如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。

TCP用拥塞窗口(cwnd)来进行拥塞控制,主要利用了慢启动、拥塞避免、快速重传和快速恢复这四个算法。

发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。

为了便于讨论,做如下假设:

  • 接收方有足够大的接收缓存,因此不会发生流量控制;
  • 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。

这里写图片描述

2.4.1 慢启动与拥塞避免

发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 …

注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。

如果出现了超时,则令 ssthresh = cwnd/2,然后重新执行慢开始。

算法具体说明:

拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口 cwnd 和一个慢启动门限 ssthresh。这样得到的算法的工作过程如下:

  • 1) 对一个给定的连接,初始化 cwnd 为 1 个报文段,ssthresh 为 65535个字节。
  • 2) TCP 输出例程的输出不能超过 cwnd 和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
  • 3) 当拥塞发生时(超时或收到重复确认),ssthresh 被设置为当前窗口大小的一半( cwnd 和接收方通告窗口大小的最小值,但最少为 2 个报文段)。此外,如果是超时引起了拥塞,则 cwnd 被设置为1个报文段(这就是慢启动)。
  • 4) 当新的数据被对方确认时,就增加 cwnd, 但增加的方法依赖于我们是否正在进行慢启 动或拥塞避免。如果 cwnd 小于或等于 ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处位置的半时候才停止(因为我们记录了在步骤 2 中给我们制造麻烦的窗口大小的一半),然后转为执行拥塞避免。
2.3.2 快速重传与快速恢复算法

在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。

在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。

在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。

慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。
这里写图片描述

算法具体说明:

  • 1) 当收到第 3 个重复的 ACK 时,将 ssthresh 设置为当前拥塞窗口 cwnd 的一半。重传丢失的报文段。设置 cwnd 为 ssthresh 加上 3 倍的报文段大小。
  • 2) 每次收到另一个重复的 ACK 时,cwnd 增加 1 个报文段大小并发送 1 个分组(如果新的 cwnd 允许发送)。
  • 3) 当下一个确认新数据的 ACK 到达时,设置 cwnd 为 ssthresh(在第1 步中设置的值)。这个 ACK 应该是在进行重传后的一个往返时间内对步骤 1 中重传的确认。另外,这个 ACK 也应该是对丢失的分组和收到的第 1 个重复的 ACK 之间的所有中间报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的速率减半。

参考

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值