您的位置:首页 > 理论基础 > 计算机网络

第18章 TCP连接的建立与终止

2018-11-11 22:09 4343 查看

18.1 引言

TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。本章将详细讨论一个TCP连接是如何建立的以及通信结束后是如何终止的。

这种两端间连接的建立与无连接协议如UDP不同。我们在第11章看到一端使用UDP向另一端发送数据报时,无需任何预先的握手。

18.2 连接的建立与终止

为了了解一个TCP连接在建立及终止时发生了什么,我们在系统svr4上键入下列命令:

telnet命令在与丢弃(discard)服务(参见1.12节)对应的端口上与主机bsdi建立一条TCP连接。这服务类型正是我们需要观察的一条连接建立与终止的服务类型,而不需要服务器发起任何数据交换。

18.2.1tcpdump的输出

图18-1显示了这条命令产生TCP报文段的tcpdump输出。

图18-1 TCP连接建立与终止的tcpdump输出显示

这7个TCP报文段仅包含TCP首部。没有任何数据。

对于TCP段,每个输出行开始按如下格式显示:

第18章 TCP连接的建立与终止175

图18-2 tcpdump对TCP首部中部分标志比特的字符表示

在这个例子中,我们看到了S、F和句点“.”标志符。我们将在以后看到其他的两个标志(R和P)。TCP首部中的其他两个标志比特—ACK和URG—tcpdump将作特殊显示。

图18-2所示的4个标志比特中的多个可能同时出现在一个报文段中,但通常一次只见到一个。

RFC 1025[Postel 1987],“TCP and IP Bake Off”,将一种报文段称为Kamikaze分组Θ,在这样的报文段中有最大数量的标志比特同时被置为1(SYN,URG,PSH,FIN和1字节的数据)。这样的报文段也叫作nastygram,圣诞树分组,灯测试报文段(lamp test segment)。

在第1行中,字段1415531521:1415531521(0)表示分组的序号是1415531521,而报文段中数据字节数为0。tcpdump显示这个字段的格式是开始的序号、一个冒号、隐含的结尾序号及圆括号内的数据字节数。显示序号和隐含结尾序号的优点是便于了解数据字节数大于0时的隐含结尾序号。这个字段只有在满足条件(1)报文段中至少包含一个数据字节;或者(2)SYN、FIN或RST被设置为1时才显示。图18-1中的第1、2、4和6行是因为标志比特被置为1而显示这个字段的,在这个例子中通信双方没有交换任何数据。

在第2行中,字段ack1415531522表示确认序号。它只有在首部中的ACK标志比特被设置1时才显示。

每行显示的字段win4096表示发端通告的窗口大小。在这些例子中,我们没有交换任何数据,窗口大小就维持默认情况下的4096(我们将在20.4节中讨论TCP窗口大小)。

图18-1中的最后一个字段<mss1024>表示由发端指明的最大报文段长度选项。发端将不接收超过这个长度的TCP报文段。这通常是为了避免分段(见11.5节)。我们将在18.4节讨论最大报文段长度,而在18.10节介绍不同TCP选项的格式。

18.2.2 时间系列

图18-3显示了这些分组序列的时间系列(在图6-11中已经首次介绍了这些时间系列的一些基本特性)。这个图显示出哪一端正在发送分组。我们也将对tcpdump输出作一些扩展(例如,印出SYN而不是S)。在这个时间系列中也省略窗口大小的值,因为它和我们的讨论无关。

18.2.3 建立连接协议

现在让我们回到图18-3所示的TCP协议中来。为了建立一条TCP连接:

  1. Θ Kamikaze是神风队队员或神风队所使用的飞机。在第二次世界大战末期,日本空军的神风队队员驾驶满载炸弹的飞机去撞击轰炸目标,企图与之同归于尽。

176TCP/IP详解,卷1:协议

图18-3 连接建立与终止的时间系列

发送第一个SYN的一端将执行主动打开(active open)。接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)(在18.8节我们将介绍双方如何都执行主动打开)。

当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。RFC 793[Postel 1981c]指出ISN可看作是一个32比特的计数器,每4ms加1。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释。

如何进行序号选择?在4.4BSD(和多数的伯克利的实现版)中,系统初始化时初始的发送序号被初始化为1。这种方法违背了Host Requirements RFC(在这个代码中的一个注释确认这是一个错误)。这个变量每0.5秒增加64000,并每隔9.5小时又回到0(对应这个计数器每8 ms加1,而不是每4 ms加1)。另外,每次建立一个连接后,这个变量将增加64000。

报文段3与报文段4之间4.1秒的时间间隔是建立TCP连接到向telnet键入quit命令来中止该连接的时间。

第18章 TCP连接的建立与终止177

图18-4 连接终止期间报文段的正常交换

连接通常是由客户端发起的,这样第一个SYN从客户传到服务器。每一端都能主动关闭这个连接(即首先发送FIN)。然而,一般由客户端决定何时终止连接,因为客户进程通常由用户交互控制,用户会键入诸如“quit”一样的命令来终止进程。在图18-4中,我们能改变上边的标识,将左方定为服务器,右方定为客户,一切仍将像显示的一样工作(例如在14.4节中的第一个例子中就是由daytime服务器关闭连接的)。

18.2.5 正常的tcpdump输出

对所有的数值很大的序号进行排序是很麻烦的,因此默认情况下tcpdump只在显示SYN报文段时显示完整的序号,而对其后的序号则显示它们与初始序号的相对偏移值(为了得到图18-1的输出显示必须加上-S选项)。对应于图18-1的正常tcpdump显示如图18-5所示:

除非我们需要显示完整的序号,否则将在以下的例子中使用这种形式的输出显示。

178TCP/IP详解,卷1:协议

图18-5 连接建立与终止的正常tcpdump输出

18.3 连接建立的超时

有很多情况导致无法建立连接。一种情况是服务器主机没有处于正常状态。为了模拟这种情况,我们断开服务器主机的电缆线,然后向它发出telnet命令。图18-6显示了tcpdump的输出。

图18-6 建立连接超时的tcpdump输出

在这个输出中有趣的一点是客户间隔多长时间发送一个SYN,试图建立连接。第2个SYN与第1个的间隔是5.8秒,而第3个与第2个的间隔是24秒。

作为一个附注,这个例子运行38分钟后客户重新启动。这对应初始序号为291 008 001(约为38×60×64000×2)。我们曾经介绍过使用典型的伯克利实现版的系统将初始序号初始化为1,然后每隔0.5秒就增加64 000。

另外,因为这是系统启动后的第一个TCP连接,因此客户的端口号是1024。

图18-6中没有显示客户端在放弃建立连接尝试前进行SYN重传的时间。为了了解它我们必须对telnet命令进行计时:

时间差值是76秒。大多数伯克利系统将建立一个新连接的最长时间限制为75秒。我们将在21.4节看到由客户发出的第3个分组大约在16:25:29超时,客户在它第3个分组发出后48秒而不是75秒后放弃连接。

18.3.1 第一次超时时间

在图18-6中一个令人困惑的问题是第一次超时时间为5.8秒,接近6秒,但不准确,相比之下第二个超时时间几乎准确地为24秒。运行十多次测试,发现第一次超时时间在5.59秒~5.93秒之间变化。然而,第二次超时时间则总是24.00秒(精确到小数点后面两位)。

第18章 TCP连接的建立与终止179

图18-7 TCP的500 ms定时器

当滴答计数器为0时,6秒的定时器便会超时(见图18-7),这个定时器会在以后的24秒(48个滴答)重新复位。之后的下一个定时器将更接近24秒,因为当TCP的500 ms定时器被内核调用时,它就会被修改一次。

18.3.2 服务类型字段

在图18-6中,出现了符号 [tos 0x10]。这是IP数据报内的服务类型(TOS)字段(参见图3-2)。BSD/386中的Telnet客户进程将这个字段设置为最小时延。

18.4 最大报文段长度

最大报文段长度(MSS)表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS。我们已经见过MSS都是1024。这导致IP数据报通常是40字节长:20字节的TCP首部和20字节的IP首部。

在有些书中,将它看作可“协商”选项。它并不是任何条件下都可协商。当建立一个连接时,每一方都有用于通告它期望接收的MSS选项(MSS选项只能出现在SYN报文段中)。如果一方不接收来自另一方的MSS值,则MSS就定为默认值536字节(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节IP数据报)。

一般说来,如果没有分段发生,MSS还是越大越好(这也并不总是正确,参见图24-3和图24-4中的例子)。报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部有更高的网络利用率。当TCP发送一个SYN时,或者是因为一个本地应用进程想发起一个连接,或者是因为另一端的主机收到了一个连接请求,它能将MSS值设置为外出接口上的MTU长度减去固定的IP首部和TCP首部长度。对于一个以太网,MSS值可达1460字节。使用IEEE 802.3的封装(参见2.2节),它的MSS可达1452字节。

在本章见到的涉及BSD/386和SVR4的MSS为1024,这是因为许多BSD的实现版本需要MSS为512的倍数。其他的系统,如SunOS 4.1.3、Solaris 2.2和AIX 3.2.2,当双方都在一个本地以太网上时都规定MSS为1460。[Mogul 1993] 的比较显示了在以太网上1460的MSS在性能上比1024的MSS更好。

180TCP/IP详解,卷1:协议

图18-8 显示sunslip间TCP连接的MSS值

sunslip发起一个TCP连接,并使用tcpdump来观察报文段。图18-9显示这个连接的建立(省略了通告窗口大小)。

图18-9 tcpdump显示了从sunslip建立连接的过程

在这个例子中,sun发送的报文段不能超过256字节的数据,因为它收到的MSS选项值为256(第2行)。此外,由于slip知道它外出接口的MTU长度为296,即使sun已经通告它的MSS为1460,但为避免将数据分段,它不会发送超过256字节数据的报文段。系统允许发送的数据长度小于另一端的MSS值。

只有当一端的主机以小于576字节的MTU直接连接到一个网络中,避免这种分段才会有效。如果两端的主机都连接到以太网上,都采用536的MSS,但中间网络采用296的MTU,也将会出现分段。使用路径上的MTU发现机制(参见24.2节)是关于这个问题的唯一方法。

18.5 TCP的半关闭

TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。正如我们早些时候提到的只有很少的应用程序使用它。

第18章 TCP连接的建立与终止181

图18-10 TCP半关闭的例子

为什么要有半关闭?一个例子是Unix中的rsh(1)命令,它将完成在另一个系统上执行一个命令。

命令

sun % rsh bsdi sort < datafile

将在主机bsdi上执行sort排序命令,rsh命令的标准输入来自文件datafilersh将在它与在另一主机上执行的程序间建立一个TCP连接。rsh的操作很简单:它将标准输入(datafile)复制给TCP连接,并将结果从TCP连接中复制给标准输出(我们的终端)。图18-11显示了这个建立过程(牢记TCP连接是全双工的)。

图18-11 命令:rsh bsdi sort < datafile

在远端主机bsdi上,rshd服务器将执行sort程序,它的标准输入和标准输出都是TCP连接。第14章的[Stevens 1990]详细介绍了有关Unix进程的结构,但这儿涉及的是使用TCP连接以及需要使用TCP的半关闭。

sort程序只有读取到所有输入数据后才能产生输出。所有的原始数据通过TCP连接从rsh客户端传送到sort服务器进行排序。当输入(datafile)到达文件尾时,rsh客户端执行这个TCP连接的半关闭。接着sort服务器在它的标准输入(这个TCP连接)上收到一个文件结束符,对数据进行排序,并将结果写在它的标准输出上(TCP连接)。rsh客户端继续接收来自TCP连接另一端的数据,并将排序的文件复制到它的标准输出上。

182TCP/IP详解,卷1:协议

图18-12 TCP的状态变迁图

在这个图中要注意的第一点是一个状态变迁的子集是“典型的”。我们用粗的实线箭头表示正常的客户端状态变迁,用粗的虚线箭头表示正常的服务器状态变迁。

第18章 TCP连接的建立与终止183

图18-13 TCP正常连接建立和终止所对应的状态

假定在图18-13中左边的客户执行主动打开,而右边的服务器执行被动打开。尽管图中显示出由客户端执行主动关闭,但和早前我们提到的一样,另一端也能执行主动关闭。

可以使用图18-12的状态图来跟踪图18-13的状态变化过程,以便明白每个状态的变化。

18.6.1 2MSL等待状态

TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。

RFC 793 [Postel 1981c]指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。

从第8章我们知道在实际应用中,对IP数据报TTL的限制是基于跳数,而不是定时器。

对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。

184TCP/IP详解,卷1:协议

当重新启动服务器程序时,程序报告一个差错信息说明不能绑定它的熟知端口,因为该端口已被使用(即它处于2MSL等待)。

运行netstat程序来查看连接的状态,以证实它的确处于2MSL等待状态。

如果我们一直试图重新启动服务器程序,并测量它直到成功所需的时间,我们就能确定出2MSL值。对于SunOS 4.1.3、SVR4、BSD/386和AIX 3.2.2,它需要1分钟才能重新启动服务器程序,这意味着它们的MSL值为30秒。而对于Solaris 2.2,它需要4分钟才能重新启动服务器程序,这表示它的MSL值为2分钟。

第18章 TCP连接的建立与终止185

我们在第1次执行客户程序时采用-v选项来查看它使用的本地端口为(11 62)。第2次执行客户程序时则采用-b选项来选择端口11 62为它的本地端口。正如我们所预料的那样,客户程序无法那么做,因为那个端口是一个还处于2MSL等待连接的一部分。

需要再次强调2MSL等待的一个效果,因为我们将在第27章的文件传输协议FTP中遇到它。和以前介绍的一样,一个插口对(即包含本地IP地址、本地端口、远端IP地址和远端端口的4元组)在它处于2MSL等待时,将不能再被使用。尽管许多具体的实现中允许一个进程重新使用仍处于2MSL等待的端口(通常是设置选项SO_REUSEADDR),但TCP不能允许一个新的连接建立在相同的插口对上。可通过下面的试验来看到这一点:

第1次运行sock程序中,我们将它作为服务器程序,端口号为6666,并从主机bsdi上的一个客户程序与它连接,这个客户程序使用的端口为1098。我们终止服务器程序,因此它将执行主动关闭。这将导致4元组140.252.13.33(本地IP地址)、6666(本地端口号)、140.252.13.35(另一端IP地址)和1098(另一端的端口号)在服务器主机进入2MSL等待。

在第2次运行sock程序时,我们将它作为客户程序,并试图将它的本地端口号指明为6666,同时与主机bsdi在端口1098上进行连接。但这个程序在试图将它的本地端口号赋值为6666时产生了一个差错,因为这个端口是处于2MSL等待4元组的一部分。

为了避免这个差错,我们再次运行这个程序,并使用选项-A来设置前面提到的SO_REUSEADDR。这将让sock程序能将它的本地端口号设置为6666,但当我们试图进行主动打开时,又出现了一个差错。即使它能将它的本地端口设置为6666,但它仍不能和主机bsdi在端口1098上进行连接,因为定义这个连接的插口对仍处于2MSL等待状态。

如果我们试图从其他主机来建立这个连接会如何?首先我们必须在sun上以-A标记来重新启动服务器程序,因为它需要的端口(6666)是还处于2MSL等待连接的一部分。

sun % sock -A -s 6666    启动服务器程序,在端口6666监听

接着,在2MSL等待结束前,我们在bsdi上启动客户程序:

bsdi % sock -b1098 sun 6666

186TCP/IP详解,卷1:协议

图18-14 试图在不存在的端口上打开连接而产生的复位

在这个图中需要注意的值是复位报文段中的序号字段和确认序号字段。因为ACK比特在到达的报文段中没有被设置为1,复位报文段中的序号被置为0,确认序号被置为进入的ISN加上数据字节数。尽管在到达的报文段中没有真正的数据,但SYN比特从逻辑上占用了1字节的序号空间;因此,在这个例子中复位报文段中确认序号被置为ISN与数据长度(0)、SYN比特所占的1的总和。

18.7.2 异常终止一个连接

我们在18.2节中看到终止一个连接的正常方式是一方发送FIN。有时这也称为有序释放(orderly release),因为在所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。但也有可能发送一个复位报文段而不是FIN来中途释放一个连接。有时称这为异常释放(abortive release)。

异常终止一个连接对应用程序来说有两个优点:(1)丢弃任何待发数据并立即发送复位报文段;(2)RST的接收方会区分另一端执行的是异常关闭还是正常关闭。应用程序使用的API必须提供产生异常关闭而不是正常关闭的手段。

使用sock程序能够观察这种异常关闭的过程。Socket API通过“linger on close”选项(SO_LINGER)提供了这种异常关闭的能力。我们加上-L选项并将停留时间设为0。这将导致连接关闭时进行复位而不是正常的FIN。我们连接到处于服务器上的sock程序,并键入一输入行:

图18-15是这个例子的tcpdump输出显示(在这个图中我们已经删除了所有窗口大小的说明,因为它们与讨论无关)。

第1~3行显示出建立连接的正常过程。第4行发送我们键入的数据行(12个字符和Unix换行符),第5行是对收到数据的确认。

188TCP/IP详解,卷1:协议

图18-15 使用复位(RST)而不是FIN来异常终止一个连接

第6行对应为终止客户程序而键入的文件结束符(Control_D)。由于我们指明使用异常关闭而不是正常关闭(命令行中的-L0选项),因此主机bsdi端的TCP发送一个RST而不是通常的FIN。RST报文段中包含一个序号和确认序号。需要注意的是RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止该连接,并通知应用层连接复位。

我们在服务器上得到下面的差错信息:

这个服务器程序从网络中接收数据并将它接收的数据显示到其标准输出上。通常,从它的TCP上收到文件结束符后便将结束,但这里我们看到当收到RST时,它产生了一个差错。这个差错正是我们所期待的:连接被对方复位了。

18.7.3 检测半打开连接

如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的TCP连接称为半打开(Half-Open)的。任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。

半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后再关机。这可能发生在使用PC机作为Telnet的客户主机上,例如,用户在一天工作结束时关闭PC机的电源。当关闭PC机电源时,如果已不再有要向服务器发送的数据,服务器将永远不知道客户程序已经消失了。当用户在第二天到来时,打开PC机,并启动新的Telnet客户程序,在服务器主机上会启动一个新的服务器程序。这样会导致服务器主机中产生许多半打开的TCP连接(在第23章中我们将看到使用TCP的keepalive选项能使TCP的一端发现另一端已经消失)。

能很容易地建立半打开连接。在bsdi上运行Telnet客户程序,通过它和svr4上的丢弃服务器建立连接。我们键入一行字符,然后通过tcpdump进行观察,接着断开服务器主机与以太网的电缆,并重启服务器主机。这可以模拟服务器主机出现异常(在重启服务器之前断开以太网电缆是为了防止它向打开的连接发送FIN,某些TCP在关机时会这么做)。服务器主机重启后,我们重新接上电缆,并从客户向服务器发送另一行字符。由于服务器的TCP已经重新启动,它将丢失复位前连接的所有信息,因此它不知道数据报文段中提到的连接。TCP的处理原则是接收方以复位作为应答。图18-16是这个例子的tcpdump输出显示(已从这个输出中删除了窗口大小的说明、服务类型信息和MSS声明,因为它们与讨论无关)。

第18章 TCP连接的建立与终止189

图18-16是这个例子的tcpdump输出显示(已从这个输出中删除了窗口大小的说明、服务类型信息和MSS声明,因为它们与讨论无关)。

图18-16 复位作为半打开连接上数据段的应答

第1~3行是正常的连接建立过程。第4行向丢弃服务器发送字符行“hithere”,第5行是确认。

然后是断开svr4的以太网电缆,重新启动svr4,并重新接上电缆。这个过程几乎需要190秒。接着从客户端输入下一行(即“another line”),当我们键入回车键后,这一行被发往服务器(图18-16的第6行)。这导致服务器产生一个响应,但要注意的是由于服务器主机经过重新启动,它的ARP高速缓存为空,因此需要一个ARP请求和应答(第7、8行)。第9行表示RST被发送出去。客户收到复位报文段后显示连接已被另一端的主机终止(Te lnet客户程序发出的最后信息不再有什么价值)。

18.8 同时打开

两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开(simultaneous open)。

例如,主机A中的一个应用程序使用本地端口7777,并与主机B的端口8888执行主动打开。主机B中的应用程序则使用本地端口8888,并与主机A的端口7777执行主动打开。

这与下面的情况不同:主机A中的Telnet客户程序和主机B中Telnet的服务器程序建立连接,与此同时,主机B中的Telnet客户程序与主机A的Telnet服务器程序也建立连接。在这个Telnet例子中,两个Telnet服务器都执行被动打开,而不是主动打开,并且Telnet客户选择的本地端口不是另一端Te lnet服务器进程所熟悉的端口。

TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接(其他的协议族,最突出的是OSI运输层,在这种情况下将建立两条连接而不是一条连接)。

当出现同时打开的情况时,状态变迁与图18-13所示的不同。两端几乎在同时发送SYN,并进入SYN_SENT状态。当每一端收到SYN时,状态变为SYN_RCVD(如图18-12),同时它们都再发SYN并对收到的SYN进行确认。当双方都收到SYN及相应的ACK时,状态都变迁为ESTABLISHED。图18-17显示了这些状态变迁过程。

190TCP/IP详解,卷1:协议

图18-17 同时打开期间报文段的交换

一个同时打开的连接需要交换4个报文段,比正常的三次握手多一个。此外,要注意的是我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。

一个例子

尽管很难,但仍有可能产生一个同时打开的连接。两端必须几乎在同时启动,以便收到彼此的SYN。只要两端有较长的往返时间就能保证这一点。这样我们将一端设置在主机bsdi上,另一端则设置在主机vangogh.cs.berkeley.edu上。由于两端之间有一条拨号链路SLIP,它的往返时间对保证双方同步收到SYN是足够长的(几百毫秒)。

一端(bsdi)将本地端口设置为8888(使用命令行选项-b),并对另一端主机端口7777执行主动打开。

另一端也几乎在同一时间将本地端口设置为7777,并对端口8888执行主动打开。

我们指明带-v标志的sock程序来验证连接两端的IP地址和端口号。这个选项也显示每一端的MSS值。为证实两端确实在相互交谈,我们在每一端还输入一行字符,看它们是否会被送到另一端并显示出来。

图18-18显示了这个连接的段交换过程(我们删除了出现在来自vangogh第一个SYN中的一些新的TCP选项,因为vangogh使用4.4BSD系统。将在18.10节介绍这些较新的选项)。注意两个SYN(第1~2行)后跟着两个带ACK的SYN(第3~4行)。它们将执行同时打开。

第5行显示了由bsdi发送给vangogh的输入行“hello,world”,第6行对此进行确认。第7~8行对应另一方向的输入行“and hi there”和确认。第9~12行显示正常的连接关闭。

第18章 TCP连接的建立与终止191

图18-18 同时打开期间的报文段交换过程

18.9 同时关闭

我们在以前讨论过一方(通常但不总是客户方)发送第一个FIN执行主动关闭。双方都执行主动关闭也是可能的,TCP协议也允许这样的同时关闭(simultaneous close)。

在图18-12中,当应用层发出关闭命令时,两端均从ESTABLISHED变为FIN_WAIT_1。这将导致双方各发送一个FIN,两个FIN经过网络传送后分别到达另一端。收到FIN后,状态由FIN_WAIT_1变迁到CLOSING,并发送最后的ACK。当收到最后的ACK时,状态变化为TIME_WAIT。图18-19总结了这些状态的变化。

图18-19 同时关闭期间的报文段交换

同时关闭与正常关闭使用的段交换数目相同。

18.10 TCP选项

TCP首部可以包含选项部分(图17-2)。仅在最初的TCP规范中定义的选项是选项表结束、无操作和最大报文段长度。在我们的例子中,几乎每个SYN报文段中我们都遇到过MSS选项。

192TCP/IP详解,卷1:协议

图18-20 TCP选项

每个选项的开始是1字节kind字段,说明选项的类型。kind字段为0和1的选项仅占1个字节。其他的选项在kind字节后还有len字节。它说明的长度是指总长度,包括kind字节和len字节。

设置无操作选项的原因在于允许发方填充字段为4字节的倍数。如果我们使用4.4BSD系统进行初始化TCP连接,tcpdump将在初始的SYN上显示下面TCP选项:

<mss 512, nop, wscale 0, nop, nop, timestamp 146647 0>

MSS选项设置为512,后面是NOP,接着是窗口扩大选项。第一个NOP用来将窗口扩大选项填充为4字节的边界。同样,10字节的时间戳选项放在两个NOP后,占12字节,同时使两个4字节的时间戳满足4字节边界。

其他kind值为4、5、6和7的四个选项称为选择ACK及回显选项。由于回显选项已被时间戳选项取代,而目前定义的选择ACK选项仍未定论,并未包括在RFC 1323中,因此图18-20没有将它们列出。另外,作为TCP事务(第24.7节)的T/TCP建议也指明kind为11,12和13的三个选项。

18.11 TCP服务器的设计

我们在1.8节说过大多数的TCP服务器进程是并发的。当一个新的连接请求到达服务器时,服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。不同的操作系统使用不同的技术来调用新的服务器进程。在Unix系统下,常用的技术是使用fork函数来创建新的进程。如果系统支持,也可使用轻型进程,即线程(thread)。

我们感兴趣的是TCP与若干并发服务器的交互作用。需要回答下面的问题:当一个服务器进程接受一来自客户进程的服务请求时是如何处理端口的?如果多个连接请求几乎同时到达会发生什么情况?

第18章 TCP连接的建立与终止193

-a标志将显示网络中的所有主机端,而不仅仅是处于ESTABLISHED的主机端。-n标志将以点分十进制的形式显示IP地址,而不是通过DNS将地址转化为主机名,同时还要求显示端口号(例如为23)而不是服务名称(如Te lnet)。-f inet选项则仅要求显示使用TCP或UDP的主机。

显示的本地地址为*.23,星号通常又称为通配符。这表示传入的连接请求(即SYN)将被任何一个本地接口所接收。如果该主机是多接口主机,我们将制定其中的一个IP地址为本地IP地址,并且只接收来自这个接口的连接(在本节后面我们将看到这样的例子)。本地端口为23,这是Telnet的熟知端口号。

远端地址显示为*.*,表示还不知道远端IP地址和端口号,因为该端还处于LISTEN状态,正等待连接请求的到达。

现在我们在主机slip(140.252.13.65)启动一个Te lnet客户程序来连接这个Te lnet服务器。以下是netstat程序的输出行:

端口为23的第1行表示处于ESTABLISHED状态的连接。另外还显示了这个连接的本地IP地址、本地端口号、远端IP地址和远端端口号。本地IP地址为该连接请求到达的接口(以太网接口,140.252.13.33)。

处于LISTEN状态的服务器进程仍然存在。这个服务器进程是当前Te lnet服务器用于接收其他的连接请求。当传入的连接请求到达并被接收时,系统内核中的TCP模块就创建一个处于ESTABLISHED状态的进程。另外,注意处于ESTABLISHED状态的连接的端口不会变化:也是23,与处于LISTEN状态的进程相同。

现在我们在主机slip上启动另一个Telnet客户进程,并仍与这个Telnet服务器进行连接。以下是netstat程序的输出行:

现在我们有两条从相同主机到相同服务器的处于ESTABLISHED的连接。它们的本地端口号均为23。由于它们的远端端口号不同,这不会造成冲突。因为每个Telnet客户进程要使用一个外设端口,并且这个外设端口会选择为主机(slip)当前未曾使用的端口,因此它们的端口号肯定不同。

194TCP/IP详解,卷1:协议

现在第一个ESTABLISHED连接的本地IP地址对应多地址主机sun中的SLIP链路接口地址(140.252.1.29)。

18.11.2 限定的本地IP地址

我们来看看当服务器不能任选其本地IP地址而必须使用特定的IP地址时的情况。如果我们为sock程序指明一个IP地址(或主机名),并将它作为服务器,那么该IP地址就成为处于LISTEN服务器的本地IP地址。例如

sun % sock -s 140.252.1.29 8888

使这个服务器程序的连接仅局限于来自SLIP接口(140.252.1.29)。netstat的显示说明了这一点:

如果我们从主机solaris通过SLIP链路与这个服务器相连接,它将正常工作。

但如果我们试图从以太网(140.252.13)中的主机与这个服务器进行连接,连接请求将被TCP模块拒绝。如果使用tcpdump来观察这一切,对连接请求SYN的响应是一个如图18-21所示的RST。

图18-21 具有限定本地IP地址服务器对连接请求的拒绝

这个连接请求将不会到达服务器的应用程序,因为它根据应用程序中指定的本地IP地址被内核中的TCP模块拒绝。

第18章 TCP连接的建立与终止195

图18-22 TCP服务器本地和远端IP地址及端口号的规范

18.11.4 呼入连接请求队列

一个并发服务器调用一个新的进程来处理每个客户请求,因此处于被动连接请求的服务器应该始终准备处理下一个呼入的连接请求。那正是使用并发服务器的根本原因。但仍有可能出现当服务器在创建一个新的进程时,或操作系统正忙于处理优先级更高的进程时,到达多个连接请求。当服务器正处于忙时,TCP是如何处理这些呼入的连接请求?

在伯克利的TCP实现中采用以下规则:

  1. 正等待连接请求的一端有一个固定长度的连接队列,该队列中的连接已被TCP接受(即三次握手已经完成),但还没有被应用层所接受。
    注意区分TCP接受一个连接是将其放入这个队列,而应用层接受连接是将其从该队列中移出。
  2. 应用层将指明该队列的最大长度,这个值通常称为积压值(backlog)。它的取值范围是0~5之间的整数,包括0和5(大多数的应用程序都将这个值说明为5)。
  3. 当一个连接请求(即SYN)到达时,TCP使用一个算法,根据当前连接队列中的连接数来确定是否接收这个连接。我们期望应用层说明的积压值为这一端点所能允许接受连接的最大数目,但情况不是那么简单。图18-23显示了积压值与传统的伯克利系统和Solaris2.2所能允许的最大接受连接数之间的关系。注意,积压值说明的是TCP监听的端点已被TCP接受而等待应用层接受的最大连接数。这个积压值对系统所允许的最大连接数,或者并发服务器所能并发处理的客户数,并无影响。在这个图中,Solaris系统规定的值正如我们所期望的。而传统的BSD系统,将这个值(由于某些原因)设置为积压值乘3除以2,再加1。

    图18-23 对正在听的端点所允许接受的最大连接数
  4. 如果对于新的连接请求,该TCP监听的端点的连接队列中还有空间(基于图18-23),TCP模块将对SYN进行确认并完成连接的建立。但应用层只有在三次握手中的第三个报文段收到后才会知道这个新连接时。另外,当客户进程的主动打开成功但服务器的应用层还不知道这个新的连接时,它可能会认为服务器进程已经准备好接收数据了(如果发生这种情况,服务器的TCP仅将接收的数据放入缓冲队列)。
  5. 如果对于新的连接请求,连接队列中已没有空间,TCP将不理会收到的SYN。也不发回任何报文段(即不发回RST)。如果应用层不能及时接受已被TCP接受的连接,这些连接可能占满整个连接队列,客户的主动打开最终将超时。

196TCP/IP详解,卷1:协议

图18-24 积压值例子的tcpdump输出

第18章 TCP连接的建立与终止197

  • 在18.6节的最后,我们介绍了FIN_WAIT_2状态,提到如果应用程序仅过11分钟后实行完全关闭(不是半关闭),许多具体的实现都将一个连接由这个状态转移到CLOSED状态。如果另一端(处于CLOSE_WAIT状态)在宣布关闭(即发送FIN)之前等待了12分钟,这一端的TCP将如何响应这个FIN?
  • 对于一个电话交谈,哪一方是主动打开,哪一方是被动打开?是否允许同时打开?是否允许同时关闭?
  • 在图18-6中,我们没有见到一个ARP请求或一个ARP应答。显然主机svr4的硬件地址一定在bsdi的ARP高速缓存中。如果这个ARP高速缓存不存在,这个图会有什么变化?
  • 解释如下的tcpdump输出,并和图18-13进行比较。
  • 为什么图18-4中的服务器不将对客户FIN的ACK与自己的FIN合并,从而将报文段数减少为3个?
  • 在图18-16中,RST的序号为什么是26368002?
  • TCP向链路层查询MTU是否违反分层的规则?
  • 假定在图14.16中,每个DNS使用TCP而不是UDP进行查询,试问需要交换多少个报文段?
  • 假定MSL为120秒,试问系统能够初始化一个新连接然后进行主动关闭的最大速率是多少?
  • 阅读RFC 793,分析处于TIME_WAIT状态的主机收到使其进入此状态的重复的FIN时所发生的情况。
  • 阅读RFC793,分析处于TIME_WAIT状态的主机收到一个RST时所发生的情况。
  • 阅读Host Requirements RFC并找出半双工TCP关闭的定义。
  • 在图1-8中,我们曾提到到来的TCP报文段可根据其目的端口号进行分用,请问这种说法是否正确?
  • 第18章 TCP连接的建立与终止199







    - 本页无内容 -






    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: