相关博客:https://blog.csdn.net/knowledgebao/article/details/84626184
目录
第18章 TCP连接的建立与终止
18.1 引言
T C P是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。本章将详细讨论一个 T C P连接是如何建立的以及通信结束后是如何终止的。这种两端间连接的建立与无连接协议如 U D P不同。我们在第 11章看到一端使用U D P向另一端发送数据报时,无需任何预先的握手。
18.2 连接的建立与终止
为了了解一个T C P连接在建立及终止时发生了什么,我们在系统 s v r 4上键入下列命令:
t e l n e t命令在与丢弃( d i s c a r d )服务(参见1 . 1 2节)对应的端口上与主机 b s d i建立一条T C P连接。这服务类型正是我们需要观察的一条连接建立与终止的服务类型,而不需要服务器发起任何数据交换。
18.2.1 tcpdump的输出
图1 8 - 1显示了这条命令产生T C P报文段的t c p d u m p输出。
这7个T C P报文段仅包含T C P首部。没有任何数据。对于T C P段,每个输出行开始按如下格式显示:
源 > 目的: 标志
这里的标志代表T C P首部(图1 7 - 2)中6个标志比特中的4个。图1 8 - 2显示了表示标志的5个字符的含义。
在这个例子中,我们看到了 S、F和句点“.”标志符。我们将在以后看到其他的两个标志( R和P)。T C P首部中的其他两个标志比特 — ACK 和 U R G — t c p d u m p将作特殊显示。图1 8 - 2所示的4个标志比特中的多个可能同时出现在一个报文段中,但通常一次只见到一个。
RFC 1025 [Postel 1987], “TCP and IP Bake Off”,将一种报文段称为K a m i k a z e分组 ,
在这样的报文段中有最大数量的标志比特同时被置为1(SYN, URG, PSH, FIN和1字节的
数据)。这样的报文段也叫作nastygram, 圣诞树分组,灯测试报文段(lamp test segment)。
在第1行中,字段1 4 1 5 5 3 1 5 2 1 : 1 4 1 5 5 3 1 5 2 1 ( 0 )表示分组的序号是1 4 1 5 5 3 1 5 2 1,而报文段中数据字节数为0。t c p d u m p显示这个字段的格式是开始的序号、一个冒号、隐含的结尾序号及圆括号内的数据字节数。显示序号和隐含结尾序号的优点是便于了解数据字节数大于 0时的隐含结尾序号。这个字段只有在满足条件( 1)报文段中至少包含一个数据字节;或者( 2)S Y N、F I N或R S T被设置为1时才显示。图1 8 - 1中的第1、2、4和6行是因为标志比特被置为1而显示这个字段的,在这个例子中通信双方没有交换任何数据。
每行显示的字段win 4096表示发端通告的窗口大小。在这些例子中,我们没有交换任何数据,窗口大小就维持默认情况下的 4 0 9 6(我们将在2 0 . 4节中讨论T C P窗口大小)。
图1 8 - 1中的最后一个字段<mss 1024>表示由发端指明的最大报文段长度选项。发端将不接收超过这个长度的 T C P报文段。这通常是为了避免分段(见 11 . 5节)。我们将在1 8 . 4节讨论最大报文段长度,而在1 8 . 1 0节介绍不同TCP 选项的格式。
18.2.2 时间系列
图1 8 - 3显示了这些分组序列的时间系列(在图 6 - 11中已经首次介绍了这些时间系列的一些基本特性)。这个图显示出哪一端正在发送分组。我们也将对 t c p d u m p输出作一些扩展(例如,印出S Y N而不是S)。在这个时间系列中也省略窗口大小的值,因为它和我们的讨论无关。
18.2.3 建立连接协议
现在让我们回到图1 8 - 3所示的T C P协议中来。为了建立一条T C P连接:
- 请求端(通常称为客户)发送一个 S Y N段指明客户打算连接的服务器的端口,以及初始序号(I S N,在这个例子中为1415531521)。这个S Y N段为报文段1。
- 服务器发回包含服务器的初始序号的 S Y N报文段(报文段2)作为应答。同时,将确认序号设置为客户的I S N加1以对客户的S Y N报文段进行确认。一个S Y N将占用一个序号。
- 客户必须将确认序号设置为服务器的 I S N加1以对服务器的S Y N报文段进行确认(报文段3)。
这三个报文段完成连接的建立。这个过程也称为三次握手( three-way handshake)。
发送第一个S Y N的一端将执行主动打开( active open)。接收这个S Y N并发回下一个S Y N的另一端执行被动打开(passive open)(在1 8 . 8节我们将介绍双方如何都执行主动打开)。
当一端为建立连接而发送它的 S Y N时,它为连接选择一个初始序号。 I S N随时间而变化,因此每个连接都将具有不同的 I S N。RFC 793 [Postel 1981c]指出I S N可看作是一个3 2比特的计数器,每4 m s加1。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释。
报文段3与报文段4之间4 . 1秒的时间间隔是建立 T C P连接到向t e l n e t键入q u i t命令来中止该连接的时间。
18.2.4 连接终止协议
建立一个连接需要三次握手,而终止一个连接要经过 4次握手。这由T C P的半关闭(h a l f -c l o s e)造成的。既然一个T C P连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个 F I N来终止这个方向连接。当一端收到一个 F I N,它必须通知应用层另一端几经终止了那个方向的数据传送。发送F I N通常是应用层进行关闭的结果。
收到一个F I N只意味着在这一方向上没有数据流动。一个 T C P连接在收到一个F I N后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的 T C P应用程序这样做。正常关闭过程如图1 8 - 3所示。我们将在1 8 . 5节中详细介绍半关闭。
首先进行关闭的一方(即发送第一个 F I N)将执行主动关闭,而另一方(收到这个 F I N)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭,但我们将在 1 8 . 9节看到双方如何都执行主动关闭。
图1 8 - 3中的报文段4发起终止连接,它由Te l n e t客户端关闭连接时发出。这在我们键入 q u i t命令后发生。它将导致T C P客户端发送一个F I N,用来关闭从客户到服务器的数据传送。
当服务器收到这个 F I N,它发回一个A C K,确认序号为收到的序号加 1(报文段5)。和S Y N一样,一个F I N将占用一个序号。同时T C P服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的 T C P端发送一个F I N(报文段6),客户必须发回一个确认,并将确认序号设置为收到序号加1(报文段7)。
图1 8 - 4显示了终止一个连接的典型握手顺序。我们省略了序号。在这个图中,发送F I N将导致应用程序关闭它们的连接,这些F I N的A C K是由T C P软件自动产生的。
连接通常是由客户端发起的,这样第一个 S Y N从客户传到服务器。每一端都能主动关闭这个连接(即首先发送 F I N)。然而,一般由客户端决定何时终止连接,因为客户进程通常由用户交互控制,用户会键入诸如“ q u i t”一样的命令来终止进程。在图 1 8 - 4中,我们能改变上边的标识,将左方定为服务器,右方定为客户,一切仍将像显示的一样工作(例如在 1 4 . 4节中的第一个例子中就是由d a y t i m e服务器关闭连接的)。
18.2.5 正常的t c p d u m p输出
对所有的数值很大的序号进行排序是很麻烦的,因此默认情况下 t c p d u m p只在显示S Y N报文段时显示完整的序号,而对其后的序号则显示它们与初始序号的相对偏移值(为了得到
图1 8 - 1的输出显示必须加上- S选项)。对应于图1 8 - 1的正常t c p d u m p显示如图1 8 - 5所示:除非我们需要显示完整的序号,否则将在以下的例子中使用这种形式的输出显示。
18.3 连接建立的超时
有很多情况导致无法建立连接。一种情况是服务器主机没有处于正常状态。为了模拟这种情 况,我们断开服务器主机的电缆线,然后向它发出te l n e t 命令。图18 - 6 显示了tc p d u m p 的输出。
在这个输出中有趣的一点是客户间隔多长时间发送一个 S Y N,试图建立连接。第2个S Y N 与第1个的间隔是5 . 8秒,而第3个与第2个的间隔是2 4秒。
图1 8 - 6中没有显示客户端在放弃建立连接尝试前进行 S Y N重传的时间。为了了解它我们 必须对t e l n e t命令进行计时:
时间差值是 7 6秒。大多数伯克利系统将建立一个新连接的最长时间限制为 7 5秒。我们将在 2 1 . 4节看到由客户发出的第 3个分组大约在1 6 : 2 5 : 2 9超时, 客户在它第3个分组发出后4 8秒而 不是7 5秒后放弃连接。
18.3.1 第一次超时时间
在图1 8 - 6中一个令人困惑的问题是第一次超时时间为 5 . 8秒,接近6秒,但不准确,相比之下第二个超时时间几乎准确地为 2 4秒。运行十多次测试,发现第一次超时时间在 5 . 5 9秒~ 5 . 9 3 秒之间变化。然而,第二次超时时间则总是 2 4 . 0 0秒(精确到小数点后面两位)。
这是因为B S D版的T C P软件采用一种500 ms的定时器。这种500 ms的定时器用于确定本章 中所有的各种各样的T C P超时。当我们键入t e l n e t命令,将建立一个6秒的定时器(1 2个时钟滴 答(t i c k)),但它可能在之后的 5 . 5秒~ 6秒内的任意时刻超时。图 1 8 - 7显示了这一发生过程。 尽管定时器初始化为1 2个时钟滴答,但定时计数器会在设置后的第一个 0~500 ms中的任意时 刻减1。从那以后,定时计数器大约每隔 500 ms减1,但在第1个500 ms内是可变的(我们使用 限定词“大约”是因为在 T C P每隔500 ms获得系统控制的瞬间,系统内核可能会优先处理其 他中断)。
当滴答计数器为 0时,6秒的定时器便会超时(见图 1 8 - 7),这个定时器会在以后的 2 4秒 (4 8个滴答)重新复位。之后的下一个定时器将更接近 2 4秒,因为当T C P的500 ms定时器被内 核调用时,它就会被修改一次。
18.3.2 服务类型字段
在图1 8 - 6中,出现了符号 [ tos 0x10 ]。这是I P数据报内的服务类型(TO S)字段(参见图 3 - 2)。B S D / 3 8 6中的Te l n e t客户进程将这个字段设置为最小时延。
18.4 最大报文段长度
最大报文段长度(M S S)表示T C P传往另一端的最大块数据的长度。当一个连接建立时, 连接的双方都要通告各自的 M S S。我们已经见过M S S都是1 0 2 4。这导致I P数据报通常是4 0字 节长:2 0字节的T C P首部和2 0字节的I P首部。
在有些书中,将它看作可“协商”选项。它并不是任何条件下都可协商。当建立一个连 接时,每一方都有用于通告它期望接收的 M S S选项(M S S选项只能出现在S Y N报文段中)。如 果一方不接收来自另一方的M S S值,则M S S就定为默认值5 3 6字节(这个默认值允许2 0字节的 I P首部和2 0字节的T C P首部以适合5 7 6字节I P数据报)。
一般说来,如果没有分段发生, M S S还是越大越好(这也并不总是正确,参见图 2 4 - 3和 图2 4 - 4中的例子)。报文段越大允许每个报文段传送的数据就越多,相对 I P和T C P首部有更高 的网络利用率。当 T C P发送一个S Y N时,或者是因为一个本地应用进程想发起一个连接,或 者是因为另一端的主机收到了一个连接请求,它能将 M S S值设置为外出接口上的 M T U长度减 去固定的I P首部和T C P首部长度。对于一个以太网,M S S值可达1 4 6 0字节。使用IEEE 802.3的 封装(参见2 . 2节),它的M S S可达1 4 5 2字节。
在本章见到的涉及 B S D / 3 8 6和S V R 4的M S S为1 0 2 4,这是因为许多 B S D的实现版本需要M S S为5 1 2的倍数。其他的系统,如SunOS 4.1.3、Solaris 2.2 和AIX 3.2.2,当双方都在一个本 地以太网上时都规定M S S为1 4 6 0。[Mogul 1993] 的比较显示了在以太网上 1 4 6 0的M S S在性能 上比1 0 2 4的M S S更好。
如果目的I P地址为“非本地的( n o n l o c a l )”,M S S通常的默认值为5 3 6。而区分地址是本地 还是非本地是简单的,如果目的 I P地址的网络号与子网号都和我们的相同,则是本地的;如 果目的I P地址的网络号与我们的完全不同,则是非本地的;如果目的 I P地址的网络号与我们 的相同而子网号与我们的不同,则可能是本地的,也可能是非本地的。大多数 T C P实现版都 提供了一个配置选项(附录E和图E - 1),让系统管理员说明不同的子网是属于本地还是非本地。 这个选项的设置将确定 M S S可以选择尽可能的大(达到外出接口的 M T U长度)或是默认值 5 3 6。
M S S让主机限制另一端发送数据报的长度。加上主机也能控制它发送数据报的长度,这 将使以较小M T U连接到一个网络上的主机避免分段。 考虑我们的主机s l i p,通过M T U为2 9 6的S L I P链路连接到路由器b s d i上。图1 8 - 8显示这 些系统和主机s u n。
从s u n向s l i p发起一个T C P连接,并使用t c p d u m p来观察报文段。图1 8 - 9显示这个连接 的建立(省略了通告窗口大小)。
在这个例子中,s u n发送的报文段不能超过 2 5 6字节的数据,因为它收到的 M S S选项值为 2 5 6(第2行)。此外,由于s l i p知道它外出接口的 M T U长度为2 9 6,即使s u n已经通告它的 M S S为1 4 6 0,但为避免将数据分段,它不会发送超过 2 5 6字节数据的报文段。系统允许发送的 数据长度小于另一端的M S S值。
只有当一端的主机以小于5 7 6字节的M T U直接连接到一个网络中,避免这种分段才会有效。 如果两端的主机都连接到以太网上,都采用 5 3 6的M S S,但中间网络采用2 9 6的M T U,也将会 出现分段。使用路径上的M T U发现机制(参见2 4 . 2节)是关于这个问题的唯一方法。
18.5 TCP的半关闭
T C P提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。正如我们早些时候提到的只有很少的应用程序使用它。
为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据 传送,因此发送一个文件结束( F I N)给另一端,但我还想接收另一端发来的数据,直到它给 我发来文件结束(F I N)”。
如果应用程序不调用c l o s e而调用s h u t d o w n,且第2个参数值为1,则插口的A P I支持 半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。
图1 8 - 1 0显示了一个半关闭的典型例 子。让左方的客户端开始半关闭,当然也 可以由另一端开始。开始的两个报文段和 图1 8 - 4是相同的:初始端发出的 F I N,接 着是另一端对这个 F I N的A C K报文段。但 后面就和图1 8 - 4不同,因为接收半关闭的 一方仍能发送数据。我们只显示一个数据 报文段和一个A C K报文段,但可能发送了 许多数据报文段(将在第 1 9章讨论数据报 文段和确认报文段的交换)。当收到半关 闭的一端在完成它的数据传送后,将发送 一个F I N关闭这个方向的连接,这将传送 一个文件结束符给发起这个半关闭的应用 进程。当对第二个 F I N进行确认后,这个 连接便彻底关闭了。
为什么要有半关闭?一个例子是 U n i x中的r s h( 1 )命令,它将完成在另一个系统上执行一 个命令。命令
sun % rsh bsdi sort < datafile
将在主机b s d i上执行s o r t排序命令,r s h命令的标准输入来自文件d a t a f i l e。r s h将在它 与在另一主机上执行的程序间建立一个 T C P连接。 r s h的操作很简单:它将标准输入 (d a t a f i l e)复制给T C P连接,并将结果从 T C P连接中复制给标准输出(我们的终端)。图 1 8 - 11显示了这个建立过程(牢记T C P连接是全双工的)。
在远端主机b s d i上,r s h d服务器将执行s o r t程序,它的标准输入和标准输出都是 T C P 连接。第1 4章的 [Stevens 1990] 详细介绍了有关U n i x进程的结构,但这儿涉及的是使用 T C P连 接以及需要使用T C P的半关闭。
s o r t程序只有读取到所有输入数据后才能产生输出。所有的原始数据通过 T C P连接从 r s h客户端传送到s o r t服务器进行排序。当输入( d a t a f i l e)到达文件尾时, r s h客户端执行这个T C P连接的半关闭。接着s o r t服务器在它的标准输入(这个 T C P连接)上收到一个 文件结束符,对数据进行排序,并将结果写在它的标准输出上( T C P连接)。r s h客户端继续 接收来自T C P连接另一端的数据,并将排序的文件复制到它的标准输出上。
没有半关闭,需要其他的一些技术让客户通知服务器, 客户端已经完成了它的数据传送,但 仍要接收来自服务器的数据。使用两个TC P 连接也可作为一个选择,但使用半关闭的单连接更好。
18.6 TCP的状态变迁图
我们已经介绍了许多有关发起和终止 T C P连接的规则。这些规则都能从图 1 8 - 1 2所示的状 态变迁图中得出。
在这个图中要注意的第一点是一个状态变迁的子集是“典型的”。我们用粗的实线箭头表示正常的客户端状态变迁,用粗的虚线箭头表示正常的服务器状态变迁,细的实线表示其他情况。
第二点是两个导致进入ES T AB L I S H - ED 状态的变迁对应打开一个连接,而两 个导致从ES T AB L I S H E D 状态离开的变迁 对应关闭一个连接。ES T AB L I S H E D 状态 是连接双方能够进行双向数据传递的状 态。以后的章节将介绍这个状态。
将图中左下角4个状态放在一个虚 线框内,并标为“主动关闭”。其他两 个状态(C L O S E _ WA I T和L A S T _ A C K) 也用虚线框住,并标为“被动关闭”。
在 这 个 图 中 1 1 个 状 态 的 名 称 (CLOSED, LISTEN, SYN_SENT等) 是有意与n e t s t a t命令显示的状态名 称一致。n e t s t a t对状态的命名几乎 与 在 RFC 793中 的 最 初 描述 一 致 。 C L O S E D状态不是一个真正的状态, 而是这个状态图的假想起点和终点。
从L I S T E N到S Y N _ S E N T的变迁是 正确的,但伯克利版的T C P软件并不支持它。 只有当S Y N _ R C V D状态是从L I S T E N状态(正常情况)进入,而不是从 S Y N _ S E N T状态 (同时打开)进入时,从 S Y N _ R C V D回到L I S T E N的状态变迁才是有效的。这意味着如果我们 执行被动关闭(进入L I S T E N),收到一个S Y N,发送一个带A C K的S Y N(进入S Y N _ R C V D), 然后收到一个R S T,而不是一个A C K,便又回到L I S T E N状态并等待另一个连接请求的到来。
图1 8 - 1 3显示了在正常的T C P连接的建立与终止过程中,客户与服务器所经历的不同状态。 它是图1 8 - 3的再现,不同的是仅显示了一些状态。
假定在图1 8 - 1 3中左边的客户执行主动打开,而右边的服务器执行被动打开。尽管图中显 示出由客户端执行主动关闭,但和早前我们提到的一样,另一端也能执行主动关闭。
可以使用图1 8 - 1 2的状态图来跟踪图1 8 - 1 3的状态变化过程,以便明白每个状态的变化。
18.6.1 2MSL等待状态
T I M E _ WA I T状态也称为2 M S L等待状态。每个具体 T C P实现必须选择一个报文段最大生 存时间M S L(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。 我们知道这个时间是有限的,因为 T C P报文段以I P数据报在网络内传输,而I P数据报则有限制 其生存时间的T T L字段。
RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟, 或2分钟。
从第8章我们知道在实际应用中,对I P数据报T T L的限制是基于跳数,而不是定时器。 对一个具体实现所给定的 M S L值,处理的原则是:当 T C P执行一个主动关闭,并发回最
后一个A C K,该连接必须在T I M E _ WA I T状态停留的时间为2倍的M S L。这样可让T C P再次发 送最后的A C K以防这个A C K丢失(另一端超时并重发最后的F I N)。
这种2 M S L等待的另一个结果是这个 T C P连接在2 M S L等待期间,定义这个连接的插口 (客户的I P地址和端口号,服务器的 I P地址和端口号)不能再被使用。这个连接只能在 2 M S L 结束后才能再被使用。
遗憾的是,大多数 T C P实现(如伯克利版)强加了更为严格的限制。在 2 M S L等待期间, 插口中使用的本地端口在默认情况下不能再被使用。我们将在下面看到这个限制的例子。
某些实现和A P I提供了一种避开这个限制的方法。使用插口A P I时,可说明其中的 S O _ R E U S E A D D R选项。它将让调用者对处于2 M S L等待的本地端口进行赋值,但我们 将看到TCP原则上仍将避免使用仍处于2MSL连接中的端口。
在连接处于2 M S L等待时,任何迟到的报文段将被丢弃。因为处于 2 M S L等待的、由该插 口对(socket pair)定义的连接在这段时间内不能被再用,因此当要建立一个有效的连接时,来 自该连接的一个较早替身( i n c a r n a t i o n)的迟到报文段作为新连接的一部分不可能不被曲解 (一个连接由一个插口对来定义。一个连接的新的实例( i n s t a n c e)称为该连接的替身)。
我们说图1 8 - 1 3中客户执行主动关闭并进入 T I M E _ WA I T是正常的。服务器通常执行被动 关闭,不会进入T I M E _ WA I T状态。这暗示如果我们终止一个客户程序,并立即重新启动这个 客户程序,则这个新客户程序将不能重用相同的本地端口。这不会带来什么问题,因为客户 使用本地端口,而并不关心这个端口号是什么。
然而,对于服务器,情况就有所不同,因为服务器使用熟知端口。如果我们终止一个已 经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的 这个熟知端口赋值给它的端点,因为那个端口是处于 2 M S L连接的一部分。在重新启动服务器 程序前,它需要在1 ~ 4分钟。
可以通过s o c k程序看到这一切。我们启动服务器程序,从一个客户程序进行连接,然后 停止这个服务器程序。
当重新启动服务器程序时,程序报告一个差错信息说明不能绑定它的熟知端口,因为该端口 已被使用(即它处于2 M S L等待)。 运行n e t s t a t程序来查看连接的状态,以证实它的确处于 2 M S L等待状态。
如果我们一直试图重新启动服务器程序,并测量它直到成功所需的时间,我们就 能确定出2 M S L值。对于SunOS 4.1.3、S V R 4、B S D / 3 8 6和AIX 3.2.2,它需要1分钟才能 重新启动服务器程序,这意味着它们的M S L值为3 0秒。而对于Solaris 2.2 ,它需要4分钟才能重新启动服务器程序,这表示它的MSL值为2分钟。如果一个客户程序试图申请一个处于 2 M S L等待的端口(客户程序通常不会这么做),就 会出现同样的差错。
我们在第1次执行客户程序时采用 - v选项来查看它使用的本地端口为( 11 6 2)。第2次执行客 户程序时则采用-b选项来选择端口11 6 2为它的本地端口。正如我们所预料的那样,客户程序 无法那么做,因为那个端口是一个还处于 2 M S L等待连接的一部分。
需要再次强调2 M S L等待的一个效果,因为我们将在第2 7章的文件传输协议F T P中遇到它。 和以前介绍的一样,一个插口对(即包含本地 I P地址、本地端口、远端 I P地址和远端端口的4 元组)在它处于2 M S L等待时,将不能再被使用。尽管许多具体的实现中允许一个进程重新使 用仍处于2 M S L等待的端口(通常是设置选项 S O _ R E U S E A D D R),但T C P不能允许一个新的连 接建立在相同的插口对上。可通过下面的试验来看到这一点:
在第1次运行s o c k程序中,我们将它作为服务器程序,端口号为 6 6 6 6,并从主机b s d i上的一 个客户程序与它连接,这个客户程序使用的端口为 1 0 9 8。我们终止服务器程序,因此它将执 行主动关闭。这将导致 4元组 1 4 0 . 2 5 2 . 1 3 . 3 3(本地 I P地址)、6 6 6 6(本地端口号)、 1 4 0 . 2 5 2 . 1 3 . 3 5(另一端I P地址)和1 0 9 8(另一端的端口号)在服务器主机进入 2 M S L等待。
在第2次运行s o c k程序时,我们将它作为客户程序,并试图将它的本地端口号指明为 6 6 6 6,同时与主机b s d i在端口1 0 9 8上进行连接。但这个程序在试图将它的本地端口号赋值为 6 6 6 6时产生了一个差错,因为这个端口是处于 2 M S L等待4元组的一部分。
为了避免这个差错,我们再次运行这个程序,并使用选项- A来设置前面提到的 S O _ R E U S E A D D R。这将让s o c k程序能将它的本地端口号设置为 6 6 6 6,但当我们试图进行主 动打开时,又出现了一个差错。即使它能将它的本地端口设置为 6 6 6 6,但它仍不能和主机 b s d i在端口1 0 9 8上进行连接,因为定义这个连接的插口对仍处于 2 M S L等待状态。
如果我们试图从其他主机来建立这个连接会如何?首先我们必须在 s u n上以-A标记来重新 启动服务器程序,因为它需要的端口( 6 6 6 6)是还处于2 M S L等待连接的一部分。
sun % sock -A -s 6666 启动服务器程序,在端口6 6 6 6监听,接着,在2 M S L等待结束前,我们在b s d i上启动客户程序:
bsdi % sock -b1098 sun 6666
connected on 140.252.13.35.1098 to 140.252.13.33.6666
不幸的是它成功了!这违反了 T C P规范,但被大多数的伯克利版实现所支持。这些实现允许 一个新的连接请求到达仍处于 T I M E _ WA I T状态的连接,只要新的序号大于该连接前一个替身 的最后序号。在这个例子中,新替身的 I S N被设置为前一个替身最后序号与 128 000的和。附 录的RFC 1185 [Jacobsan、B r a d e n和Zhang 1990] 指出了这项技术仍可能存在缺陷。
对于同一连接的前一个替身,这个具体实现中的特性让客户程序和服务器程序能连续地 重用每一端的相同端口号,但这只有在服务器执行主动关闭才有效。我们将在图 2 7 - 8中使用 F T P时看到这个2 M S L等待条件的另一个例子。也见习题 1 8 . 5。
18.6.2 平静时间的概念
对于来自某个连接的较早替身的迟到报文段, 2 M S L等待可防止将它解释成使用相同插口 对的新连接的一部分。但这只有在处于 2 M S L等待连接中的主机处于正常工作状态时才有效。
如果使用处于2 M S L等待端口的主机出现故障,它会在 M S L秒内重新启动,并立即使用故 障前仍处于2 M S L的插口对来建立一个新的连接吗?如果是这样,在故障前从这个连接发出而 迟到的报文段会被错误地当作属于重启后新连接的报文段。无论如何选择重启后新连接的初 始序号,都会发生这种情况。
为了防止这种情况,RFC 793指出T C P在重启动后的M S L秒内不能建立任何连接。这就称 为平静时间(quiet time)。只有极少的实现版遵守这一原则,因为大多数主机重启动的时间都比MSL秒要长。
18.6.3 FIN_WAIT_2 状态
在F I N _ WA I T _ 2状态我们已经发出了F I N,并且另一端也已对它进行确认。除非我们在实 行半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并向我们发一 个F I N来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从 F I N _ WA I T _ 2状态进入T I M E _ WA I T状态。
这意味着我们这端可能永远保持这个状态。另一端也将处于 C L O S E _ WA I T状态,并一直 保持这个状态直到应用层决定进行关闭。
许多伯克利实现采用如下方式来防止这种在FI N _ W AI T _ 2 状态的无限等待。如果执 行主动关闭的应用层将进行全关闭,而不是半关闭来说明它还想接收数据,就设置一 个定时器。如果这个连接空闲1 0分钟7 5秒,T C P将进入C L O S E D状态。在实现代码的注 释中确认这个实现代码违背协议的规范。
18.7 复位报文段
我们已经介绍了T C P首部中的R S T比特是用于“复位”的。一般说来,无论何时一个报文 段发往基准的连接( referenced connection)出现错误,T C P都会发出一个复位报文段(这里 提到的“基准的连接”是指由目的 I P地址和目的端口号以及源 I P地址和源端口号指明的连接。 这就是为什么RFC 793称之为插口)。
18.7.1 到不存在的端口的连接请求
产生复位的一种常见情况是当连接请求到达时,目的端口没有进程正在听。对于 U D P, 我们在6 . 5节看到这种情况,当一个数据报到达目的端口时,该端口没在使用,它将产生一个 I C M P端口不可达的信息。而T C P则使用复位。 产生这个例子也很容易,我们可使用 Te l n e t客户程序来指明一个目的端口没在使用的情 况:
Te l n e t客户程序会立即显示这个差错信息。图 1 8 - 1 4显示了对应这个命令的分组交换过程。
在这个图中需要注意的值是复位报文段中的序号字段和确认序号字段。因为 A C K比特在 到达的报文段中没有被设置为1,复位报文段中的序号被置为 0,确认序号被置为进入的I S N加 上数据字节数。尽管在到达的报文段中没有真正的数据,但 S Y N比特从逻辑上占用了1字节的 序号空间;因此,在这个例子中复位报文段中确认序号被置为 I S N与数据长度(0)、S Y N比特 所占的1的总和。
18.7.2 异常终止一个连接
我们在1 8 . 2节中看到终止一个连接的正常方式是一方发送 F I N。有时这也称为有序释放 (orderly release),因为在所有排队数据都已发送之后才发送 F I N,正常情况下没有任何数据 丢失。但也有可能发送一个复位报文段而不是 F I N来中途释放一个连接。有时称这为异常释放 (abortive release)。
异常终止一个连接对应用程序来说有两个优点:(1)丢弃任何待发数据并立即发送复位 报文段;(2)R S T的接收方会区分另一端执行的是异常关闭还是正常关闭。应用程序使用的 A P I必须提供产生异常关闭而不是正常关闭的手段。
使用s o c k程序能够观察这种异常关闭的过程。 Socket API通过“linger on close”选项 (S O _ L I N G E R)提供了这种异常关闭的能力。我们加上 - L选项并将停留时间设为 0。这将导 致连接关闭时进行复位而不是正常的 F I N。我们连接到处于服务器上的 s o c k程序,并键入一 输入行:
bsdi % sock -L0 svr4 8888 这是客户程序,服务器程序显示后面
hello, world 键入一行输入,它被发往到另一端
^ D 键入文件结束符,终止客户程序
图1 8 - 1 5是这个例子的t c p d u m p输出显示(在这个图中我们已经删除了所有窗口大小的说 明,因为它们与讨论无关)。 第1 ~ 3行显示出建立连接的正常过程。第 4行发送我们键入的数据行( 1 2个字符和U n i x换行符),第5行是对收到数据的确认。
第6行对应为终止客户程序而键入的文件结束符(C o n t r o l _ D)。由于我们指明使用异常关闭 而不是正常关闭(命令行中的- L 0选项),因此主机b s d i端的T C P发送一个R S T而不是通常的F I N。 R S T报文段中包含一个序号和确认序号。需要注意的是 R S T报文段不会导致另一端产生任何响 应,另一端根本不进行确认。收到R S T的一方将终止该连接,并通知应用层连接复位。 我们在服务器上得到下面的差错信息:
svr4 % sock -s 8888 作为服务器进程运行,在端口 8 8 8 8监听
hello, world 这行是客户端发送的
read error: Connection reset by peer
这个服务器程序从网络中接收数据并将它接收的数据显示到其标准输出上。通常,从它 的T C P上收到文件结束符后便将结束,但这里我们看到当收到 R S T时,它产生了一个差错。这 个差错正是我们所期待的:连接被对方复位了。
18.7.3 检测半打开连接
如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的 T C P连接称为半 打开(H a l f - O p e n)的。任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开 连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。
半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后 再关机。这可能发生在使用 P C机作为Te l n e t的客户主机上,例如,用户在一天工作结束时关 闭P C机的电源。当关闭P C机电源时,如果已不再有要向服务器发送的数据,服务器将永远不 知道客户程序已经消失了。当用户在第二天到来时,打开 P C机,并启动新的Te l n e t客户程序, 在服务器主机上会启动一个新的服务器程序。这样会导致服务器主机中产生许多半打开的 T C P连接(在第2 3章中我们将看到使用T C P的k e e p a l i v e选项能使T C P的一端发现另一端已经消 失)。
能很容易地建立半打开连接。在 b s d i上运行Te l n e t客户程序,通过它和s v r 4上的丢弃服务 器建立连接。我们键入一行字符,然后通过 t c p d u m p进行观察,接着断开服务器主机与以太 网的电缆,并重启服务器主机。这可以模拟服务器主机出现异常(在重启服务器之前断开以 太网电缆是为了防止它向打开的连接发送 F I N,某些T C P在关机时会这么做)。服务器主机重 启后,我们重新接上电缆,并从客户向服务器发送另一行字符。由于服务器的 T C P已经重新 启动,它将丢失复位前连接的所有信息,因此它不知道数据报文段中提到的连接。 T C P的处 理原则是接收方以复位作为应答。
图1 8 - 1 6是这个例子的t c p d u m p输出显示(已从这个输出中删除了窗口大小的说明、服务 类型信息和M S S声明,因为它们与讨论无关)。
第1 ~ 3行是正常的连接建立过程。第 4行向丢弃服务器发送字符行“ h i t h e r e”,第5行是确
认。
然后是断开s v r 4的以太网电缆,重新启动s v r 4,并重新接上电缆。这个过程几乎需要 1 9 0 秒。接着从客户端输入下一行(即“ another line”),当我们键入回车键后,这一行被发往服 务器(图1 8 - 1 6的第6行)。这导致服务器产生一个响应,但要注意的是由于服务器主机经过重 新启动,它的 A R P高速缓存为空,因此需要一个 A R P请求和应答(第 7、8行)。第9行表示 R S T被发送出去。客户收到复位报文段后显示连接已被另一端的主机终止( Te l n e t客户程序发 出的最后信息不再有什么价值)。
18.8 同时打开
两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方 必须发送一个S Y N,且这些S Y N必须传递给对方。这需要每一方使用一个对方熟知的端口作 为本地端口。这又称为同时打开( simultaneous open)。
例如,主机A中的一个应用程序使用本地端口7 7 7 7,并与主机B的端口8 8 8 8执行主动打开。 主机B中的应用程序则使用本地端口8 8 8 8,并与主机A的端口7 7 7 7执行主动打开。
这与下面的情况不同:主机A中的Te l n e t客户程序和主机B中Te l n e t的服务器程序建立连接, 与此同时,主机B中的Te l n e t客户程序与主机A的Te l n e t服务器程序也建立连接。在这个 Te l n e t 例子中,两个Te l n e t服务器都执行被动打开,而不是主动打开,并且 Te l n e t客户选择的本地端 口不是另一端Te l n e t服务器进程所熟悉的端口。
T C P是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连 接(其他的协议族,最突出的是O S I运输层,在这种情况下将建立两条连接而不是一条连接)。
当出现同时打开的情况时,状态变迁与图 1 8 - 1 3所示的不同。两端几乎在同时发送 S Y N, 并进入S Y N _ S E N T状态。当每一端收到S Y N时,状态变为S Y N _ R C V D(如图1 8 - 1 2),同时它
们都再发S Y N并对收到的S Y N进行确认。当双方都收到 S Y N及相应的A C K时,状态都变迁为 E S TA B L I S H E D。图1 8 - 1 7显示了这些状态变迁过程。
一个同时打开的连接需要交换 4个报文段,比正常的三次握手多一个。此外,要注意的是 我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。许多伯克利版的T C P实现都不能正确地支持同时打开。例子相见参考资料1.
18.9 同时关闭
我们在以前讨论过一方(通常但不总是客户方)发送第一个 F I N执行主动关闭。双方都执 行主动关闭也是可能的,T C P协议也允许这样的同时关闭(simultaneous close)。
在图1 8 - 1 2中,当应用层发出关闭命令时,两端均从 E S TA B L I S H E D变为F I N _ WA I T _ 1。 这将导致双方各发送一个 F I N,两个F I N经过网络传送后分别到达另一端。收到 F I N后,状态 由F I N _ WA I T _ 1变迁到C L O S I N G,并发送最后的 A C K。当收到最后的 A C K时,状态变化为 T I M E _ WA I T。图1 8 - 1 9总结了这些状态的变化。
同时关闭与正常关闭使用的段交换数目相同。
18.10 TCP 选项
T C P首部可以包含选项部分(图1 7 - 2)。仅在最初的T C P规范中定义的选项是选项表结束、 无操作和最大报文段长度。在我们的例子中,几乎每个 S Y N报文段中我们都遇到过M S S选项。
新的R F C,主要是RFC 1323 [Jacobson, Braden和Borman 1992],定义了新的T C P选项,这些选项的大多数只在最新的 T C P实现中才能见到(我们将在第 2 4章介绍这些新选项)。图 1 8 - 2 0显示了当前T C P选项的格式,这些选项的定义出自于 RFC 793和RFC 1323。
每个选项的开始是1字节k i n d字段,说明选项的类型。k i n d字段为0和1的选项仅占1个字节。 其他的选项在k i n d字节后还有l e n字节。它说明的长度是指总长度,包括 k i n d字节和l e n字节。
设置无操作选项的原因在于允许发方填充字段为 4字节的倍数。如果我们使用4 . 4 B S D系统 进行初始化T C P连接,t c p d u m p将在初始的S Y N上显示下面T C P选项:
<mss 512, nop, wscale 0, nop, nop, timestamp 146647 0>
M S S选项设置为5 1 2,后面是N O P,接着是窗口扩大选项。第一个 N O P用来将窗口扩大选项填 充为4字节的边界。同样, 1 0字节的时间戳选项放在两个 N O P后,占1 2字节,同时使两个4字 节的时间戳满足4字节边界。
其他k i n d值为4、5、6和7的四个选项称为选择A C K及回显选项。由于回显选项已 被时间戳选项取代,而目前定义的选择 A C K选项仍未定论,并未包括在RFC 1323中, 因此图1 8 - 2 0没有将它们列出。另外,作为T C P事务(第2 4 . 7节)的T / T C P建议也指明 kind为11, 12和 13的三个选项。
18.11 TCP 服务器的设计
我们在1 . 8节说过大多数的T C P服务器进程是并发的。当一个新的连接请求到达服务器时, 服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。不同的操作系统使用不 同的技术来调用新的服务器进程。在 U n i x系统下,常用的技术是使用 f o r k函数来创建新的进 程。如果系统支持,也可使用轻型进程,即线程( t h r e a d)。
我们感兴趣的是 T C P与若干并发服务器的交互作用。需要回答下面的问题:当一个服务 器进程接受一来自客户进程的服务请求时是如何处理端口的?如果多个连接请求几乎同时到
达会发生什么情况?
18.11.1 TCP服务器端口号
通过观察任何一个 T C P服务器,我们能了解 T C P如何处理端口号。我们使用 n e t s t a t 命令来观察 Te l n e t服务器。下面是在没有 Te l n e t连接时的显示(只留下显示 Te l n e t服务器的 行)。
- a标志将显示网络中的所有主机端,而不仅仅是处于 E S TA B L I S H E D的主机端。- n标志 将以点分十进制的形式显示 I P地址,而不是通过 D N S将地址转化为主机名,同时还要求显示 端口号(例如为 2 3)而不是服务名称(如 Te l n e t)。-f inet选项则仅要求显示使用 T C P或 U D P的主机。
显示的本地地址为 * . 2 3,星号通常又称为通配符。这表示传入的连接请求(即 S Y N)将 被任何一个本地接口所接收。如果该主机是多接口主机,我们将制定其中的一个 I P地址为本 地I P地址,并且只接收来自这个接口的连接(在本节后面我们将看到这样的例子)。本地端口 为2 3,这是Te l n e t的熟知端口号。
远端地址显示为* . *,表示还不知道远端I P地址和端口号,因为该端还处于 L I S T E N状态, 正等待连接请求的到达。
现在我们在主机s l i p(1 4 0 . 2 5 2 . 1 3 . 6 5)启动一个Te l n e t客户程序来连接这个 Te l n e t服务器。 以下是n e t s t a t程序的输出行:
端口为2 3的第1行表示处于E S TABLISHED 状态的连接。另外还显示了这个连接的本地 I P 地址、本地端口号、远端 I P地址和远端端口号。 本地I P地址为该连接请求到达的接口(以太 网接口,1 4 0 . 2 5 2 . 1 3 . 3 3)。
处于L I S T E N状态的服务器进程仍然存在。这个服务器进程是当前 Te l n e t服务器用于接收 其他的连接请求。当传入的连接请求到达并被接收时,系统内核中的 T C P模块就创建一个处 于E S TA B L I S H E D状态的进程。另外,注意处于 E S TA B L I S H E D状态的连接的端口不会变化: 也是2 3,与处于L I S T E N状态的进程相同。
现在我们在主机s l i p上启动另一个Te l n e t客户进程,并仍与这个Te l n e t服务器进行连接。以 下是n e t s t a t程序的输出行:
现在我们有两条从相同主机到相同服务器的处于 E S TA B L I S H E D的连接。它们的本地端口号均 为2 3。由于它们的远端端口号不同,这不会造成冲突。因为每个 Te l n e t客户进程要使用一个外设端口,并且这个外设端口会选择为主机( s l i p)当前未曾使用的端口,因此它们的端口号 肯定不同。
这个例子再次重申T C P使用由本地地址和远端地址组成的 4元组:目的I P地址、目的端口 号、源I P地址和源端口号来处理传入的多个连接请求。 T C P仅通过目的端口号无法确定那个 进程接收了一个连接请求。另外,在三个使用端口 2 3的进程中,只有处于L I S T E N的进程能够 接收新的连接请求。处于 E S TA B L I S H E D的进程将不能接收S Y N报文段,而处于L I S T E N的进 程将不能接收数据报文段。
下面我们从主机s o l a r i s上启动第3个Te l n e t客户进程,这个主机通过 S L I P链路与主机s u n相 连,而不是以太网接口。
现在第一个 E S TA B L I S H E D连接的本地 I P地址对应多地址主机 s u n中的S L I P链路接口地址 (1 4 0 . 2 5 2 . 1 . 2 9)。
18.11.2 限定的本地IP地址
我们来看看当服务器不能任选其本地 I P地址而必须使用特定的 I P地址时的情况。如果我 们为s o c k程序指明一个I P地址(或主机名),并将它作为服务器,那么该 I P地址就成为处于 L I S T E N服务器的本地I P地址。例如
sun % sock -s 140.252.1.29 8888
使这个服务器程序的连接仅局限于来自 S L I P接口(1 4 0 . 2 5 2 . 1 . 2 9)。n e t s t a t的显示说明了这 一点:
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 140.252.1.29.8888 *.* LISTEN
如果我们从主机s o l a r i s通过S L I P链路与这个服务器相连接,它将正常工作。
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 140.252.1.29.8888 140.252.1.32.34614 ESTABLISHED
tcp 0 0 140.252.1.29.8888 *.* LISTEN
但如果我们试图从以太网(1 4 0 . 2 5 2 . 1 3)中的主机与这个服务器进行连接,连接请求将被 T C P模块拒绝。如果使用t c p d u m p来观察这一切,对连接请求S Y N的响应是一个如图1 8 - 2 1所 示的R S T。
这个连接请求将不会到达服务器的应用程序,因为它根据应用程序中指定的本地 I P地址 被内核中的T C P模块拒绝。
18.11.3 限定的远端IP地址
在11 . 1 2节,我们知道U D P服务器通常在指定I P本地地址和本地端口外,还能指定远端 I P 地址和远端端口。RFC 793中显示的接口函数允许一个服务器在执行被动打开时,可指明远端 插口(等待一个特定的客户执行主动打开),也可不指明远端插口(等待任何客户)。 遗憾的是,大多数A P I都不支持这么做。服务器必须不指明远端插口,而等待连接请求的 到来,然后检查客户端的I P地址和端口号。
图1 8 - 2 2总结了T C P服务器进行连接时三种类型的地址绑定。在三种情况中, l p o r t是服务 器的熟知端口,而l o c a l I P必须是一个本地接口的I P地址。表中行的顺序正是T C P模块在收到一 个连接请求时确定本地地址的顺序。最常使用的绑定(第 1行,如果支持的话)将最先尝试, 最不常用的(最后一行两端的I P地址都没有制定)将最后尝试。
18.11.4 呼入连接请求队列
一个并发服务器调用一个新的进程来处理每个客户请求,因此处于被动连接请求的服务 器应该始终准备处理下一个呼入的连接请求。那正是使用并发服务器的根本原因。但仍有可 能出现当服务器在创建一个新的进程时,或操作系统正忙于处理优先级更高的进程时,到达 多个连接请求。当服务器正处于忙时, T C P是如何处理这些呼入的连接请求?
在伯克利的T C P实现中采用以下规则:
1) 正等待连接请求的一端有一个固定长度的连接队列,该队列中的连接已被 T C P接受 (即三次握手已经完成),但还没有被应用层所接受。 注意区分T C P接受一个连接是将其放入这个队列,而应用层接受连接是将其从该队列 中移出。
2) 应用层将指明该队列的最大长度,这个值通常称为积压值 ( b a c k l o g )。它的取值范围是 0 ~ 5之间的整数,包括0和5(大多数的应用程序都将这个值说明为 5)。
3) 当一个连接请求(即S Y N)到达时,T C P使 用一个算法,根据当前连接队列中的连接数 来确定是否接收这个连接。我们期望应用层 说明的积压值为这一端点所能允许接受连接 的最大数目,但情况不是那么简单。图18 - 2 3 显示了积压值与传统的伯克利系统和S o l a r i s 2. 2 所能允许的最大接受连接数之间的关系。 注意,积压值说明的是 T C P监听的端点已 被T C P接受而等待应用层接受的最大连接数。这个积压值对系统所允许的最大连接数, 或者并发服务器所能并发处理的客户数,并无影响。
在这个图中,S o l a r i s系统规定的值正如我们所期望的。而传统的B S D系统,将这个值(由于某些原因)设置为积压值乘3除以2,再加1。
4) 如果对于新的连接请求,该 T C P监听的端点的连接队列中还有空间(基于图 1 8 - 2 3), T C P模块将对S Y N进行确认并完成连接的建立。但应用层只有在三次握手中的第三个 报文段收到后才会知道这个新连接时。另外,当客户进程的主动打开成功但服务器的 应用层还不知道这个新的连接时,它可能会认为服务器进程已经准备好接收数据了 (如果发生这种情况,服务器的T C P仅将接收的数据放入缓冲队列)。
5) 如果对于新的连接请求,连接队列中已没有空间, T C P将不理会收到的S Y N。也不发 回任何报文段(即不发回 R S T)。如果应用层不能及时接受已被 T C P接受的连接,这些 连接可能占满整个连接队列,客户的主动打开最终将超时。
通过s o c k程序能了解这种情况。我们调用它,并使用新的选项( - O)。让它在创建一个 新的服务器进程后而没有接受任何连接请求之前暂停下来。如果在它暂停期间又调用了多个 客户进程,它将导致接受连接队列被填满,通过 t c p d u m p能够看到这一切。
bsdi % sock -s -v -q1 -O30 5555
- q 1选项将服务器端的积压值置 1。在这种情况下,传统的 B S D系统中的队列允许接受两 个连接请求(图1 8 - 2 3)。- O 3 0选项使程序在接受任何客户连接之前暂停 3 0秒。在这3 0秒内, 我们可启动其他客户进程来填充这个队列。在主机 s u n上启动4个客户进程。
图1 8 - 2 4显示了t c p d u m p的输出,首先是第 1个客户进程的第 1个S Y N(省略窗口大小和 M S S声明。当T C P连接建立时,将客户进程的端口号用粗体标出)。
端口为1 0 9 0的第一个客户连接请求被T C P接受(报文段1 ~ 3)。端口为1 0 9 1的第2个客户连 接请求也被T C P接受(报文段4 ~ 6)。而服务器的应用仍处于休眠状态,还未接受任何连接。 目前的一切工作都由内核中的 T C P模块完成。另外,两个客户进程已经成功地完成了它们的 主动打开,因为它们建立连接的三次握手已经完成。
我们接着在报文段7(端口1 0 9 2)和报文段8(端口1 0 9 3)启动第3和第4个客户进程。由 于服务器的连接队列已满,T C P将不理会两个S Y N。这两个客户进程在报文段9, 10, 11, 12, 15 重发它们的S Y N。第4个客户进程的第3个S Y N重传被接受了,因为服务器程序的 3 0秒休眠结 束后,它将已接受的两个连接从队列中移出,使连接队列变空(服务器程序接收连接的时间 是2 8 . 1 9,小于3 0的原因在于启动服务器程序后它需要几秒的时间来启动第 1个客户进程(报文 段1,显示的就是启动时间))。第3个客户进程的第4个S Y N重传这时将被接受(报文段1 5 ~ 1 7)。 服务器程序先接受第4个客户连接(端口1 0 9 3)的原因是服务器程序3 0秒休眠与客户程序重传 之间的定时交互作用。
我们期望接收连接队列按先进先出顺序传递给应用层。如 T C P接受了端口为1 0 9 0 和1 0 9 1的连接,我们希望应用层先接受端口为1 0 9 0的连接,然后再接受端口为1 0 9 1的 连接。但许多伯克利的T C P实现都出现按后进先出的传递顺序,这个错误已存在了多 年。产商最近已开始改正这个错误,但在如SunOS 4.13等系统中仍存在这个问题。
当队列已满时,T C P将不理会传入的S Y N,也不发回R S T作为应答,因为这是一个软错误, 而不是一个硬错误。通常队列已满是由于应用程序或操作系统忙造成的,这样可防止应用程 序对传入的连接进行服务。这个条件在一个很短的时间内可以改变。但如果服务器的 T C P以 系统复位作为响应,客户进程的主动打开将被废弃(如果服务器程序没有启动我们就会遇到)。 由于不应答S Y N,服务器程序迫使客户 T C P随后重传S Y N,以等待连接队列有空间接受新的 连接。
这个例子中有一个巧妙之处,这在大多 T C P / I P的具体实现中都能见到,就是如果服务器 的连接队列未满时,T C P将接受传入的连接请求(即 S Y N),但并不让应用层了解该连接源于 何处(即不告知源I P地址和源端口)。这不是T C P所要求的,而只是共同的实现技术(如伯克 利源代码通常都这么做)。如果一个A P I如T L I(见1 . 1 5节)向应用程序提供了解连接请求的到 来的方法,并允许应用程序选择是否接受连接。当应用程序假定被告知连接请求已经到来时, T C P的三次握手已经结束!其他运输层的实现可能将连接请求的到达与接受分开(如 O S I的运 输层),但T C P不是这样。
Solaris 2.2 提供了一个选项使 T C P只有在应用程序说可以接受( t c p _ e a g e r _ li s t e n e r s 见E. 4 ),才允许接受传入的连接请求。
这种行为也意味着 T C P服务器无法使客户进程的主动打开失效。当一个新的客户连接传 递给服务器的应用程序时, T C P的三次握手就结束了,客户的主动打开已经完全成功。如果 服务器的应用程序此时看到客户的 I P地址和端口号,并决定是否为该客户进行服务,服务器 所能做的就是关闭连接(发送 F I N),或者复位连接(发送R S T)。无论哪种情况,客户进程都 认为一切正常,因为它的主动打开已经完成,并且已经向服务器程序发送过请求。
18.12 小结
两个进程在使用 T C P交换数据之前,它们之间必须建立一条连接。完成后,要关闭这 个连接。本章已经详细介绍了如何使用三次握手来建立连接以及使用 4个报文段来关闭连 接。
我们用t c p d u m p程序显示了T C P首部中的各个字段。也了解了连接建立是如何超时,连接复位是如何发送,使用半打开连接发生的情况以及 T C P是如何提供半关闭、同时打开和同 时关闭。
弄清T C P操作的关键在于它的状态变迁图。我们跟踪了连接建立与关闭的步骤以及它们的 状态变迁过程。还讨论了在设计T C P并发服务器时T C P连接建立的具体实现方法。
一个T C P连接由一个4元组唯一确定:本地I P地址、本地端口号、远端 I P地址和远端端口 号。无论何时关闭一个连接,一端必须保持这个连接,我们看到 T I M E _ WA I T状态将处理这个 问题。处理的原则是执行主动打开的一端在进入这个状态时要保持的时间为 T C P实现中规定 的M S L值的两倍。
参考资料:
1,TCP-IP详解卷1:第18章
链接:https://pan.baidu.com/s/1l9H40jhYgcw4jwh0is7hHA 提取码:hz0z
有任何问题,请联系:knowledgebao@163.com