计算机网络 第三章:运输层(二)
3.4.2 流水线可靠数据传输协议
rdt3.0 是一个功能正确的协议,但是性能无法保证高效,rdt3.0 性能问题的核心在于它是一个停等协议。
为了评价该停等行为对性能的影响,可考虑一种具有两台主机的理想化场合,两台主机分别位于美国西海岸和东海岸,如图 3-17 所示。在这两个端系统之间的光速往返传播时延 RTT 大约为 30毫秒。假定彼此通过一条发送速率 R 为 1Gbps(每秒 10^9 比特)的信道相连。包括首部字段和数据的分组长 L 为 1000字节(8000比特),发送一个分组进入 1Gbps 链路的实际时间是:
图 3-18a 显示了对于该停等协议,如果发送方在 t=0 时刻开始发送分组,则在 t=L/R=8μs后,最后 1 比特数据进入了发送端信道。该分组经过 15ms(传播时延) 的穿越国家的旅途后到达接收端,该分组的最后 1 比特在时刻 =RTT/2 + VR = 15. 008ms 时到达接收方。为了简化起见,假 ACK 分组很小(以使我们可以忽略其发送时间) ,接收方一旦收到一个数据分组的最后 1 比特后立即发送 ACK,ACK 在时刻=RTT+L/R=30. 008ms 时在发送方出现。 此时,发送方可以发送下一个报文。因此,在 30.008ms 内,发送方的发送只用了0.008ms。如果我们定义发送方(或倍溢)的利用率 (utilization) 为:发送方实际忙于将发送比特送进信道的那部分时间与发送时间之比,图 3-18a 中的分析表明了停等协议有着非常低的发送方利用率:
发送方只有万分之2.7时间是忙的,从其他角度来看,发送方在 30.008ms 内只能发送 1000字节,有效的吞吐量仅为 267kbps,即使有 1Gbps 的链路可用也是如此!想象一个不幸的网络经理购买了一条千兆比容量的链路,但他仅能得到 267 kbps 吞吐量的情况,这是一个形象的网络协议限制底层网络硬件所提供的能力的图例。而且,还忽略了在发送方和接收方的底层协议处理时间,以及可能出现在发送方与接收方之间的任何中间路由器上的处理与排队时延。考虑这些因素,将进一步增加时延,使其性能更糟糕。
这种特殊的性能问题的一个简单解决方法是:不以停等方式运行,允许发送方发送多个分组而无须等待确认,如在图 3-17b 图示的那样。图 3-18b 显示了如果发送方可以在等待确认之前发送 3 个报文,其利用率也基本上提高 3 倍。 因为许多从发送方向接收方输送的分组可以被看成填充到一条流水线中,故这种技术被称作流水线(pipelining)。流水线技术对可靠数据传输协议可带来如下影响。
① 必须增加序号范围,因为每个输送中的分组(不计算重传的)必须有一个唯一的序号,而且也许有多个在输送中的未确认报文。
② 协议的发送方和接收方两端也许不得不缓存多个分组。发送方最低限度应当能缓冲那些已发送但没有确认的分组。如下面讨论的那样,接收方或许也需要缓存那些已正确接收的分组。
③ 所需序号范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏及延时过大的分组。解决流水线的差错恢复有两种基本方法是:回退 N 步( Go- Back- N , GBN) 和选择重传 (Selective Repeat , SR)。
3.4.3 回退 N 步
在 回退N步 (GBN) 协议中,允许发送方发送多个分组(当有多个分组可用时)而不需等待确认,但它也受限于在流水线中未确认的分组数不能超过某个最大允许数 N。在本节中我们较为详细地描述 GBN。
图 3-19 显示了发送方看到的 GBN 协议的序号范围。如果将基序号(base)定义为最早未确认分组的序号,将下一个序号(nextseqnum)定义为最小的未使用序号(即下一个待发分组的序号),即可将序号范围分割成 4 段。在【0,base-1】段内的序号对应于已经发送并被确认的分组。【base,nextseqnum-1】段内对应已经发送但未被确认的分组。【nextsequm,base+N-1】段内的序号能用于哪些要被立即发送的分组,如果有数据来自于上层的话。最后,大于等于 base+N 的序号是不能被使用的,直到当前流水线中未被确认的分组(序号为 base 的分组)已得到确认为止。
如图 3-19 所提示的那样,那些已被发送但还未被确认的分组的许可序号范围可以被看成是一个在序号范围内长度为 N 的窗口。随着协议的运行,该窗口在序号空间向前滑动。因此,N 常被称为窗口长度 (window size) , GBN 协议也常被称为 滑动窗口协议( sliding- window-protocol)。你也许想知道,为什么先要限制这些被发送的、未被确认的分组的数目为 N 呢?为什么不允许这些分组为无限制的数目呢?将在 3.5 节看到,流量控制是对发送方施加限制的原因之一。将在 3.7 节学习 TCP 拥塞控制时分析另一个原因。
在实践中, 一个分组的序号承载在分组首部的一个固定长度的字段中。如果分组序号字段的比特数是 k, 则该序号范围是 [0,2^K -1]。 在一个有限的序号范围内,所有涉及序号的运算必须使用模 2^k 运算。 (即序号空间可被看作是一个长度为 2^k 的环,其中序号2^k-1 紧接着序号 0)前面讲过,rdt3.0 有一个 1 比特的序号,序号范围是 [0,1 ]。在本章末的几道习题中探讨了一个有限的序号范围所产生的结果。我们将在 3.5 节看到,TCP 有一个 32 比特的序号字段,其中的 TCP 序号是按字节流中的字节进行计数的,而不是按分组计数。
图 3-20 和 图3-21 给出了一个基于 ACK、无 NAK 的 GBN 协议的发送方和接收方这两端的扩展 FSM 的描述。该 FSM 描述为 扩展FSM,是因为已经增加了变量 base 和 nextseqnum,还增加了对这些变量的操作以及与这些变量有关的条件动作。注意到该扩展的 FSM 规约现在变得优点像编程语言规约。
GBN 发送方必须响应三种类型的事件:
① 上层的调用。当上层调用 rdt_send() 时,发送方首先检查发送窗口是否已满,即是否有 N 个已发送但未被确认的分组。如果窗口未满,则产生一个分组并将其发送,并相应地更新变量。如果窗口已满,发送方只需将数据返回给上层,隐式地指上层该窗口己满。然后上层可能会过一会儿再试。在实际实现中,发送方更可能缓存(并不立刻发送)这些数据,或者使用同步机制(如一个信号量或标志)允许上层在仅当窗口不满时才调用 rdt_send()。
② 收到一个 ACK。在 GBN 协议中,对序号为 n 的分组的确认采取 累积确认的方式,表明接收方已正确接收到 序号为 n 的以前且包括 n 在内的所有分组。稍后讨论 GBN 接收方一段。
③ 超时事件。协议的名 "回退 N 步"来源于出现丢失和时延过长分组时发送方的行为。就像在停等协议中那样,定时器将再次用于恢复数据或确认分组的丢失。如果出现超时,发送方重传所有已发送但还未被确认过的分组。图 3-20 中的发送方仅使用一个定时器,它可被当作是最早的已发送但未被确认的分组所使用的定时器。如果收到一个 ACK ,但仍有已发送但未被确认的分组,则定时器被重新启动。如果没有已发送但未被确认的分组,停止该定时器。
在 GBN 中,接收方的动作也很简单。如果一个序号为 n 的分组被正确接收到,并且按序(即上次交付给上层的数据是序号为 n-1 的分组),则接收方为分组 n 发送一个ACK ,并将该分组中的数据部分交付到上层。在所有其他情况下,接收方丢弃该分组,并为最近按序接收的分组重新发送 ACK。注意到因为一次交付给上层一个分组,如果分组k 已接收并交付,则所有序号比 k 小的分组也已经交付。因此,使用累积确认是 GBN 一个自然的选择。
在 GBN 协议中,接收方丢弃所有失序分组。尽管丢弃一个正确接收(但失序)的分组有点愚蠢和浪费,但这样做是有理由的。前面讲过,接收方必须按序将数据交付给上层。假定现在期望接收分组 n,而分组 n+1 却到了。因为数据必须按序交付,接收方可能缓存(保存)分组 n+1,然后,在它收到并交付分组 n 后,再将该分组交付到上层。然而,如果分组 n 丢失,则该分组及分组 n+1 最终将在发送方根据 GBN 重传规则而被重传。因此,接收方只需丢弃分组 n+1 即可。这种方法的优点是接收缓存简单,即接收方不需要缓存任何失序分组。因此,虽然发送方必须维护窗口的上下边界及 nextseqnum 在该窗口中的位置,但是接收方需要维护的唯一信息就是下一个按序接收的分组的序号。该值保存在 expectedseqnum 变量中,如图 3-21 中接收方 FSM 所示。当然,丢弃一个正确接收的分组的确定是随后对该分组的重传也许会丢失或出错,因此甚至需要更多的重传。
图 3-22 给出了窗口长度为 4 个分组的GBN 协议的运行情况。因为该窗口长度的限制,发送方发送分组 0~3 ,然后在继续发送之前,必须等待直到一个或多个分组被确认。当接收到每一个连续的 ACK (例如 ACK0 ACK1) 时,该窗口便向前滑动,发送方便可以发送新的分组(分别是分组4 和分组 5)。在接收方,分组2 丢失,因此分组 3 4 5 被发现是失序分组并被丢弃。
在协议栈中实现该协议可能与 图3-20 中的扩展 FSM 有相似的结构。该实现也可能是以各种过程形式出现,每个过程实现了在响应各种可能出现的事件时要采取的动作。在这种基于事件的编程方式中,这些过程要么被协议栈中的其他过程调用,要么作为一次中断的结果。在发送方,这些事件包括:① 来自上层实体的调用去调用 rdt_send() ② 定时器中断 ③ 报文到达时,来自下层的调用去调用 rdt_rcv()。
3.4.4 选择重传
在图 3-17 中, GBN 协议潜在地允许发送方用多个分组" 填充流水线 ",因此避免了停等协议中所提到的信道滥利用率问题。然而,GBN 本身 也有一些情况存在着性能问题。尤其是当窗口长度和带宽时延积都很大时,在流水线中会有很多分组更是如此。单个分组的差错就能够引起 GBN 重传大量分组,许多分组根本没有必要重传。随着信道差错率的增加,流水线可能会被这些不必要重传的分组所充斥。想象一下,在口述消息的例子中,如果每次有一个单词含糊不清,其前后 1000 个单词(例如 窗口长度为 1000 个单词)不得不被重传的情况.此次口述会由于这些反复述说的单词而变慢。
顾名思义,选择重传(SR)协议通过让发送方仅重传那些它怀疑在接收方出错(即丢失或受损)的分组而避免了不必要的重传。这种个别的、按需的重传要求接收方逐个地确认正确接收的分组。再次用窗口长度 N 来限制流水线中未完成、未被确认的分组数。然而,与 GBN 不同的是,发送方已经收到了对窗口中某些分组的 ACK。图 3-23 显示了 SR 发送方看到的序号空间。图 3-24 详细描述了 SR 发送方所采取的动作。
SR 接收方将确认一个正确接收的分组而不管其是否按序。失序的分组将被缓存直到所有丢失分组(即序号更小的分组)皆被收到为止,这时才可以将一批分组按序交付给上层。图 3-25 详细列出了 SR 接收方所采用的各种动作,图 3-26 给出了一个例子以说明出现丢包时 SR 的操作。值得注意的是,在图 3-26 中接收方初始时缓存了分组 3、4、5,并在最终收到分组 2 时,才将它们一并交付给上层。
注意到图 3-25 中的第二步很重要,接收方重新确认(而不是忽略)已收到过的那些序号小于当前窗口基序号的分组。应该理解这种重新确认确实是需要的。例如,给定在图 3-23 中所示的发送方和接收方的序号空间,如果分组 send_base ACK 没有从接收方传播回发送方,则发送方最终将重传分组 send_base,即使显然(对我们而不是对发送方来说!)接收方已经收到了该分组。如果接收方不确认该分组,则发送方窗口将永远不能向前滑动!这个例子说明了 SR 协议(和很多其他协议一样)的一个重要方面。对于哪些分组已经被正确接收,哪些没有,发送方和接收方并不总是能看到相同的结果。SR 协议而言,这就意味着发送方和接收方的窗口并不总是一致的。
当面对有限序号范围的现实时,发送方和接收方窗口间缺乏同步会产生严重的后果。考虑下面例子中可能发生的情况,该例有包括 4 个分组序号 0、1、2、3 的有限序号范围且窗口长度为 3.假定发送了分组 0至2,并在接收方正确接收且确认了。此时,接收方窗口落在第 4 5 6 个分组上,其序号分别为 3 0 1。限制考虑两种情况,在第一种情况下,如图 3-27a,对前三个分组的 ACK 丢失,因此发送方重传这些分组。因此,接收方下一步要接收序号为 0 的分组,即第一个发送分组的副本。
在第二种情况下,如图3-27b,对前 3 个分组的 ACK 都被正确交付。因此发送方向前移动窗口并发送第 4 5 6个分组,其序号分别为 3 0 1。序号为 3 的分组丢失,但序号为 0 的分组到达(一个包含新数据的分组)。
现在考虑一下图 3-27 中接收方的观点,在发送方和接收方之间有一个假想的帘子,因为接收方不能" 看见 "发送方采取的动作。接收方所能观察到的是它从信道中收到的以及它向信道中发出报文序列。就其所关注的而言,图 3-27 中的两种情况是等同的。没有办法区分是第 1 个分组的重传还是第 5 个分组的初次传输。显然,窗口长度比序号空间小1时协议无法工作。但窗口必须多小呢?窗口长度必须小于或等于序号空间大小的一半。
3.5 面向连接的运输:TCP
TCP 是因特网运输层的面向连接的可靠的运输协议。为了提供可靠数据传输,TCP依赖于前一节的许多基本原理,其中包括差错检测、重传、累积确认、定时器以及用于序号和确认号的首部字段。
3.5.1 TCP 连接
TCP 被称为是面向连接的( connection- oriented ) ,这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互 "握手',即它们必须相互发送某些预备报文段 ,以建立确保数据传输的参数。作为 TCP 连接建立的一部分,连接的双方都将初始化与 TCP 连接相关的许多 TCP 状态变量。
这种 TCP " 连接 "不是一条像在电路交换网络中的端到端 TDM 或 FDM 电路。相反,该" 连接 "是一条逻辑连接,其共同状态仅保留在两个通信端系统的 TCP 程序中。前面讲过,由于 TCP 协议只在端系统中运行,而不在中间的网络元素(路由器和链路层交换机)中运行,所以中间的网络元素不会维持 TCP 连接状态。事实上,中间路由器对 TCP 连接完全视而不见,它们看到的是数据报,而不是连接。
TCP 连接提供的是全双工服务。如果一台主机上的进程 A 与另一台主机上的进程 B 存在一条 TCP 连接,那么应用层数据就可在从进程 B 流向进程 A 的同时,也从进程 A 流向 进程 B。TCP 连接也总是点对点的,即在单个发送方与单个接收方之间的连接。所谓 “ 多播 ”,即在依次发送操作中,从一个发送方将数据传送给多个接收方,这对 TCP 是不可能的。对于 TCP 而言,两台主机是一对。
来看 TCP 连接是怎样建立的。假设运行在某台主机上的一个进程想与另一台主机上的一个进程建立一条连接。发起连接的这个进程称为客户进程,而另一个进程被称为服务器进程。该客户应用进程首先要通知客户运输层,他想与服务器上的一个进程建立一条连接。一个 Python 客户程序通过发出下面的命令来实现:
clientSocket.connect((serverName,serverPort))
其中 serverName 是服务器名字,serverPort 标识了服务器上的进程。客户上的 TCP 便开始与服务器上的 TCP 建立一条 TCP 连接。后面将更为详细地讨论连接建立的过程。现在知道下列事实就可以了:客户首先发送一个特殊的 TCP 报文段,服务器用另一个特殊的 TCP 报文段来响应,最后,客户再用第三个特殊报文段作为响应。前两个报文段不承载" 有效载荷 ",也就是不包含应用层数据;而第三个报文段可以承载有效载荷。由于在这两台主机之间发送了三个报文段,所以这种连接建立过程常被称为 三次握手。
一旦建立起一条 TCP 连接,两个应用进程之间就可以相互发送数据了。考虑一下从客户进程向服务器进程发送数据的情况。如 2.7 节中所述,客户进程通过套接字(该进程之门)传递数据流。数据一旦通过该门,它就由客户中运行的 TCP 控制了。如图 3-28所示, TCP 将这些数据引导到该连接的发送缓存 (send buffer) 里,发送缓存是发起三次握手期间设置的缓存之一。 接下来 TCP 就会不时从发送缓存里取出一块数据,并将数据传递到网络层。有趣的是,在 TCP 规范 【RFC 793】 中却没提及 TCP 应何时实际发送缓存里的数据,只是描述为 "TCP 应该在它方便的时候以报文段的形式发送数据"。 TCP 可从缓存中取出并放入报文段中的数据数量受限于最大报文段长度(MSS)。MSS 通常根据最初确定的由本地发送主机发送的最大链路层帧长度(即最大传输单元(MTU))来设置。设置该 MSS 要保证一个 TCP报文段(当封装在一个 IP 数据报中)加上 TCP/IP 首部长度(通常 40 字节)将适合单个链路层帧。以太网和 PPP 链路层协议都具有 1500 字节的 MTU ,因此 MSS 的典型值为 1460字节。已经提出了多种发现路径 MTU 的方法,并基于路径 MTU 值设置 MSS (路径 MTU是指能在从源到目的地的所有链路上发送的最大链路层帧 [RFC 1191])。注意到 MSS指在报文段里应用层数据的最大长度,而不是指包括首部的 TCP 报文段的最大长度。
TCP 为每块客户数据配上一个 TCP 首部,从而形成多个 TCP 报文段(TCP segment)。这些报文段被下传给网络层,网络层将其分别封装在网络层 IP 数据报中。然后这些 IP 数据报被发送到网络中。当 TCP 在另一端接收到一个报文段后,该报文段的数据就被放入该 TCP 连接的接收缓存中,如图 3-28 中所示。应用程序从此缓存中读取数据流。该连接的每一端都有各自的发送缓存和接收缓存。
3.5.2 TCP 报文段结构
TCP 报文段由首部字段和一个数据字段组成。数据字段包含一块应用数据。如前所述, MSS 限制了报文段数据字段的最大长度。当 TCP 发送一个大文件,例如某 Web 页面上的一个图像时, TCP 通常是将该文件划分成长度为 MSS 若干块(最后一块除外,通常小于 MSS)。然而,交互式应用通常传送长度小于 MSS 的数据块。例如,对于像 Telnet 这一的远程登录应用,其报文段的数据字段经常只有一个字节。由于 TCP 的首部一般是 20字节(比UDP首部多12字节),所以 Telnet 发送的报文段也许只有 21 字节长。
图 3-29 显示了 TCP 报文段的结构。与 UDP 一样,首部包括源端口号和目的端口号,被用于多路复用/分解来自或送到上层应用的数据。另外,同 UDP 一样,TCP 首部也包括检验和字段。TCP 报文段首部还包含下列字段:
① 32 比特的序号字段和 32 比特的确认号字段:这些字段被 TCP 发送方和接收方用来实现可靠数据传输服务。
② 16 比特的接收窗口字段:该字段用于流量控制。指示接收方愿意接受的字节数量。
③ 4 比特的首部长度字段:指示了以 32 比特的字为单位的 TCP 首部长度。由于 TCP 选项字段的原因,TCP 首部的长度是可变的。(通常选项字段为空,所以TCP首部的典型长度是20字节)
④ 可选与变长的选项字段:该字段用于发送方与接收方协商最大报文段长度 (MSS) 时,或在高速网络环境下用作窗口调节因子时使用。首部字段中还定义了一个时间戳选项。
⑤ 6 比特的标志字段:ACK 比特用于指示确认字段中的值是有效的,即该报文段包括一个对已被成功接收报文段的确认。RST SYN 和 FIN 比特用于连接建立和拆除,我们将在本节后面讨论该问题。在明确拥塞通告中使用了 CWR 和ECE 比特,如 3.7.2 节中讨论的那样。当 PSH 比特被置位时,就指示接收方应立即将数据交给上层。最后,URG 比特用来指示报文段里存在着被发送端的上层实体置为"紧急"的数据。紧急数据的最后一个字节由 16 比特的紧急数据指针字段(urgent data pointer fìeld) 指出 。当紧急数据存在并给出指向紧急数据尾指针的时候, TCP 必须通知接收端的上层实体。
1.序号和确认号
TCP 报文段首部中两个最重要的字段是序号字段和确认号字段,是 TCP 可靠传输服务的关键部分。
TCP 把数据看成一个无结构的、有序的字节流。从 TCP 对序号的使用上可以看出这一点,因为序号是建立在传送的字节流之上,而不是建立在传送的报文段的序列之上。一个报文段的序号 (sequence number for a segment) 因此是该报文段首字节的字节流编号。举例来说 ,假设主机 A 上的 1个进程想通过 1 条 TCP 连接向主机 B 上的一个进程发送一个数据流。主机 A 中的 TCP 将隐式地对数据流中的每一个字节编号。假定数据流由一个包含 50000 字节的文件组成,其 MSS 为 1000 字节,数据流的首字节编号是 0.如图3-30所示,该 TCP 将为该数据流构建 500 个报文段。给第一个报文段分配序号 0,第二个报文段分配序号 1000 ,第三个报文段分配序号 2000 ,以此类推。每一个序号被填入到相应 TCP报文段首部的序号字段中。
现在考虑确认号。TCP 是全双工的,因此,主机 A 在向主机 B 发送数据的同时,也许接收来自主机 B 的数据(都是同一条 TCP 连接的一部分)。从主机 B 到达的每个报文段都有一个序号用于从 B 流向 A 的数据。主机 A 填充进报文段的确认号是主机 A 期望从主机 B 收到的下一字节的序号。例如,假设主机A已经收到了来自主机 B 的编号为 0~535 的所有字节,同时假设它打算发送一个报文段给主机 B。主机 A 等待主机 B 的数据流中字节 536 及之后的所有字节。所以主机 A 会在它发往主机 B 的报文段的确认号字段中填上 536.
再举一个例子,假设主机 A 已收到 个来自主机 B 的包含字节 0-535 的报文段,以及另一个包含字节 900-1000 的报文段。由于某种原因,主机 A 还没有收到字节 536~899 的报文段。在这个例子中, 主机 A 为了重新构建主机 B 的数据流,仍在等待字节 536(和其后的字节)。因此,A 到 B 的下一个报文段将在确认号字段中包含 536。因为 TCP 只确认该流中至第一个丢失字节为止的字节,所以 TCP 被称为提供累积确认。
最后一个例子也会引发一个重要而微妙的问题。主机 A 在收到第二个报文段(字节536 - 899)之前收到第三个报文段(字节 900 - 1000)。因此,第三个报文段失序到达。该微妙的问题是:当主机在 一条TCP 连接中收到失序报文段时该怎么办?有趣的是, TCP RFC 并没有为此明确规定任何规则,而是把这一问题留给实现 TCP 的编程人员去处理。有两个基本的选择:① 接收方立即丢弃失序报文段(如前所述,这可以简化接收方的设计) ② 接收方保留失序的字节,并等待缺少的字节以填补该问隔。显然,后一种选择对网络带宽而言更为有效,是实践中采用的方法。
图 3-30 中,假设初始序号为 0。事实上,一条 TCP 连接的双方均可随机地选择初始序号。这样做可以减少将那些仍在网络中存在的来自两台主机之间先前已终止的连接的报文段。误认为是后来这两台主机之间新建连接所产生的有效报文段的可能性(碰巧与旧连接使用了相同的端口号)。
2. Telnet:序号和确认号的一个学习案例
Telnet 是一个用于远程登录的流行应用层协议。运行在 TCP 之上,被设计成可在任意一对主机之间工作。它是一个交互式应用。讨论一个 Telnet 例子。
假设主机 A 发起一个与主机 B 的 Telnet 会话。因为是主机 A 发起该会话,因此被标记为客户,而主机 B 被标记为服务器。(在客户端的)用户键入的每个字符都会被发送至远程主机;远程主机将回送每个字符的副本给客户,并将这些字符显示在 Telnet 用户的屏幕上。这种“ 回显 ” (echo back)用于确保由 Telnet 用户发送的字符已经被远程主机收到并在远程站点上得到处理。因此,在从用户击键到字符被显示在用户屏幕上这段时间内,每个字符在网络中传输了两次。
现在假设用户输入了一个字符 ‘C’,考察一下在客户与服务器之间发送的 TCP 报文段。如图 3-31所示,假设客户和服务器的起始序号分别是 42、79。前面讲过,一个报文段的序号就是该报文段数据字段首字节的序号。因此,客户发送的第一个报文段的序号为 42,服务器发送的第一个报文段的序号为 79.前面讲过,确认号就是主机正在等待的数据的下一个字节序号。在 TCP 连接建立后但没有发送任何数据之前,该客户等待字节 79 ,而该服务器等待字节 42。
如图 3-31 中所示,共发送三个报文段。第一个报文段是由客户发往服务器,在它的数据字段里包含一字节的字符 'C '的 ASCII 码。如刚讲的那样,第一个报文段的序号字段里是 42。另外,由于客户还没有接收到来自服务器的任何数据,因此该第一个报文段中的确认号字段中是 79。
第二个报文段是由服务器发往客户。它有两个目的:首先它是为该服务器所收到数据提供一个确认。通过在确认号字段中填入 43 ,服务器告诉客户它已经成功地收到字节 42及以前的所有字节,现在正等待着字节 43 的出现。该报文段的第二个目的是回显字符' C '。因此,在第二个报文段的数据字段里填入的是字符 'c'的 ASCII 码。第二个报文段的序号为 79 它是该 TCP 连接上从服务器到客户的数据流的起始序号,这也正是服务器要发送的第一个字节的数据。值得注意的是,对客户到服务器的数据的确认被装载在承载服务器到客户的数据的报文段中;这种确认被称为是被捎带( piggybacked )在服务器到客户的数据报文段中的。
第三个报文段是从客户发往服务器的。它的唯一目的是确认已从服务器收到的数据(前面讲过,第二个报文段中包含的数据是字符 'C' ,是从服务器到客户的。)该报文段的数据字段为空(即确认信息没有被任何从客户到服务器的数据所捎带)。该报文段的确认号字段填入的是 80 ,因为客户已经收到了字节流中序号为 79 及以前的字节,它现在正等待着字节 80 的出现。你可能认为这有点奇怪,即使该报文段里没有数据还仍有序号,这是因为 TCP 存在序号字段,报文段需要填入某个序号。
3.5.3 往返时间的估计与超时
TCP 如同前面 3.4 节所讲的 rdt 协议一样,它采用超时/重传机制来处理报文段的丢失问题。
尽管这在概念上简单,但是当在如 TCP 这样的实际协议中实现超时/重传机制时还是会
产生许多微妙的问题。也许最明显的一个问题就是超时间隔长度的设置。显然,超时间隔必
须大于该连接的往返时间 (RTT) ,即从一个报文段发出到它被确认的时间。否则会造成不
必要的重传。但是这个时间间隔到底应该是多大呢?刚开始时应如何估计往返时间呢?是否
应该为所有未确认的报文段各设一个定时器?
1. 估计往返时间
TCP 是如何估计发送方与接收方之间往返时间的。通过如下方法完成:报文段的样本 RTT(SampleRTT)就是从某报文段被发出(交给IP)到对该报文段的确认被收到之间的时间量。大多数 TCP 的实现仅在某个时刻做一个 SampleRTT 测量,而不是为每个发送的报文段测量一个 SampleRTT。就是说,在任意时刻,仅为一个已发送的但目前尚未被确认的报文段估计 SampleRTT,从而产生一个接近每个 RTT 的新Sample RTT 值。另外,TCP 决不为已被重传的报文段计算 SampleRTT;它仅为传输一次的报文段测量 SampleRTT。
显然,由于路由器的拥塞和端系统负载的变化,这些报文段的 SampleRTT 值会随之波动。由于这种波动,任何给定的 SampleRTT 值也许都是非典型的。因此,为了估计一个典型的 RTT,自然要采取某种对 SampleRTT 取平均的办法。TCP 维持一个 SampleRTT 均值(称为 EstimatedRTT)。一旦获得一个新 SampleRTT 时,TCP 就会根据下列公式来更新 EstimatedRTT:
即 EstimatedRTT 的新值是由以前的 EstimatedRTT 值与 SampleRTT 新值加权组合而成的,其中,α 的推荐值为 0.125(1/8)。这种平均被称为指数加权移动平均(EWMA)。
除了估算 RTT 外,测量 RTT 的变化也是有价值的。定义了 RTT 偏差 DevRTT,用于估算 SampleRTT 一般会偏离 EstimatedRTT 的程度:
DevRTT=(1-β)*DevRTT+β*|SampleRTT-EstimatedRTT|
注意到 DevRTT 是一个 SampleRTT 与 EstimatedRTT 之间差值的 EWMA。如果 SampleRTT 值波动较小,那么 DevRTT 值就会很小,反之,SampleRTT 值波动较大,DevRTT 的值就会很大。β 推荐值为 0.25。
2.设置和管理重传超时间隔
假设已经给出了 EstimatedRTT 值和 DevRTT 值,那么 TCP 超时间隔应该为什么值?很明显,超时间隔应该大于 EstimatedRTT,否则将造成不必要的重传。但是超时间隔也不能超出 EstimatedRTT 太多,否则报文段丢失时,TCP 不能很快地重传该报文段,导致数据传输时延大。因此要求将超时间隔设为 EstimatedRTT 加上一定余量。当 SampleRTT 值波动较大时,该余量应该大些,波动较小时,余量应该小些。因此重传超时间隔为:
TimeoutInterval = EstimatedRTT + 4* DevRTT
推荐的初始 Timeoutlnterval 值为 1秒。同时,当出现超时后 Timeoutlnterval 值将加倍,以免即将被确认的后继报文段过早出现超时。然而,只要收到报文段并更新 EstimatedRTT,就使用上述公式再次计算。
3.5.4 可靠数据传输
因特网的网络层服务(IP 服务)是不可靠的。lP 不保证数据报的交付,不保证数据报的按序交付,也不保证数据报中数据的完整性。对于 IP 服务,数据报能够溢出路由器缓存而永远不能到达目的地,数据报也可能是乱序到达,而且数据报中的比特可能损坏(由 0变为1 或者相反)。由于运输层报文段是被 IP 数据报携带着在网络中传输的,所以运输层的报文段也会遇到这些问题。
TCP 在 IP 不可靠的尽力而为服务之上创建了一种可靠数据传输服务(reliable data transfer service)。TCP 的可靠数据传输服务确保一个进程从其接收缓存中读出的数据流是无损坏、无间隙、非冗余和按序的数据流;即该字节流与连接的另一方端系统发送出的字节流是完全相同。 TCP 提供可靠数据传输的方法涉及在 3.4 节中所学的许多原理。
将以两个递增的步骤来讨论 TCP 是如何提供可靠数据传输的。我们先给出一个 TCP发送方的高度简化的描述,该发送方只用超时来恢复报文段的丢失;然后再给出一个更全面的描述,该描述中除了使用超时机制外,还使用冗余确认技术。在接下来的讨论中,假定数据仅向一个方向发送,即从主机 A到主机 B,且主机 A 在发送一个大文件。
图3-33 给出了一个 TCP 发送方高度简化的描述。看到在 TCP 发送方有 3 个与发送和重传有关的主要事件:从上层应用程序接收数据;定时器超时和收到 ACK。一旦第一个主要事件发生, TCP 从应用程序接收数据,将数据封装在一个报文段中,并把该报文段交给 IP。注意到每一个报文段都包含一个序号 ,如 3.5.2 节所讲的那样,这个序号就是报文段第一个数据字节的字节流编号。还要注意到如果定时器还没有为某些其他报文段而运行,则当报文段被传给 IP 时, TCP 就启动该定时器。( 将定时器想象为与最早的未被确认的报文段相关联是有帮助的。)该定时器的过期间隔是 Timeoutlnterval ,它是 3.5.3中所描述的 EstimatedRTT 和 DevRTT 计算得出的。
第二个主要事件是超时。TCP 通过重传引起超时的报文段来响应超时事件。然后 TCP 重启定时器。
TCP 发送方必须处理的第三个主要事件是,到达一个来自接收方的确认报文段(ACK)(更确切的说,是一个包含了有效 ACK 字段值的报文段)。该事件发生时,TCP 将 ACK 的值 y 与它的变量 Sendbase 进行比较。TCP 状态变量 SendBase 是最早未被确认的字节的序号。 因此 SendBase-1 是指接收方已正确按序接收到的数据的最后一个字节的序号。如前面指出的那样,TCP 采用累积确认,所以 y 确认了字节编号在 y 之前的所有字节均已收到。如果 y>SendBase,则该 ACK 是在确认一个或多个先前未被确认的报文段。因此发送方更新它的 SendBase 变量;如果当前有未被确认的报文段,TCP 还要重新启动定时器。
1. 一些有趣的情况
刚刚描述了一个关于 TCP 如何提供可靠数据传输的高度简化版本。 但即使这种高度简化的版本,仍然存在着许多微妙之处。为了较好地感受该协议的工作过程,来看几种简单情况。图3-34 描述了第一种情况,主机 A 向主机 B 发送一个报文段。假设该报文段的序号是 92 ,而且包含 8 字节数据。在发出该报文段之后,主机 A 等待一个来自主机 B 的确认号为 100 的报文段。虽然 A 发出的报文段在主机B上被收到,但从主机B发往主机 A 的确认报文丢失了。在这种情况下,超时事件就会发生,主机 A 会重传相同的报文段。当然,当主机 B 收到该重传的报文段时,它将通过序号发现该报文段包含了已收到的数据。因此,主机 B 中的 TCP 将丢弃该重传的报文段中的这些字节。
第二种情况中,如图3-35所示,主机 A 连续发回了两个报文段。第一个报文段序号是 92,包含 8 字节数据;第二个报文段序号是 100,包含 20 字节数据。假设两个报文段都完好无损地到达主机 ,并且主机 B 为每一个报文段分别发送一个确认。第一个确认报文的确认号是 100,第二个确认报文的确认号是 120。现在假设在超时之前这两个报文段中没有一个确认报文到达主机A。当超时事件发生时,主机 A重传序号 92 的第一个报文段,并重启定时器。只要第二个报文段的 ACK 在新的超时发生以前到达,则第二个报文段将不会被重传。
第三种也是最后一种情况,假设主机 A 与在第二种情况下完全一样,发送两个报文段。第一个报文段的确认报文在网络丢失,但在超时事件发生之前 A 收到一个确认号为 120 的确认报文。主机 A 因而知道主机 B 已经收到了序号为 119 及之前的所有字节;所以主机 A 不会重传这两个报文段中的任何一个。该情况在图 3-36 中进行图示。
2.超时间隔加倍
现在讨论一下在大多数 TCP 实现中所做的一些修改。首先关注的是在定时器时限过期后超时间隔的长度。在这种修改中,每当超时事件发生时,如前所述,TCP 重传具有最小序号的还未被确认的报文段。只是每次 TCP 重传时都会将下一次的超时间隔设置为原来的两倍,而不是用从 EstimatedRTT 和 DevRTT 推算出来的值。
例如,假设当定时器第一次过期时,与最早未被确认的报文段相关联的 TimeoutInterval 是 0.75 秒。TCP 就会重传该报文段,并把新的过期时间设置为 1.5 秒。如果 1.5 秒后定时器又过期了,则 TCP 将再次重传该报文段,并把过期时间设置为 3.0 秒。因此,超时间隔在每次重传后会呈指数型增长。然而,每当定时器在另两个事件(收到上层应用的数据和收到 ACK)中的任意一个启动时,TimeoutIntervak 由最近的 EstimatedRTT 与 DevRTT 值推算得到。
这种修改提供了一个形式受限的拥塞控制。(更复杂的 TCP 拥塞控制形式将在 3.7中学习)定时器过期很可能是由网络拥塞引起的,即太多的分组到达源与目的地之间路径上的一台(或多台)路由器的队列中,造成分组丢失或长时间的排队时延。在拥塞的时候,如果源持续重传分组,会使拥塞更加严重。相反, TCP 使用更文雅的方式,每个发送方的重传都是经过越来越长的时间间隔后进行的。
3. 快速重传
超时触发重传存在的问题之一是超时周期可能相对较长。当一个报文段丢失时,这种长超时周期迫使发送方延迟重传丢失的分组,因而增加了端到端时延。幸运的是,发送方通常可在超时事件发生之前通过注意所谓冗余 ACK 来较好地检测到丢包情况。冗余 ACK (duplicate ACK) 就是再次确认某个报文段的 ACK ,而发送方先前已经收到对该报文段的确认。要理解发送方对冗余 ACK 的响应,我们必须首先看下接收方为什么会发送冗余 ACK。表 3-2 总结了 TCP 接收方的 ACK 生成策略。TCP 接收方收到一个具有这样序号的报文段时,即其序号大于下一个所期望的、按序的报文段,它检测到了数据流中的一个问隔,这就是说有报文段丢失。这个间隔可能是由于在网络中报文段丢失或重新排序造成的。因为 TCP 不使用否定确认,所以接收方不能向发送方发回一个显式的否定确认。相反,它只是对已经接收到的最后一个按序字节数据进行重复确认(即产生一个冗余 ACK))即可。(注意到在表3-2中允许接收方不丢弃失序报文段。)
因为发送方经常一个接一个地发送大量的报文段,如果一个报文段丢失,就很可能引起许多一个接一个的冗余 ACK。如果 TCP 发送方接收到对相同数据的 3 个冗余 ACK,它把这当作一种指示,说明跟在这个已被确认过 3 次的报文段之后的报文段已经丢失。一旦收到 3 个冗余 ACK,TCP 就执行快速重传(fast retransmit),即在该报文段的定时器过期之前重传丢失的报文段。对于采用快速重传的 TCP ,可用下列代码片段代替图3-33 中的 ACK 收到事件:
4.回退 N 步还是选择重传
考虑下面这个问题:TCP 是一个 GBN 协议还是一个 SR 协议?TCP 确认是累积式的,正确接收但失序的报文段是不会被接收方逐个确认的。因此如 图3-33 所示,TCP 发送方仅需维持已发送过但未被确认的字节的最小序号和下一个要发送的字节的序号。这种意义下,TCP 更像是 GBN 风格的协议。但二者有一些显著的区别,许多 TCP 实现会将正确接收单失序的报文段缓存起来,另外考虑,当发送方发送的一组报文段 1,2....N,并且所有的报文段都按序无差错地到达接收方时会发生的情况。进一步假设对分组 n<N 的确认报文丢失,但是其余 N-1 个确认报文在分别超时以前到达发送端,这时又会发生的情况。在该例中,GBN 不仅会重传 n,还会重传其后分组 n+1、n+2、...N。另一方面,TCP将重传至多一个报文段n。此外,如果对报文段 n+1 的确认报文在报文段 n 超时之前到达,TCP 甚至不会重传报文段 n。
对 TCP 提出的一种修改意见是所谓的选择确认,它允许 TCP 接收方有选择地确认失序报文段,而不是累积地确认最后一个正确接收的有序报文段。当将该机制与选择重传机制结合起来使用时(跳过重传那些已被接收方选择性确认过的报文段),TCP 看起来像通常的 SR 协议。因此,TCP 的差错恢复机制也许最好被分类为 GBN 协议 和 SR 协议的混合体。