TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须现在双方之间建立一条连接。
连接的建立与终止
在第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报文段。这通常是为了避免分段。
建立连接协议
三次握手过程:
- 1. 请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为1415531521)。这个SYN段为报文段1。
- 2. 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
- 3. 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。
发送第一个SYN的一端将执行主动打开(active open)。接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)。
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。
连接终止协议
4次挥手过程:
原因: 这由TCP的半关闭(halfclose)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端几经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。
收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做。
首先进行关闭的一方(即发送第一个FIN)将执行关闭,而另一方(收到这个FIN)执行被动关闭.通常一方完成主动关闭而另一方完成被动关闭
当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1.和SYN一样,一个FIN将占用一个序号.同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符.接着这个服务程序就关闭它的连接,导致它的TCP端发送一个FIN,客户端必须发回一个确认,并将确认序号设置为收到序号加1.
连接建立的超时
有很多情况导致无法建立连接。一种情况是服务器主机没有处于正常状态。为了模拟这种情况,我们断开服务器主机的电缆线,然后向它发出telnet命令。图18-6显示了tcpdump的输出。
在这个输出中有趣的一点是客户间隔多长时间发送一个SYN,试图建立连接。第2个SYN与第1个的间隔是5.8秒,而第3个与第2个的间隔是24秒。
最大报文端长度
最大报文段长度(MSS)表示TCP传往另一端的最大块数据的长度.当一个连接建立时,连接的双方都要通告各自的MSS.我们已经见过MSS都是1024. 这导致IP数据报通常是40字节:20字节的TCP首部和20字节的IP首部.
当建立一个连接时,每一方都有用于通告它期望接受的MSS选项(MSS选项只能出现在SYN报文段中).如果一方不接受来自另一方的MSS值.则MSS就定位默认值536字节(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节IP数据报)
TCP的半关闭
半关闭: TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束(FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)”。
如果应用程序不调用close而调用shutdown,且第2个参数值为1,则插口的API支持半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。
图18-10显示了一个半关闭的典型例子。
让左方的客户端开始半关闭,当然也可以由另一端开始。
初始端发出的FIN,接着是另一端对这个FIN的ACK报文段
接收半关闭的一方仍能发送数据。我们只显示一个数据报文段和一个ACK报文段,但可能发送了许多数据报文段
当收到半关闭的一端在完成它的数据传送后,将发送一个FIN关闭这个方向的连接,这将传送一个文件结束符给发起这个半关闭的应用进程
当对第二个FIN进行确认后,这个连接便彻底关闭了。
为什么要有半关闭?
没有半关闭,需要其他的一些技术让客户通知服务器,客户端已经完成了它的数据传送,但仍要接受来自服务端的数据.使用两个TCP连接也可作为一个选择,但使用半关闭的单连接更好.
TCP的状态变迁图
2MSL等待状态
TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口)不能再被使用.这个连接只能在2MSL结束后才能再被使用.
客户执行主动关闭并进入TIME_WAIT是正常的。服务器通常执行被动关闭,不会进入TIME_WAIT状态。这暗示如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口。这不会带来什么问题,因为客户使用本地端口,而并不关心这个端口号是什么。
然而,对于服务器,情况就有所不同,因为服务器使用熟知端口。如果我们终止一个已经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的这个熟知端口赋值给它的端点,因为那个端口是处于2MSL连接的一部分。在重新启动服务器程序前,它需要在1~4分钟.
FIN_WAIT_2状态
在FIN_WAIT_2状态我们已经发出了FIN,并且另一端也已对它进行确认。除非我们在实行半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并向我们发一个FIN来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从FIN_WAIT_2状态进入TIME_WAIT状态。
这意味着我们这端可能永远保持这个状态。另一端也将处于CLOSE_WAIT状态,并一直保持这个状态直到应用层决定进行关闭。
许多伯克利实现采用如下方式来防止这种在FIN_WAIT_2状态的无限等待。如果执行主动关闭的应用层将进行全关闭,而不是半关闭来说明它还想接收数据,就设置一个定时器。如果这个连接空闲10分钟75秒,TCP将进入CLOSED状态。在实现代码的注释中确认这个实现代码违背协议的规范。
复位报文段
TCP首部中的RST比特是用于"复位"的.一般说来,无论何时一个报文段发往基准的连接出现错误,TCP都会出现一个复位报文段.
出现情况:
-
到不存在的端口的连接请求
产生复位的一种常见情况是当连接请求到达时,目的端口没有进程正在听.当一个数据报到达目的端口时,该端口没在使用,TCP使用复位
- 异常终止一个连接
终止一个连接的正常方式是一方发送FIN.有时这也称为有序释放(orderly release),因为在所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失.但也有可能发送一个复位报文段而不是FIN来中途释放一个连接.这被称为异常释放
异常终止一个连接对应用程序来说有两个优点:(1)丢弃任何待发数据并立即发送复位报文段;(2)RST的接收方会区分另一端执行的是异常关闭还是正常关闭.应用程序使用的API必须提供产生异常关闭而不是正常关闭的手段.
半打开连接
如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的TCP连接称为半打开(Half-Open)的.任何一端的主机异常都可能导致发送这种情况.只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。
半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后再关机。这可能发生在使用PC机作为Telnet的客户主机上,例如,用户在一天工作结束时关闭PC机的电源。当关闭PC机电源时,如果已不再有要向服务器发送的数据,服务器将永远不知道客户程序已经消失了。当用户在第二天到来时,打开PC机,并启动新的Telnet客户程序,在服务器主机上会启动一个新的服务器程序。这样会导致服务器主机中产生许多半打开的TCP连接
同时打开
TCP特意设计可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接;
一个同时打开的连接需要交换4个报文段,比正常的三次握手多一个.此外,我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器.
同时关闭
当应用层发出关闭命令时,两端均从ESTABLISHED变为FIN_WAIT_1。这将导致双方各发送一个FIN,两个FIN经过网络传送后分别到达另一端。收到FIN后,状态由FIN_WAIT_1变迁到CLOSING,并发送最后的ACK。当收到最后的ACK时,状态变化为TIME_WAIT。
同时关闭与正常关闭使用的段交换数目相同。