018TCP连接的建立与终止.docx
《018TCP连接的建立与终止.docx》由会员分享,可在线阅读,更多相关《018TCP连接的建立与终止.docx(46页珍藏版)》请在冰豆网上搜索。
018TCP连接的建立与终止
下载
第18章TCP连接的建立与终止
18.1引言
TCP是一个面向连接的协议。
无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。
本章将详细讨论一个TCP连接是如何建立的以及通信结束后是如何终止的。
这种两端间连接的建立与无连接协议如UDP不同。
我们在第11章看到一端使用UDP向另一端发送数据报时,无需任何预先的握手。
18.2连接的建立与终止
为了了解一个TCP连接在建立及终止时发生了什么,我们在系统svr4上键入下列命令:
键入Ctrl和右括号,使Telnet客户进程终止连接
telnet命令在与丢弃(discard)服务(参见1.12节)对应的端口上与主机bsdi建立一条
TCP连接。
这服务类型正是我们需要观察的一条连接建立与终止的服务类型,而不需要服务器发起任何数据交换。
18.2.1tcpdump的输出
图18-1显示了这条命令产生TCP报文段的tcpdump输出。
图18-1TCP连接建立与终止的tcpdump输出显示
这7个TCP报文段仅包含TCP首部。
没有任何数据。
对于TCP段,每个输出行开始按如下格式显示:
源>目的:
标志
这里的标志代表TCP首部(图17-2)中6个标志比特中的4个。
图18-2显示了表示标志的5
个字符的含义。
标志3字符缩写描述
同步序号发送方完成数据发送复位连接
尽可能快地将数据送往接收进程
以上四个标志比特均置0
图18-2tcpdump对TCP首部中部分标志比特的字符表示
在这个例子中,我们看到了S、F和句点“.”标志符。
我们将在以后看到其他的两个标志(R
和P)。
TCP首部中的其他两个标志比特—ACK和URG—tcpdump将作特殊显示。
图18-2所示的4个标志比特中的多个可能同时出现在一个报文段中,但通常一次只见到一个。
,
RFC1025[Postel1987],“TCPandIPBakeOff”,将一种报文段称为Kamikaze分组在这样的报文段中有最大数量的标志比特同时被置为1(SYN,URG,PSH,FIN和1字节的数据)。
这样的报文段也叫作nastygram,圣诞树分组,灯测试报文段(lamptestsegment)。
在第1行中,字段1415531521:
1415531521(0)表示分组的序号是1415531521,而报文段中数据字节数为0。
tcpdump显示这个字段的格式是开始的序号、一个冒号、隐含的结尾序号及圆括号内的数据字节数。
显示序号和隐含结尾序号的优点是便于了解数据字节数大于0时的隐含结尾序号。
这个字段只有在满足条件
(1)报文段中至少包含一个数据字节;或者
(2)SYN、FIN或RST被设置为1时才显示。
图18-1中的第1、2、4和6行是因为标志比特被置为1而显示这个字段的,在这个例子中通信双方没有交换任何数据。
在第2行中,字段ack141553152表2示确认序号。
它只有在首部中的ACK标志比特被设置1时才显示。
每行显示的字段win4096表示发端通告的窗口大小。
在这些例子中,我们没有交换任何数据,窗口大小就维持默认情况下的4096(我们将在20.4节中讨论TCP窗口大小)。
图18-1中的最后一个字段表示由发端指明的最大报文段长度选项。
发端将不接收超过这个长度的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连接:
Ka
mikaze是神风队队员或神风队所使用的飞机。
在第二次世界大战末期,日本空军的神风队队员驾驶满载
炸弹的飞机去撞击轰炸目标,企图与之同归于尽。
1)请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为1415531521)。
这个SYN段为报文段1。
2)服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。
同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。
一个SYN将占用一个序号。
3)客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。
这三个报文段完成连接的建立。
这个过程也称为三次握手(three-wayhandshake)。
报文段1
报文段2
报文段3
报文段4
报文段5
报文段6
报文段7
图18-3连接建立与终止的时间系列
发送第一个SYN的一端将执行主动打开(activeopen)。
接收这个SYN并发回下一个SYN
的另一端执行被动打开(passiveopen)(在18.8节我们将介绍双方如何都执行主动打开)。
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。
ISN随时间而变化,
因此每个连接都将具有不同的ISN。
RFC793[Postel1981c]指出ISN可看作是一个32比特的计数器,每4ms加1。
这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释。
如何进行序号选择?
在4.4BSD(和多数的伯克利的实现版)中,系统初始化时初始的发送序号被初始化为1。
这种方法违背了HostRequirementsRFC(在这个代码中的一个注释确认这是一个错误)。
这个变量每0.5秒增加64000,并每隔9.5小时又回到0
(对应这个计数器每8ms加1,而不是每4ms加1)。
另外,每次建立一个连接后,这个变量将增加64000。
报文段3与报文段4之间4.1秒的时间间隔是建立TCP连接到向telnet键入quit命令来中止该连接的时间。
18.2.4连接终止协议
建立一个连接需要三次握手,而终止一个连接要经过4次握手。
这由TCP的半关闭(half-close)造成的。
既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。
这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。
当一端收到一个FIN,它必须通知应用层另一端几经终止了那个方向的数据传送。
发送FIN通常是应用层进行关闭的结果。
收到一个FIN只意味着在这一方向上没有数据流动。
一个TCP连接在收到一个FIN后仍能发送数据。
而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做。
正常关闭过程如图18-3所示。
我们将在18.5节中详细介绍半关闭。
首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭。
通常一方完成主动关闭而另一方完成被动关闭,但我们将在18.9节看到双方如何都执行主动关闭。
图18-3中的报文段4发起终止连接,它由Telnet客户端关闭连接时发出。
这在我们键入quit
命令后发生。
它将导致TCP客户端发送一个FIN,用来关闭从客户到服务器的数据传送。
当服务器收到这个FIN,它发回一个
ACK,确认序号为收到的序号加1(报文段
5)。
和SYN一样,一个FIN将占用一个序号同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。
接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN(报文段6),客户必须发回一个确认,并将确认序号设置为收到序号加1(报文段7)。
图18-4显示了终止一个连接的典型握手
应用程序关闭
客户服务器
向应用程序交付EOF
。
应用程序关闭
顺序。
我们省略了序号。
在这个图中,发送FIN将导致应用程序关闭它们的连接,这些FIN的ACK是由TCP软件自动产生的。
图18-4连接终止期间报文段的正常交换
连接通常是由客户端发起的,这样第一个SYN从客户传到服务器。
每一端都能主动关闭这个连接(即首先发送FIN)。
然而,一般由客户端决定何时终止连接,因为客户进程通常由用户交互控制,用户会键入诸如“quit”一样的命令来终止进程。
在图18-4中,我们能改变上边的标识,将左方定为服务器,右方定为客户,一切仍将像显示的一样工作(例如在14.4节中的第一个例子中就是由daytime服务器关闭连接的)。
18.2.5正常的tcpdump输出
对所有的数值很大的序号进行排序是很麻烦的,因此默认情况下tcpdump只在显示SYN报文段时显示完整的序号,而对其后的序号则显示它们与初始序号的相对偏移值(为了得到图18-1的输出显示必须加上-S选项)。
对应于图18-1的正常tcpdump显示如图18-5所示:
除非我们需要显示完整的序号,否则将在以下的例子中使用这种形式的输出显示。
图18-5连接建立与终止的正常tcpdump输出
18.3连接建立的超时
有很多情况导致无法建立连接。
一种情况是服务器主机没有处于正常状态。
为了模拟这种情况,我们断开服务器主机的电缆线,然后向它发出telnet命令。
图18-6显示了tcpdump的输出。
图18-6建立连接超时的tcpdump输出
在这个输出中有趣的一点是客户间隔多长时间发送一个SYN,试图建立连接。
第2个SYN
与第1个的间隔是5.8秒,而第3个与第2个的间隔是24秒。
作为一个附注,这个例子运行38分钟后客户重新启动。
这对应初始序号为291008001
(约为38×60×64000×2)。
我们曾经介绍过使用典型的伯克利实现版的系统将初始序号初始化为1,然后每隔0.5秒就增加64000。
另外,因为这是系统启动后的第一个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秒(精确到小数点后面两位)。
这是因为BSD版的TCP软件采用一种500ms的定时器。
这种500ms的定时器用于确定本章
中所有的各种各样的TCP超时。
当我们键入telnet命令,将建立一个6秒的定时器(12个时钟滴答(tick)),但它可能在之后的5.5秒~6秒内的任意时刻超时。
图18-7显示了这一发生过程。
尽管定时器初始化为12个时钟滴答,但定时计数器会在设置秒后的第一个0~500ms中的任意时刻减1。
从那以后,定时计数器大约每隔500ms减1,但在第1个500ms内是可变的(我们使用限定词“大约”是因为在TCP每隔500ms获得系统控制的瞬间,系统内核可能会优先处理其他中断)。
11时钟滴答×500ms/滴答=5.5
应用程序在此刻使TCP设置一个6秒(12滴答)的定时器
每滴答500毫秒
TCP重新设置一个
24秒的定时器
图18-7TCP的500ms定时器
当滴答计数器为0时,6秒的定时器便会超时(见图18-7),这个定时器会在以后的24秒
(48个滴答)重新复位。
之后的下一个定时器将更接近24秒,因为当TCP的500ms定时器被内核调用时,它就会被修改一次。
18.3.2服务类型字段
在图18-6中,出现了符号[tos0x10]。
这是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字节。
使用IEEE802.3的封装(参见2.2节),它的MSS可达1452字节。
在本章见到的涉及BSD/386和SVR4的MSS为1024,这是因为许多BSD的实现版本需要
MSS为512的倍数。
其他的系统,如SunOS4.1.3、Solaris2.2和AIX3.2.2,当双方都在一个本地以太网上时都规定MSS为1460。
[Mogul1993]的比较显示了在以太网上1460的MSS在性能上比1024的MSS更好。
如果目的IP地址为“非本地的(nonlocal)”,MSS通常的默认值为536。
而区分地址是本地还是非本地是简单的,如果目的IP地址的网络号与子网号都和我们的相同,则是本地的;如果目的IP地址的网络号与我们的完全不同,则是非本地的;如果目的IP地址的网络号与我们的相同而子网号与我们的不同,则可能是本地的,也可能是非本地的。
大多数TCP实现版都提供了一个配置选项(附录E和图E-1),让系统管理员说明不同的子网是属于本地还是非本地。
这个选项的设置将确定MSS可以选择尽可能的大(达到外出接口的MTU长度)或是默认值
536。
MSS让主机限制另一端发送数据报的长度。
加上主机也能控制它发送数据报的长度,这将使以较小MTU连接到一个网络上的主机避免分段。
考虑我们的主机slip,通过MTU为296的SLIP链路连接到路由器bsdi上。
图18-8显示这些系统和主机sun。
图18-8显示sun与slip间TCP连接的MSS值
从sun向slip发起一个TCP连接,并使用tcpdump来观察报文段。
图18-9显示这个连接的建立(省略了通告窗口大小)。
图18-9tcpdump显示了从sun向slip建立连接的过程
在这个例子中,sun发送的报文段不能超过256字节的数据,因为它收到的MSS选项值为
256(第2行)。
此外,由于slip知道它外出接口的MTU长度为296,即使sun已经通告它的MSS为1460,但为避免将数据分段,它不会发送超过256字节数据的报文段。
系统允许发送的数据长度小于另一端的MSS值。
只有当一端的主机以小于576字节的MTU直接连接到一个网络中,避免这种分段才会有效。
如果两端的主机都连接到以太网上,都采用536的MSS,但中间网络采用296的MTU,也将会出现分段。
使用路径上的MTU发现机制(参见24.2节)是关于这个问题的唯一方法。
18.5TCP的半关闭
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
这就是所谓
的半关闭。
正如我们早些时候提到的只有很少的应用程序使用它。
为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束(FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)”。
如果应用程序不调用close而调用shutdown,且第2个参数值为1,则插口的API支持半关闭。
然而,大多数的应用程序通过调用close终止两个方向的连接。
图18-10显示了一个半关闭的典型例子。
让左方的客户端开始半关闭,当然也可以由另一端开始。
开始的两个报文段和图18-4是相同的:
初始端发出的FIN,接着是另一端对这个FIN的ACK报文段。
但
应用进程
shutdown
客户服务器
向应用进程交付EOF
后面就和图18-4不同,因为接收半关闭的一方仍能发送数据。
我们只显示一个数据报文段和一个ACK报文段,但可能发送了许多数据报文段(将在第19章讨论数据报文段和确认报文段的交换)。
当收到半关闭的一端在完成它的数据传送后,将发送一个FIN关闭这个方向的连接,这将传送一个文件结束符给发起这个半关闭的应用进程。
当对第二个FIN进行确认后,这个连接便彻底关闭了。
应用进程
read
向应用进程交付EOF
图18-10TCP半关闭的例子
应用进程write
应用进程close
为什么要有半关闭?
一个例子是Unix中的rsh
(1)命令,它将完成在另一个系统上执行一个命令。
命令
sun%rshbsdisort将在主机bsdi上执行sort排序命令,rsh命令的标准输入来自文件datafile。
rsh将在它与在另一主机上执行的程序间建立一个TCP连接。
rsh的操作很简单:
它将标准输入
(datafile)复制给TCP连接,并将结果从TCP连接中复制给标准输出(我们的终端)。
图
18-11显示了这个建立过程(牢记TCP连接是全双工的)。
标准输入
主机sun主机bsdi
TCP连接
标准输出终端
图18-11命令:
rshbsdisort在远端主机bsdi上,rshd服务器将执行sort程序,它的标准输入和标准输出都是TCP连接。
第14章的[Stevens1990]详细介绍了有关Unix进程的结构,但这儿涉及的是使用TCP连接以及需要使用TCP的半关闭。
sort程序只有读取到所有输入数据后才能产生输出。
所有的原始数据通过TCP连接从
rsh客户端传送到sort服务器进行排序。
当输入(datafile)到达文件尾时,rsh客户端
执行这个TCP连接的半关闭。
接着sort服务器在它的标准输入(这个TCP连接)上收到一个文件结束符,对数据进行排序,并将结果写在它的标准输出上(TCP连接)。
rsh客户端继续接收来自TCP连接另一端的数据,并将排序的文件复制到它的标准输出上。
没有半关闭,需要其他的一些技术让客户通知服务器,客户端已经完成了它的数据传送,但仍要接收来自服务器的数据。
使用两个TCP连接也可作为一个选择,但使用半关闭的单连接更好。
18.6TCP的状态变迁图
我们已经介绍了许多有关发起和终止TCP连接的规则。
这些规则都能从图18-12所示的状态变迁图中得出。
开始
应用进程:
被动打开
发送:
无
被动打开
SYN收到
收:
SYN
发:
SYN,ACF
同时打开
主动打开
应用进程:
关闭
或超时
应用进程:
关闭
发:
FIN
收:
ACK
发送:
无
收:
FIN
发:
ACK
数据传送状态同时关闭
收:
ACK
发送无
收:
FIN
发:
ACK
应用进程:
关闭发:
FIN
被动关闭
收:
ACK
发送:
无
收:
FIN
发:
ACK
主动关闭
2MSL超时
说明客户的正常状态变迁说明服务器的正常状态变迁
应用进程:
说明当应用执行某种操作时发生的状态变迁
收:
说明当收到TCP报文段时状态的变迁
发:
说明为了进行某个状态变迁要发送的TCP报文段
图18-12TCP的状态变迁图
在这个图中要注意的第一点是一个状态变迁的子集是“典型的”。
我们用粗的实线箭头表示正常的客户端状态变迁,用粗的虚线箭头表示正常的服务器状态变迁。
第二点是两个导致进入ESTABLISH-
ED状态的变迁对应打开一个连接,而两个导致从ESTABLISHED状态离开的变迁对应关闭一个连接。
ESTABLISHED状态是连接双方能够进行双向数据传递的状态。
以后的章节将介绍这个状态。
将图中左下角4个状态放在一个虚线框内,并标为“主动关闭”。
其他两个状态(CLOSE_WAIT和LAST_ACK
主动打开
客户服务器
(被动打开)
也用虚线框住,并标为“被动关闭”。
在这个图中11个状态的名称
(CLOSED,LISTEN,SYN_SENT等是有意与netstat命令显示的状态名称一致。
netstat对状态的命名几乎与在RFC793中的最初描述一致
CLOSED状态不是一个真正的状态而是这个状态图的假想起点和终点。
从LISTEN到SYN_SENT的变迁是
主动关闭
)
)
。
,
(被动
关闭)
图18-13TCP正常连接建立和终止所对应的状态
正确的,但伯克利版的TCP软件并不支持它。
只有当SYN_RCVD状态是从LISTEN状态(正常情况)进入,而不是从SYN_SENT状态
(同时打开)进入时,从SYN_RCVD回到LISTEN的状态变迁才是有效的。
这意味着如果我们执行被动关闭(进入LISTEN),收到一个SYN,发送一个带ACK的SYN(进入SYN_RCVD),然后收到一个RST,而不是一个ACK,便又回到LISTEN状态并等待另一个连接请求的到来。
图18-13显示了在正常的TCP连接的建立与终止过程中,客户与服务器所经历的不同状态。
它是图18-3的再现,不同的是仅显示了一些状态。
假定在图18-13中左边的客户执行主动打开,而右边的服务器执行被动打开。
尽管图中显示出由客户端执行主动关闭,但和早前我们提到的一样,另一端也能执行主动关闭。
可以使用图18-12的状态图来跟踪图18-13的