第12章后续 (tcpip协议详解的笔记)

12.1 引言

到目前为止,我们一直在讨论那些自身不包含可靠传递数据机制的协议,它们可能会使用校验和或CRC这样的数学函数来检测接收到的数据是否有差错,但是它们不尝试纠正差错。对于IP和UDP,根本没有实现差错纠正。对于以太网和基于以太网的其它协议,协议提供一定次数的重试,如果还是不成功则放弃。

通信媒介可能会丢失或改变传递的消息,在这种环境下的通信问题已经被研究了许多年。关于这个课题的一些最重要的理论工作由克劳德·香农在1948年给出[S48],这些工作普及了术语“比特”,并成为信息理论(information theory)领域的基础,帮助我们理解了在一个有损(可能会删除或改变比特)信道里可通过的信息量的理论极限。信息理论与编码理论(coding theory)领域密切相关,编码理论提供不同的信息编码手段,从而使得信息能在通信信道里尽量免于出错。使用纠错码(基本上是添加一些冗余的比特,使得即使某些比特受损,真实的信息也可以被恢复过来)来纠正通信问题是处理差错的一种非常重要的方法。另一种方法是简单地“尝试重新发送”,直到信息被接收,这种方法被称为自动重传请求(Automatic Repeat Request,ARQ),构成了包括TCP在内的许多通信协议的基础。

12.1.1 ARQ和重传

如果我们考虑的不是单个通信信道,而是几个级联的多跳信道,我们会发现不只会碰到前面提到的几种差错类型(分组比特差错),还会有更多其它类型的差错。它们可能发生在中间路由器上,是在讨论IP时经常遇到的几种问题:分组重新排序,分组复制,分组泯灭(丢失)。设计用于多跳通信信道(例如IP)的纠错协议必须解决这些问题。现在让我们来探讨一下能处理这些问题的协议机制,在概括性地讨论这些之后,我们将探讨互联网上的TCP是如何使用它们的。

一个直接处理分组丢失(和比特差错)的方法是重发分组直到它被正确接收,这需要一种方法来判断:(1)接收方是否已收到分组;(2)接收方收到的分组是否与发送方发送的一样。接收方通知发送方自己已经收到一个分组,这种方法称为确认(acknowledgment)或ACK。最基本的形式是发送方发送一个分组,然后等待ACK,当接收方收到这个分组时,它发送对应的ACK,当发送方收到这个ACK后,它再发送另一个分组,然后继续这个过程。这里会有一些有意思的问题:(1)发送方等待ACK应该等待多长时间?(2)如果ACK丢失了怎么办?(3)如果收到了分组,但是里面有差错怎么办?

正如我们将看到的,第一个问题比较深奥,决定等待多长时间与发送方应等待ACK多长时间有关,确定这个等待时间比较困难,我们先推迟这个技术讨论,后面(见第14章)讨论TCP时再详细讨论。第二个问题的答案比较简单:如果一个ACK丢失了,发送方很难把这种情况与原分组丢失的情况区分开来,所以它只是简单地再次发送原分组。当然,在这种情况下,接收方可能会收到两个或两个以上的分组副本,接收方必须能够处理这种情况(见下一段)。至于第三个问题,我们可以借助12.1节中提到的编码技术来解决。通常使用少量的编码来检测一个大的分组中的差错(有很大的概率)比纠正差错简单得多。简单的编码通常不能纠错,但是能检测它们,这就是校验和与各种CRC如此流行的原因。为了检测分组里的差错,我们使用校验和的形式,当接收方收到一个含有差错的分组时,它不发送ACK,最终,发送方重发分组,理想情况下分组完好无损的到达。

到目前为止,即使这种简单的场景,接收方都可能收到传输的分组的副本,这个问题使用序号(sequence number)来解决。每个唯一的分组在源端被发送时,都会得到一个新的序号,这个序号由分组自身携带,接收方使用这个序号来判断它是否已经收到过这个分组,如果收到过则丢弃它。

到目前为止介绍的协议是可靠的但效率不高。考虑一下,当发送方发送分组到接收方的时间(延迟)很大(如一秒或两秒,对卫星链路来说很正常)并且有多个分组要发送时会发生什么。发送方注入一个分组到通信路径,然后停下来等待直到它收到ACK,因此这个协议被称为“停止等待协议”。假设分组在传输中没有丢失或损坏,该协议的吞吐量(throughput,单位时间内发送到网络中的数据量)与M/R成正比,M是分组大小,R是往返时间(RTT)。如果有分组丢失或损坏,情况甚至更糟糕:有效吞吐量(goodput,单位时间内传输的有用数据量)可能远远小于吞吐量。

对于损坏或丢失分组不太多的网络,低吞吐量的原因通常是网络没有处于繁忙状态。这种情况与使用装配线时新的工作在完整产品出现前不准进入类似,流水线大部分时间是空闲的。如果我们进一步比较一下,很明显,如果我们允许同一时间有多个工作单元进入流水线,就可以做得更好。网络通信也是如此——如果网络中有多个分组,就可以使网络“更繁忙”,从而提高吞吐量。

允许多个分组同时在网络中存在会使问题变得相当复杂。现在发送方不仅要决定何时向网络中注入分组,还要决定注入多少个分组,它还必须知道在等待ACK时如何维持计时器,同时还要保存每个未收到确认的分组的副本以防需要重传。接收方需要一个更复杂的ACK机制:可以区分出哪些分组已经收到哪些还未收到,接收方可能还需要一个更复杂的缓存(保存分组)机制——允许维护“失序”的分组(由于丢包或重排序而比预期提前到达的分组),除非它只是想简单地丢弃这些分组,而这样做是很低效的。还有一些问题可能不那么明显,如果接收方的接收速率比发送方的发送速率慢怎么办?如果发送方只是以非常高的速率注入很多分组,接收方可能会因处理或内存限制而丢弃它们,中间的路由器也会有同样的问题。如果网络基础设施处理不了发送方和接收方希望使用的数据速率怎么办?

12.1.2 分组窗口和滑动窗口

为了处理所有这些问题,如前所述,我们假设每个唯一分组都有一个序号。我们将分组窗口定义为:已被发送方注入网络但还未完全确认(即发送方还从没收到它们的ACK)的分组(或者它们的序号)集合。窗口中的分组数量称为窗口大小(window size)。窗口一词源于这样一种想法:如果你将通信过程中发送的所有分组排成长长的一行,但只能通过一个小孔来观察它们,你就只能看到它们的一部分——像通过一个窗口窥视一样。发送方窗口(以及行中其他分组)如图12-1所示。

第12章pid控制算法介绍,第12章讲的什么

图 12‑1发送方窗口,显示了哪些分组将要被发送(或已经发送),哪些还不能发送,以及哪些已经发送并确认。在这个例子里,窗口大小固定为三个分组

该图显示了当前窗口(窗口大小为3)的三个分组。3号分组已经发送并已确认,所以发送方保存的该分组的副本可以被释放。7号分组发送方已经准备好,但还不能发送,因为它还没有“进入”窗口。我们现在想象一下数据开始从发送方流向接收方,ACK也开始反向流动,发送方接下来可能会收到4号分组的ACK。当这种情况发生时,窗口向右边“滑动”一个分组,这意味着分组4的副本可以释放了,分组7可以被发送了。窗口的滑动给这种类型的协议增加了另外一个名字,滑动窗口(sliding window)协议。

滑动窗口方法可以用来解决到目前为止描述的许多问题,这个窗口结构在发送方和接收方通常都会有。在发送方,它记录着哪些分组可被释放,哪些分组正在等待ACK以及哪些分组还不能发送。在接收方,它记录着哪些分组已经被接收和确认,以及期望收到哪些分组(已经分配了多少内存来保存它们),以及哪些分组即使被接收也会因内存限制而被丢弃。尽管窗口结构便于记录发送方和接收方之间流动的数据,但是窗口应多大,接收方或网络不能处理发送方的数据率时会发生什么,它都没有提供指导建议。现在我们来看看这些是怎样关联在一起的。

12.1.3 可变窗口:流量控制和拥塞控制

为了解决接收方相对发送方太慢时产生的问题,我们介绍一种接收方跟不上发送方时强迫发送方慢下来的方法,这种方法被称为流量控制(flow control),通常以下述两种方式之一来处理。一种方式称为基于速率(rate-based)的流量控制,它给发送方指定某个特定速率,并确保数据永远不能超过这个速率发送。这种类型的流量控制最适合流应用程序,可被用广播和组播(见第9章)。

流量控制的另一种主要形式称为基于窗口(window-based)的流量控制,是使用滑动窗口时最流行的方法。在这种方法中,窗口大小不是固定的,而是允许随时间变化。要使用这种技术进行流量控制,必须有一种方法让接收方通知发送方要使用多大的窗口,这通常被称为窗口通告(window advertisement)或简单地称为窗口更新(window update)。发送方(即窗口通告的接收者)使用这个值来调整其窗口大小。逻辑上讲,窗口更新与我们前面讨论过的ACK是分开的,但在实践中窗口更新和ACK是由同一个分组携带的,这意味着发送方往往在向右滑动窗口的时候同时调整窗口的大小。

如果我们站在发送方的角度考虑改变窗口大小带来的影响,就会明白如何实现流量控制的。发送方在收到任何一个分组的ACK前只允许向网络中注入W个分组。如果发送方和接收方足够快,网络没有丢包并且有无限的容量,这意味着传输速率正比于(SW/R)b/s,其中W是窗口大小,S是分组大小(按比特算),R是往返时间(RTT)。接收方的窗口通告将发送方的窗口值钳在W,发送方的整体速率就可以被限制而不至于压垮接收方,这种方法可以很好地保护接收方,但是对于两者之间的网络呢?在发送方和接收方之间可能有有限内存的路由器,它们必须应对低速的网络链路,当这种情况出现时,发送方的速率可能超过路由器的能力,从而导致丢包。这个问题可以通过一种称为拥塞控制(congestion control)的特殊流量控制来解决。

拥塞控制涉及发送方放慢速度以避免压垮自身与接收方之间的网络。回想一下,在流量控制的讨论中,我们使用窗口通告来通知发送方为接收方放慢速度,这称为显式(explicit)信号,因为有一个协议字段专门用于通知发送方正在发生什么。发送方的另一个选择是猜测自己需要慢下来,这种方法涉及隐式(implicit)信号——也就是说,它根据其它一些迹象来决定是否减慢速度。

数据报式网络中的拥塞控制问题,以及与之密切相关的更普遍的排队理论问题,多年以来一直是一个主要的研究课题,而且它不太可能完全解决所有情况。在这里讨论执行流量控制的所有选项和方法也是不实际的,感兴趣的读者可参考[J90]、[K97]和[K75]。在第16章中,我们将更详细地探讨TCP使用的特殊拥塞控制技术,以及近年来出现的一些变体。

12.1.4 设置重传超时

基于重传的可靠协议的设计者要面临的一个最重要的性能问题是,要等待多长时间才能断定一个分组已经丢失并需要重传,换句话说,重传超时应该是多少?直观上看,发送方重发一个分组前应等待的时间大约是如下时间的总和:发送分组所用的时间、接收方处理分组并发送ACK所用的时间、ACK返回到发送方所用的时间,以及发送方处理ACK所用的时间。不幸的是,在实践中这些时间都是不确定的。更糟糕的是,随着向终端主机或路由器添加或减少负载,它们中的部分或全部都随时间而变化。

对用户来说,告诉协议实现在所有情况下所有时刻应取什么超时时间(或使它们保持最新)是不现实的,一个更好的策略是让协议实现尝试估计它们,这被称为往返时间估计(round-trip-time estimation),是一个统计过程。总的来说,真实的RTT很可能接近RTT样本集合的样本均值。注意,这个平均值会随着时间自然变化(不是静态的),因为通过的网络路径可能会改变。

即使对RTT进行了估计,设置触发重传的实际超时时间值的问题依然存在。回想一下均值的定义,均值永远不可能是一组样本的极值(除非样本全部一样)。因此,把重传计时器的值设置为均值是不明智的,因为很多实际的RTT可能会比均值大,从而导致不必要的重传。显然,超时时间应该设置为大于均值的值,但是这个值与均值的确切关系是什么(或者直接就使用均值)还不清楚。超时时间设置太大也是不可取的,因为这会导致网络处于空闲状态,从而降低吞吐量,第14章我们再对这个话题进一步探讨,在那里我们将探讨TCP是如何处理这个问题的。

12.2 TCP介绍

现在我们对影响可靠传输的问题有了大体的了解,让我们看看它们是如何在TCP中发挥作用的,以及TCP为互联网应用程序提供了何种类型的服务。我们会看一下TCP头部中的字段,注意到到目前为止我们看到过的许多概念(如ACK、窗口通告)都可以在首部找到。在接下来的章节中,我们将详细地研究所有这些首部字段。

对TCP的说明从本章开始,并在接下来的五章继续讨论。第13章描述了TCP连接是如何建立和终止的。第14章详细介绍了TCP如何估计每个连接的RTT,以及如何根据这个估计设置重传超时。第15章考察正常的数据传输,以“交互式”应用程序开始(例如聊天程序),然后介绍了窗口管理和流量控制,它们适用于交互式和“大块”数据流(比如文件传输)应用程序,以及TCP的紧急机制(urgent mechanism)——该机制允许发送方将数据流中的某些数据标记为特殊数据。第16章考察TCP中的拥塞控制算法,这些算法有助于减少网络繁忙时的丢包。这一章还讨论了拥塞控制算法一些修改版本,以增加快速网络的吞吐量或提高有损网络(如无线网络)的弹性。最后,第17章展示了TCP在没有数据流时如何保持连接活动(active)。

TCP最初的规范是[RFC0793],这个RFC的一些错误已经在主机请求RFC[RFC1122]中被纠正了。从那以后,TCP的规范就一直被修订和扩展以明确和改进拥塞控制行为[RFC5681][RFC3782][RFC3517][RFC3390][RFC3168]、重传超时[RFC6298][RFC5682][RFC4015]、操作NATs[RFC5382]、确认行为[RFC2883]、安全[RFC6056][RFC5927][RFC5926]、连接管理[RFC5482]以及紧急机制实现指南[RFC6093]。同时还有各种各样的实验性修改,包括重传行为[RFC5827][RFC3708]、拥塞检测和控制[RFC5690][RFC5562][RFC4782][RFC3649][RFC2861]以及其他特性。最后,在探究TCP如何利用多个同时存在的网络层路径方面也做了工作[RFC6182]。

12.2.1 TCP服务模型

虽然TCP和UDP使用相同的网络层(IPv4或IPv6),但是TCP为应用程序提供的服务与UDP完全不同。TCP提供了一种面向连接的(connection-oriented)、可靠的字节流服务。术语“面向连接”意味着使用TCP的两个应用程序必须通过相互联系来建立TCP连接,然后才能交换数据。典型的类比是拨打一个电话号码,等待对方接听电话并说“你好”,然后说“你是谁?”。TCP连接只有两个端点相互通信,广播和组播(见第9章)等概念不适用于TCP。

TCP为使用它的应用程序提供了一个字节流抽象,这种设计方案的结果是,TCP不会自动插入记录标志或消息边界(见第1章)。记录标志对应于应用程序的写入范围。如果一端的应用程序写入10字节,然后写入20字节,然后再写入50字节,连接另一端的应用程序无法知道每次写入的大小,另一端可能分4次每次20字节读取这80字节,或者以其它方式读取。一端将字节流放入TCP,相同的字节流就会出现在另一端,每个端点独立选择它的读写大小。

TCP不解释字节流中的内容,它不知道正在交换的数据是不是二进制数据、ASCII字符、EBCDIC字符或其它东西。字节流的解释取决于连接两端的应用程序。尽管不再推荐使用,但TCP确实支持前面提到的紧急机制。

12.2.2 TCP的可靠性

TCP使用刚才描述的技术的变体提供可靠性。因为它提供了字节流接口,所以TCP必须把发送方应用程序的字节流转换为IP可以携带的分组,这叫做分组化(packetization)。这些分组包含序号,该序号表示每个分组的第一个字节在整个数据流中的字节偏移量,而不是分组号。这允许分组在传输过程中大小可变,也允许它们组合,称为重新分组(repacketization)。应用程序数据被分割成TCP认为的最佳大小块来发送,通常选取的大小为不会被IP层分片的数据报大小。这与UDP不同,应用程序每次写入通常都会生成相同大小的UDP数据报(加上头部)。TCP传递给IP的块称为段(segment,见图12-2)。在第15章我们将看到TCP如何决定一个段的大小。

TCP首部维护了一个强制性的校验和,校验应用程序数据和TCP首部。这是一个端到端的伪首部校验和,用于检测传输过程中产生的比特差错,如果收到的段校验和无效,TCP会丢弃它,且不会为被丢弃的分组发送任何确认。TCP接收方可能会对一个以前的(已经确认的)段进行确认,以帮助发送方进行拥塞控制(见第16章)。 TCP校验和使用与其它互联网协议(UDP、 ICMP等)一样的数学函数。对于大数据的传送,有些人担心这个校验和不够强壮[SP00],因此谨慎的应用程序应该使用自已的错误保护方法(例如更强的校验和或各种CRC),或者使用一个中间层来达到同样的效果(例如,参见[RFC5044])。

当TCP发送一组报文段时,它通常只设置一个重传计时器,等待对方确认接收。 TCP不会为每个报文段设置一个重传计时器,相反,发送一个窗口的数据,它只设置一个计时器,当ACK到达时再更新超时时间。如果确认没有及时接收到,报文段就会被重传。在第14章我们将更详细地研究TCP的自适应超时和重传策略。

当TCP接收到连接另一端的数据时,它会发送一个确认,这个确认可能不会立即发送,通常延迟几分之一秒。TCP使用的ACK是累积的,从某种意义来讲,确认号为N的ACK意味着所有直到N的字节(但不包含N)已经成功被接收了。这提供了一定的健壮性——如果一个ACK丢失了,后续的ACK很可能就足以对前面的报文进行确认。

TCP为应用层提供了全双工服务,这意味着数据可以在每个方向上流动,而不受另一个方向的影响,因此,连接的每个端点必须维护每个方向上数据流的序号。一旦建立连接,一个方向上流动的数据的TCP段也包含了相反方向上的报文段的ACK。每个报文段还包含一个窗口通告以实现相反方向上的流量控制。为此,在一个连接中,当一个TCP报文段到达时,窗口可能向前滑动,窗口大小可能改变,新数据可能已经到达。正如我们将在第13章中看到的,一个完全活跃的TCP连接是双向和对称的,数据可以在任何一个方向上同样地流动。

通过使用序号,TCP接收方丢弃重复的报文段,并重新排序无序到达的报文段。回想一下,这些异常都有可能发生,因为TCP使用IP来传输它的报文段, IP不提供重复消除或保证正确的次序。然而,由于TCP是一个字节流协议, TCP从不向接收应用程序交付失序数据,因此,TCP接收端可能不得不先保存较大序号的数据,直到较小序号的缺失的报文段(一个“洞”)被填充才能交付给应用程序。

现在,我们开始研究TCP的一些细节。在本章中,我们只介绍TCP的封装和首部结构,其他细节将在接下来的五章中介绍。TCP可以与IPv4或IPv6一起使用,它使用的伪头部(类似于UDP的)校验对于IPv4或IPv6都是强制的。

12.3 TCP 首部和封装

TCP封装在IP数据报中,如图12-2所示。

第12章pid控制算法介绍,第12章讲的什么

图 12‑2 TCP首部紧跟着IP首部或IPv6扩展首部,通常是20字节(没有TCP选项)。带选项的话,TCP首部可达60字节。常见选项包括最大段大小、时间戳、窗口缩放和选择性ACK(SACK)

首部要比我们在第10章看到的UDP首部复杂的多,这并不奇怪,因为TCP明显是一个更复杂的协议,它必须让连接的每一端知道(同步)当前状态。如图12-3所示。

第12章pid控制算法介绍,第12章讲的什么

图 12‑3 TCP首部。它的正常大小是20字节,除非有选项。首部长度(Header Length)字段给出以32位字为单位首部大小(最小值是5)。阴影字段(确认号(Acknowledgment Number)、窗口大小(Window Size)以及ECN和ACK位)涉及该报文段发送方相反方向上的数据流

每个TCP首部包含源端口号和目的端口号,这两个值与IP首部中的源IP和目的IP地址唯一地标识了每个连接。在TCP文献中,IP地址和端口的组合有时被称为端点(endpoint)或套接字(socket)。后一个术语出现在[RFC0793]中,其名称最终被Berkeley衍生的网络通信编程接口所采用(现在经常被称为“伯克利套接字”)。每个TCP连接由一对套接字或端点(由客户端IP地址、客户端端口号、服务器IP地址和服务器端口号组成的4元组)唯一地标识,当我们研究服务器如何与多个客户端通信时,这一事实将会变得非常重要(参见第13章)。

序号(Sequence Number)字段用于标识TCP发送方到TCP接收方数据流中的字节,此值为报文段中数据的第一个字节的编号。如果我们考虑两个应用程序间一个方向上数据流,TCP使用序号给每个字节编号,这个序号是一个32位的无符号数,达到2^32-1后再回到0。因为交换的字节都有编号,所以确认号字段(也称ACK号或简称ACK字段)包含了确认号发送方期望接收的下一个序号,即最后一个被成功接收的数据字节序号加1。这个字段只有在ACK位打开时才有效,除了初始段(客户端与服务器建立连接时发送的第1个报文段)和结束段[1],其它报文段的ACK位通常都是打开(置1)的。发送ACK和发送任何其它TCP报文段的开销是一样的,因为与ACK位字段一样,32位的ACK号字段始终是首部的一部分。

当建立一个新连接时,从客户端发送到服务器的第一个报文段的SYN位被打开(置1),

这样的报文段称为SYN报文段或简称SYN。序号字段包含了本次连接那个方向上后续要使用序号的第一个序号,也是反方向ACK确认的第一个序号(回想一下,连接都是双向的)。注意这个数字不是0或1,而是其它数字,通常是随机选择的,称为初始序列号(Initial Sequence Number,ISN)。ISN不是0或1是为了安全,这将在第13章讨论。本次连接那个方向上的数据的第一个字节的序号是ISN+1,因为SYN报文段会消耗一个序号。我们将在后面看到,消耗一个序号也意味着会使用重传进行可靠传输,因此,SYN和应用程序字节(以及我们将在后面看到的FIN)是被可靠传输的。ACK不消耗序列号,因此不是会被可靠传输。

TCP可以被描述为“具有累积的正向确认的滑动窗口协议”。ACK号字段值表示接收方按顺序收到的最大字节(加1),例如,如果1~1024字节已经接收成功,第二个报文段包含2049~3072字节,但接收方无法使用常规的ACK号字段通知发送方它接收到了这个新报文段。然而,现代TCP有一个选择确认(SACK)选项,允许接收方告诉发送方它已经正确地接收到了失序的数据。与一个具有选择重发(selective repeat)能力的TCP发送方结对时,可以显著的改善性能[FF96]。在第14章我们将看到TCP是如何使用重复确认(duplicate acknowledgments)来实现拥塞控制和差错控制过程的。

首部长度字段给出了首部的长度,以32位字为单位。由于选项字段的长度可变,所以它是必需的。这是一个4位的字段,所以TCP最多60字节的首部。如果没有选项,则大小是20字节。

目前,TCP首部共定义了8个位字段,然而一些旧的实现只能识别后面的6个。它们中的一个或多个可以同时启用。我们先简要介绍它们的用法,在后面的章节里再详细讨论它们。

1. CWR——减少拥塞窗口(发送方降低了发送速率);见第16章。

2. ECN——ECN通知(发送方接收到了较早的拥塞通告);见第16章。

3. URG——紧急(紧急指针字段有效——很少使用);见第15章。

4. ACK——确认(确认号字段有效——连接建立后总是启用);见第13和15章。

5. PSH——推送(接收方应尽快将这个数据交付给应用程序——未被可靠地实现或用到);

见第15章。

6. RST——重置连接(连接中止,通常是因为错误);见第13章。

7. SYN——初始化连接时同步序号;见第13章。

8. FIN——该报文段的发送方结束向对方发送数据;见第13章。

TCP的流量控制是由连接两端通过窗口大小字段通告窗口大小来实现。这个窗口大小指的是从ACK号开始算起,接收方愿意接收的字节数。这是一个16位的字段,所以窗口大小最大为65535字节,因此限制了TCP的吞吐量。在第15章中,我们将看到窗口缩放(Window Scale)选项允许对这个窗口值进行缩放,为高速和高延迟网络提供更大的窗口和更好的性能。

TCP校验和字段使用类似于我们在第8章和第10章讨论的ICMPv6与UDP使用的伪首部进行计算,它校验TCP首部、数据和IP首部中的部分字段。这个字段必须由发送方计算和填充,然后由接收方验证。 TCP校验和的计算算法与IP、 ICMP和UDP校验和相同。

紧急指针(Urgent Pointer)字段只有设置了URG位才有效。这个“指针”是一个正偏移量,与报文段中的序号字段相加后指向紧急数据最后一字节的下一个字节[2]。 TCP的紧急机制是发送方向另一端发送特殊标记数据的一种方式。

最常见的TCP选项是“最大段大小”选项,称为MSS。连接的每个端点通常在它发送的第一个报文段(设置了SYN位以建立连接的报文段)中指定这个选项。MSS指的是选项发送者愿意接收的最大报文段。我们在第13章将更详细地介绍MSS选项,并在第14章和第15章介绍其它TCP选项。其他常见的选项还有SACk、时间戳和窗口缩放。

在图12-2中,我们注意到TCP报文段的数据部分是可选的。我们将在第13章看到,当连接建立和终止时,交换的报文段只包含TCP首部(带或不带选项)而不包含数据。如果某个方向上没有数据传输,那么不带任何数据的首部将会被用于确认接收到的数据(称为纯ACK),并通知对方改变窗口大小(称为窗口更新)。还有一些由超时引起的发送不带数据的报文段[3]。

12.4 小结

在有损通信信道上提供可靠通信的问题已经研究了许多年。处理差错的两种主要方法是差错纠正和数据重传。使用重传的协议必须处理数据丢失,通常是通过设置计时器,同时还必须安排某种方式让接收方告知发送方它收到了什么。决定等待ACK多长时间是很棘手的,因为这个时间会随网络路由或终端负载的变化而变化。现代协议估计往返时间,并根据这些测量值设置重传计时器。

除了设置重传计时器,当网络中一次只有一个分组时,重传协议是很简单,但网络性能会很差,延迟会很高。为了提高效率,在收到ACK前必须向网络注入多个分组,这种方法更有效但也更复杂。解决这种复杂性的一种典型方法是使用滑动窗口,使用序号标记分组,并使用窗口大小限制分组数量。当窗口大小根据接收方反馈或其它信号(如丢弃的分组)而变化时,就可以实现流量控制和拥塞控制。

TCP提供了一种可靠、面向连接和字节流的传输层服务。我们简单地看了下TCP首部里的所有字段,注意到它们中的大多数都与可靠交付中的抽象概念直接相关。我们将在接下来的章节中详细研究它们。TCP把应用程序数据打包成报文段,在发送数据时设置超时时间,对收到的数据进行确认,重组失序的数据,丢弃重复的数据,提供端到端流量控制,计算和校验强制的端到端校验和。TCP是互联网上使用最广泛的协议,大多数流行的应用程序(如HTTP、 SSH/TLS、NetBIOS(NBT——NetBIOS over TCP)、Telnet、 FTP以及电子邮件(SMTP))都使用TCP,许多分布式文件共享程序(如BitTorrent、Shareaza)也使用它。

[1] 译者注:除了初始段,未发现其它段ACK=0,所以这里说结束段ACK=0可能是错误的

[2] 紧急数据只有一个字节且占用序号,可参见网上文章https://allen.blog.csdn.net/article/details/70243128

[3] 如超时重传FIN段或探测响应报文