TCP/IP详解 卷1:协议 学习笔记 第十八章 TCP连接的建立与终止

telnet程序(基于TELNET协议的远程登录客户端程序)使用TCP连接:
在这里插入图片描述
上图中使用telnet命令在丢弃(discard)服务对应的端口上与主机bsdi建立一条TCP连接,这个服务不需要服务器发起任何数据交换。
在这里插入图片描述
以上的7个TCP报文段仅包含TCP首部,没有任何数据。

以上每行开始都按如下格式显示:源 > 目的 : 标志,这里的标志代表TCP首部中6个标志比特中的4个:
在这里插入图片描述
上图中包含的4个标志比特中的多个可能同时出现在一个报文段,但tcpdump通常只显示一个。TCP首部中的其他两个标志比特ACK和URG,tcpdump将作特殊显示。

有一种报文段称作Kamikaze分组,这样的报文段中,最大数量的标志比特同时置1(SYN、URG、PSH、FIN、1字节的数据),这样的报文段也叫nastygram、圣诞树分组(每个小的标志位由一个不同色彩的电灯泡代表,所有的都是亮的,数据包像一个圣诞树一样被点亮)、灯测试报文段。

第一行中,字段1415531521:1415531521(0)表示分组的序号是1415531521,隐含的结尾序号也是1415531521,报文段中的数据字节数为0。隐含的结尾序号字段只有当报文段中至少包含一个数据字节或SYN、FIN、RST其中一个标志位置1时才显示。

第二行中,ack字段值为1415531522表示确认序号,tcpdump只有在首部中的ACK标志比特置1时才显示它。

每行中的win字段表示发端通告的窗口大小。

字段<mss 1024>表示发端不接收超过这个长度的TCP报文段,通常是为了避免分段。

上例建立与终止连接的时间序列:
在这里插入图片描述
建立连接过程,这个过程也称三次握手:
1.客户发送一个SYN段指明客户打算连接的服务器端口,以及初始序号(ISN,上例中是1415531521)。
2.服务器发回包含服务器初始序号的SYN报文段作为应答,同时将确认序号设置为客户的ISN加1表示对客户的SYN报文段的确认,一个SYN占用一个序号。
3.客户将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认。

发送第一个SYN的一端执行主动打开,接收到这个SYN并发回下一个SYN的一端执行被动打开。

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

4.4 BSD和多数伯克利实现版中,系统初始化时将初始的发送序号初始化为1,这违背了RFC,这个变量每0.5秒增加64000,每隔9.5小时又回到0(每8ms加1),每次建立连接,这个变量将增加64000。

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

TCP连接是全双工(数据在两个方向上能同时传递)的,因此每个方向必须单独进行关闭,由于TCP的半关闭,终止一个连接需要四次握手。一方完成它的发送任务后就能发送一个FIN终止这个方向连接,当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送,发送FIN通常是应用层进行关闭的结果。收到FIN的一端仍能发送数据。

首先进行关闭的一方(即发送第一个FIN的一方)将执行主动关闭,而另一方(收到这个FIN的一方)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭,但双方也能都执行主动关闭。

上例中,当我们键入quit命令后,TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,FIN将占用一个序号。同时TCP服务器向丢弃服务器进程传送一个文件结束符。之后服务器开始关闭它的TCP连接,即服务器向客户也发一个FIN,客户必须发回一个确认,并将确认序号设为收到序号加1:
在这里插入图片描述
连接通常是由客户发起的,而每一端都能主动关闭这个连接(即首先发FIN)。

以上是tcpdump程序加了-S选项的输出,不加-S选项时,只在SYN报文段上显示完整报文序号,其他的报文段只显示它们与初始序号的相对偏移值:
在这里插入图片描述
连接超时:
在这里插入图片描述
客户发送的前两个SYN的时间间隔是5.8秒,而后两个SYN的发送间隔为24秒。

上图中tos 0x10指出IP数据报首部的服务类型(TOS)字段值,BSD/386的Telnet客户进程将这个字段设为最小时延。

上图显示的是系统启动后的第一个TCP连接,因此客户的端口号为1024。

客户端放弃建立连接进行SYN重传的时间:
在这里插入图片描述
时间差值是76秒,大多数伯克利系统将建立一个新连接的最长时间限制为75秒。

上例中,客户发送前两个SYN的发送间隔为5.8秒,接近6秒,但不准确,而后两个SYN的发送间隔几乎是准确的24秒,经过多次测试,第一个时间间隔在5.59秒~5.93秒之间变化,而第二次超时时间几乎总是24秒,这是由于BSD版的TCP软件采用一种500ms的定时器,用于确定各种各样的TCP超时,当输入telnet命令时,将建立一个6秒的定时器,但它可能在之后的5.5秒到6秒内的任意时刻超时:
在这里插入图片描述
如上图,虽然定时器初始化为12个时钟滴答,但定时器会在设置后的第一个0~500ms中的任意时刻减1,之后定时器大约每隔500ms减1,使用大约是由于在TCP每隔500ms获得系统控制的瞬间,内核可能优先处理其他中断。

最大报文段长度(MSS)表示发端在该连接上可以接受的最大TCP分节大小,建立连接时,双方都要通告各自的MSS,MSS选项只能出现在SYN报文段中,如果一方不接收另一方的MSS值,则MSS就定为默认值536字节,这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节的IP数据报(IP协议规定在互联网中所有的主机和路由器必须能够接受长度不超过576字节的数据报)。

一般,MSS越大越好,报文段越大,每个报文段发送的数据越多,相对IP和TCP首部网络利用率更高。

TCP发送一个SYN时,将MSS设为外出接口上MTU长度减去固定的IP首部和TCP首部长度。对于以太网,MSS值可达1460字节,使用IEEE 802.3封装的MSS可达1452字节。

本章中的BSD/386和SVR 4的MSS为1024字节,这是因为许多BSD的实现版本需要MSS为512字节的倍数,其他系统如SunOS 4.1.3、Solaris 2.2和AIX 3.2.2,当双方都在一个本地以太网上时MSS为1460字节。

如果目的IP为非本地的,通常MSS的默认值为536,如果目的IP地址的网络号与子网号都和我们的相同,则是本地的;如果目的IP地址的网络号与我们的不同,则是非本地的;如果目的IP地址的网络号与我们的相同而子网号与我们的不同,则可能是本地的,也可能是非本地的。大多TCP实现都提供一个配置选项,让系统管理员说明不同的子网是本地的还是非本地的,这个选项的设置可以让MSS尽可能地选择大的(达到外出接口的MTU长度)。

下图显示,主机slip通过MTU为296字节的SLIP链路连接到路由器bsdi上:
在这里插入图片描述
sun向slip发起TCP连接:
在这里插入图片描述
上例中,sun发送的TCP报文段中数据部分不能超过256字节。此外,由于slip知道它外出接口的MTU长为296字节,即使sun通告它的MSS为1460字节,但为了避免IP层将数据分段,它也不会发送超过256字节数据的TCP报文段。即取两端互相通告的MSS

MTU是网络层与链路层之间的接口属性,因此当IP数据报长度超过MTU时,由IP层对报文进行分段。

如果两端的主机都连在同一以太网上,且都采用536字节的MSS,但由于中间网络采用296字节的MTU,也将会出现分段,使用路径上的MTU发现机制是解决这个问题的唯一方法。

TCP的半关闭指连接的一端在结束它的发送后还能接收来自另一端数据的能力,只有很少的应用使用它。

应用调用shutdown而非close可实现半关闭。

在这里插入图片描述
如上图,左方的客户端开始半关闭(也可以由另一端开始),客户发出FIN,接着服务器对这个FIN发出ACK报文段,之后服务器端仍然可以发送数据,客户仍然可以读数据并发回对数据的ack。服务端完成数据传送后,发送一个FIN关闭这个方向上的连接,当对第二个FIN进行确认后,这个连接就彻底关闭了。

rsh命令(在另一系统上执行一个命令,并输出命令的标准输出)使用了TCP的半关闭,如以下命令,在主机bsdi上执行sort命令,rsh命令的标准输入来自文件datafile:

rsh bsdi sort < datafile

rsh命令将在它与另一主机上执行的rshd程序间建立一个TCP连接:
在这里插入图片描述
如上图,rsh命令将标准输入复制给TCP连接,并将结果从TCP连接中复制给标准输出(即我们的终端),在远端主机bsdi上,rshd服务器将执行sort程序,它的标准输入和标准输出都是TCP连接,sort程序从TCP连接中读取原始数据并对数据进行排序,当输入(即客户端的datafile)到达文件尾时,rsh客户端执行这个TCP连接的半关闭,接着rshd服务器在它的标准输入上收到一个文件结束符,之后rshd服务器使用sort对数据排序,并将排序的文件复制到它的标准输出上。如果没有半关闭,需要其他技术让客户通知服务器客户已经完成了它的数据传送,但仍要接收来自服务器的数据。
在这里插入图片描述
ESTABLISHED状态是连接双方能双向传递数据的状态。有两个导致进入ESTABLISHED状态的变迁对应打开一个连接,两个导致从ESTABLISHED状态离开的变迁对应关闭一个连接。

上图中的状态名称与netstat命令显示的状态命名一致,而netstat命令对状态的命名几乎与在RFC 793中的最初描述一致。

从LISTEN到SYN_SENT的状态是正确的,但伯克利版的TCP软件不支持它。

只有当SYN_RCVD状态是从LISTEN状态进入(正常进入),而不是从SYN_SENT状态进入时(同时打开),从SYN_RCVD回到LISTEN的状态变迁才是有效的,这意味着如果服务器在LISTEN状态收到一个SYN,之后进行第二次握手正常发送一个带ACK的SYN(进入SYN_RCVD),但第三次握手收到的是RST而非ACK时,又回到了LISTEN状态并等待另一个连接请求的到来。
在这里插入图片描述
TIME_WAIT状态也称2MSL状态,每个具体的TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间,这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报有生存时间TTL字段。

RFC 793指出MSL为2分钟,而实现中常用30秒、1分钟或2分钟。

当TCP执行一个主动关闭,并发送最后一个ACK后,该连接必须在TIME_WAIT状态停留2倍的MSL,这样当另一端FIN超时重发时,可让TCP再次发送最后的ACK以防这个ACK丢失。

这种2MSL等待的另一个结果是在等待期间,这个连接的socket不能再被使用(客户的IP和端口号,服务器的IP和端口号,这一对连接不能再使用)。大多TCP实现强加了更为严格的限制,在2MSL等待期间,socket中使用的本地端口默认不能再被使用。

连接处于2MSL等待时,任何迟到的报文段将被丢弃,处于2MSL等待中的、由这一socket对定义的连接在这段时间内不能被再使用。否则当要建立一个新有效的连接时,来自该连接的一个较早incarnation(替身,化身,一个连接由一个socket对定义,一个连接的另一实例称为该连接的替身)的迟到报文段作为新连接的一部分会被曲解。

客户执行主动关闭并进入TIME_WAIT状态是正常的,如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口,这不会带来什么问题,因为客户不关心使用的本地端口号,可以换一个。

而对于服务器,由于服务器使用熟知端口,因此重新启动这个服务器程序时,服务器不能使用熟知端口,需要等待2MSL时间,一个例子如下:
在这里插入图片描述
如果我们一直试图重新启动服务器程序,并测量它直到成功所需的时间,就能确定出2MSL的值。对于Sun OS 4.1.3、SVR 4、BSD/386、AIX 3.2.2,需要1分钟才能重启服务器程序(MSL设为30秒);对于Solaris 2.2,需要4分钟才能重启服务器程序(MSL设为2分钟)。

如果一个客户程序试图申请一个处于2MSL等待的端口(客户程序通常不会这么做),会出现与上图一样的差错:
在这里插入图片描述
如上图,第一次执行客户程序时用-v选项来查看它使用的本地端口为1162,第二次执行客户程序时用-b选项来选择端口为1162为它的本地端口,可见客户如所预料的一样无法再这么做,因为这个端口是一个还处于2MSL等待的连接的一部分。

许多实现中允许一个进程重新使用仍处于2MSL等待的端口(通常是设置SO_REUSEADDR),但TCP不能允许一个新的连接建立在相同的socket对上:
在这里插入图片描述
第一次运行sock程序时,将它作为服务器程序,端口号为6666,并从主机bsdi上的一个客户程序与它连接,这个客户程序使用的端口为1098,之后终止服务器程序,它将执行主动关闭,这将导致四元组140.252.13.33(本地IP)、6666(本地端口)、140.252.13.35(另一端IP)、1098(另一端端口号)在服务器主机进入2MSL等待。

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

第三次运行sock程序使用了-A选项,它将使用SO_REUSEADDR标志,这让sock程序能将它的本地端口设为6666,当我们试图进行主动打开时,又出现了一个差错,即使它能将本地端口设为6666,但它仍不能和主机bsdi在端口1098上进行连接,因为这个连接的socket对仍处于2MSL状态。

但如果我们从bsdi上建立这个连接,首先在sun上在还处于2MSL等待连接时使用-A选项重新启动服务器:
在这里插入图片描述
之后在bsdi上在2MSL结束前启动客户程序:
在这里插入图片描述
在这里插入图片描述
它连接成功了,这违反了TCP规范,但被大多伯克利实现所支持。这些实现支持一个新的连接请求到达仍处于TIME_WAIT状态的连接,只要新的序号大于该连接前一个替身的最后序号,上例中,新替身的ISN被设置为前一个替身最后序号与128000的和。RFC 1185指出这项技术可能存在缺陷。

以上具体实现的特性让客户程序和服务器程序能连续地重用每一端的相同端口号,但这只有在服务器执行主动关闭时才有效。

对于来自某个连接的较早替身的迟到报文段,2MSL等待可防止将它解释为使用相同socket对的新连接的一部分。如果处于2MSL等待端口的主机出现故障,之后TCP又重新启动,并立即使用故障前的TCP连接,这样还是会发生新连接收到旧数据的情况,RFC 793指出TCP在重启后的MSL秒内不能建立任何连接,这称为平静时间,但只有极少实现遵守这一原则,因为大多数主机重启动的时间都比MSL时间要长。

FIN_WAIT_2状态时,我们已经发出FIN并收到了另一端的ACK,之后直到我们收到另一端的FIN才会从FIN_WAIT_2状态进入TIME_WAIT状态,这意味着我们可能永远保持这个状态,另一端也将永远处于CLOSE_WAIT状态。许多伯克利实现采用以下方式防止永远处于FIN_WAIT_2状态,如果主动关闭的应用层使用全关闭而非半关闭来说明它还想接收数据,就设置一个定时器,如果这个链接空闲11分钟15秒,TCP将进入CLOSED状态,在实现代码的注释中确认这个实现代码违背协议的规范。

TCP首部中的RST比特是用于复位的,无论何时一个报文段发往的连接出现错误(此处的连接是指由目的IP地址和目的端口号以及源IP地址和源端口号指明的连接),TCP都会发出一个复位报文段。

产生复位的一种常见情况是当连接请求到达时,目的端口没有进程正在听,UDP遇到此种情况产生一个ICMP端口不可达的信息,而TCP使用复位。

使用TCP连接一个没有被进程监听的端口:
在这里插入图片描述
报文交换情况:
在这里插入图片描述
上图中,复位报文段中的序号被置为0,确认序号被置为对端发来的ISN加上数据字节数,尽管到达的报文段中没有数据,但SYN比特从逻辑上占用1字节的序号空间。

正常终止一个TCP的方式是一方发送FIN,有时这也称为有序释放,因为在所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。但也有可能发送一个复位报文段而非FIN来中途释放一个连接,有时称其为异常释放。

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

socket API通过SO_LINGER选项提供异常关闭的能力,当我们使用sock命令的-L选项并将停留时间设为0,将导致连接关闭时发送RST而非正常的FIN:
在这里插入图片描述
以下是上图tcpdump的输出:
在这里插入图片描述
前三行是建立连接的正常过程。第四行发送我们键入的数据行(12个字符和1个UNIX换行符)。第五行是收到的对数据的确认。

第六行对应为终止客户程序而键入的文件描述符,导致发送了一个RST。RST报文段中包含一个序号和一个确认序号。RST报文段不会导致另一端产生任何响应,收到RST的一方将终止该连接,并通知应用层连接复位。

我们在服务器上收到以下信息:
在这里插入图片描述
如果一方已经关闭或异常终止连接而另一方却还不知道,这样的连接称为半打开的。任何一端主机异常都可能导致发生这种情况,只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。

半打开连接的一个常见原因是当客户主机突然掉电,可能发生在使用PC机作为telnet的客户主机上,如用户在结束一天工作后关闭PC机电源,此时客户应用程序还没发送结束连接的FIN,服务器不会知道客户程序已经消失了。当用户第二天到来时,打开PC机,并启动新的telnet客户程序,在服务器主机上会启动一个新的服务器程序,这会导致服务器主机中产生许多半打开的TCP连接(但使用TCP的keepalive选项可以使TCP的一端发现另一端已经消失)。

建立一个半打开的连接:在bsdi上运行telnet客户程序,通过它和svr4上的discard服务器建立连接,之后我们键入一行字符,通过tcpdump进行观察,接着断开服务器主机与以太网的电缆,并重启服务器主机,在重启前断开以太网电缆是为了防止它向打开的连接发送FIN,某些TCP实现在关机时会这么做,服务器主机重启后,重新接上电缆,并从客户向服务器发送另一行字符,由于TCP已经重新启动,服务器将丢失之前连接的所有信息,而TCP的处理原则是接收方以复位作为应答:
在这里插入图片描述
以下是tcpdump的输出:
在这里插入图片描述
前三行是正常的连接建立过程,第四行向discard服务器发送字符行,第五行是服务器发送的确认报文段。然后断开svr4的以太网电缆,重新启动svr4,再重新接上电缆,接着客户端输入下一行,键入回车后,这一行发往服务器,这导致服务器产生一个响应,但服务器经过重新启动后,ARP高速缓存为空,因此需要一个ARP请求和应答(第七八行)。第九行表示RST被发送出去,客户收到复位报文段后显示连接已被另一端主机中止。

上图中,将hi there当作10个字符发送,而不是9个,这是因为telnet程序将新行符号当作\r\n,而自己写的程序sock将新行符号当作\n。

两个应用同时彼此执行主动打开的情况是可能的,每一方都会发送一个SYN,这需要每一方使用一个对方知道的端口作为本地端口。这称为同时打开,同时打开时TCP仅建立一条连接而不是两条连接(其他协议族,如OSI运输层,在这种情况下将建立两条连接)。

在这里插入图片描述
如上图,两端几乎同时发送SYN,并进入SYN_SENT状态,每一端收到SYN时,状态变为SYN_RCVD,同时它们都再发SYN并对收到的SYN进行确认,当双方都收到SYN及相应ACK时,状态都变为ESTABLISHED。

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

许多伯克利版的TCP实现都不能正确地支持同时打开。

TCP允许双方同时执行主动关闭,称为同时关闭:
在这里插入图片描述
两端同时从ESTABLISHED变为FIN_WAIT_1,这两个FIN到达另一端后,两端进入CLOSING,并发送最后的ACK,当收到最后的ACK后,状态变为TIME_WAIT。

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

一些TCP首部选项字段的格式:
在这里插入图片描述
每个选项的开始是1字节的kind字段,用来说明选项的类型。有的选项在kind字段后跟着len字段,它说明的长度指总长度,包括kind字段和len字段。

设置无操作选项的原因在于发方可以用其作为填充字段将TCP首部填充为4字节的倍数。

大多服务器接收到新的连接请求时,服务器接受这个请求,并创建一个新进程处理这个新客户请求,UNIX下通常使用fork函数创建新的进程,如果系统支持,也可以使用线程。

以下是netstat命令查看一个telnet服务器在没有连接时的显示:
在这里插入图片描述
netstat命令的-a标志显示网络中所有端口,而非只是处于ESTABLISHED的端口;-n标志以点分十进制形式显示IP地址,而不是通过DNS将地址转换为主机名,同时显示端口号(23)而非服务名(telnet);-f inet选项仅显示使用TCP或UDP的主机。

上图显示的本地地址为*.23,星号又称通配符,表示传入的连接请求将被任何一个本地接口所接收,23是telnet的熟知端口号。

远端地址显示为*.*,表示还不知道远端IP地址和端口号,因为本端还处于LISTEN状态。

现在启动一个telnet客户程序来连接上述服务器,以下是netstat程序的输出行:
在这里插入图片描述
第一行表示处于ESTABLISHED的连接,显示了四元组,其中本地IP为该连接请求到达的接口,且端口还是23。

处于LISTEN状态的服务器进程仍存在,这个服务器进程是当前telnet服务器用于接收其他连接请求的。

再启动一个telnet客户进程与上述服务器连接:
在这里插入图片描述
现在有两条从相同主机到相同服务器的处于ESTABLISHED的连接,它们的服务器端口号均为23,但它们的远端端口号不同,不会造成冲突。这重申了TCP使用四元组来处理传入的多个连接请求,TCP仅通过目的端口号无法确定哪个进程接收了一个连接请求,另外,上述三个使用端口23的进程中,只有处于LISTEN状态的进程能接收新的连接请求,处于ESTABLISHED状态的进程不能接收SYN报文段,而处于LISTEN状态的进程不能接收数据报文段。

之后再从另一个主机上启动第三个telnet客户进程,这个主机通过SLIP链路与主机sun相连,而不是以太网接口:
在这里插入图片描述
现在第一个ESTABLISHED状态的连接的本地IP对应多地址主机sun的SLIP链路接口地址。

为sock程序指明一个IP地址(或主机名)和端口,作为服务器启动:
在这里插入图片描述
这个服务器程序接收的连接仅局限于SLIP接口(140.252.1.29)的,如果从以太网中的主机与这个服务器进行连接,请求会被TCP拒绝,方式是对连接请求SYN响应一个RST:
在这里插入图片描述
这个请求不会到达服务器的应用程序,因为内核中的TCP模块会根据应用程序中指定的本地IP拒绝此请求。

UDP服务器能指定远端IP和远端端口。

RFC 793中显示的接口函数允许一个服务器在执行被动打开时指明远端socket(即等待一个特定客户执行主动打开),但大多数API不支持TCP这么做。
在这里插入图片描述
一个并发服务器通过调用一个新进程来处理每个客户请求,因此处于被动连接请求的服务器应该始终准备处理下一个连接请求,但可能当服务器创建一个新进程时,或操作系统正忙于处理优先级更高的进程时,同时到达了多个连接请求,伯克利的TCP实现采用以下规则处理上述情形:
1.等待连接请求的一端有一个固定长度的连接队列,该队列中的连接已完成三次握手,但还没有被应用层接受。
2.应用层会指明该队列的最大长度,这个值通常称为积压值(backlog),它的取值范围是0~5(包括0和5)之间的整数,大多程序将这个值设为5。
3.当一个连接请求到达时,TCP根据当前连接队列中的连接数来确定是否接收这个连接。下图是积压值与各系统最大排队的连接数之间的关系:
在这里插入图片描述
4.对于新的连接请求,该TCP监听端的连接队列还有空间时,TCP将对SYN进行确认并完成连接的建立,但应用层只有当三次握手中的第三个报文段收到后才会知道这个新连接。当客户进程主动打开成功但服务器的应用层还不知道这个新连接时,客户可能会认为服务器进程已经准备好接收数据了,如果客户此时发送了数据,服务器的TCP会将接收的数据放入缓冲队列。
5.如果连接队列中已没有空间,TCP不会理会收到的SYN,不发回任何报文段,客户的主动打开最终将超时。不理会的原因在于这是一个软错误而非硬错误,通常队列已满是应用或操作系统忙造成的,这个条件可能在很短的时间内改变,如果服务器以复位响应,客户进程的主动打开将不会进行后续的尝试。

我们通过自定义的sock程序验证以上过程:
在这里插入图片描述
-q1选项含义为将服务器端的积压值置为1,此时传统BSD系统中的队列允许接收两个连接请求。-O30选项会使程序暂停30秒,在这30秒内,可启动其他客户进程填充这个队列。

之后在主机sun上启动4个客户进程,以下是tcpdump的输出:
在这里插入图片描述
如上图,端口号为1090的客户连接请求被TCP接受(报文段1~3),端口号为1091的客户连接请求也被TCP接受(报文段4~6),此时,服务器仍处于休眠状态,并未接受任何连接,目前的一切工作由内核中的TCP模块完成,对这两个客户来说,主动打开已经成功完成。

接着在端口1092和1093上启动另外两个客户进程,由于服务器的连接队列已满,TCP将不理会这两个客户的SYN,这两个客户在报文段9、10、11、12、15重发它们的SYN,在第12个报文段,连接请求被接受,这是因为服务器过了30秒的休眠时间,它将已接受的两个连接从队列中移出,这个请求被接受的时间为28.19秒,小于30秒的原因是启动服务器和启动第一个客户进程之间有时间差。

我们期望存放已接受的连接的队列按先进先出顺序传递给应用层,但许多伯克利的TCP实现都按后进先出的顺序传递,现在可能已经改正了。

上例中隐含着一个大多数TCP/IP实现都能见到的地方,就是如果服务器的连接队列未满,TCP将接受传入的连接请求,但并不让应用层参与接受连接的过程,应用不了解该连接的源IP和源端口,这不是TCP要求的,TLI的API实现(UNIX System V的TCP/IP实现的接口,现已被淘汰)向应用提供了查看连接是否到来的方法,并允许应用选择是否接受连接。其他运输层的实现(如OSI的运输层)可能将连接请求的到达与接受分开,但TCP不是这样,而是到达的连接请求如果能接受则直接接受。上述行为意味着TCP服务器无法使客户进程的主动打开失效,当一个新的客户连接传送给服务器应用时,TCP的三次握手已经结束了,客户的主动打开已经成功。

Solaris 2.2提供了一个选项使TCP只有在应用程序说可以接受时才接受传入的连接请求。

4.4BSD中,ISN的初始值为1,且每隔0.5秒增加64000,这意味着每次执行一个主动打开,ISN的最低位通常总是1,但上例中并不是这样,这是因为计数器会在系统引导大约9.5小时后从4294912000环绕到8704,再过9.5小时,它环绕到17408,如此继续下去。

TCP连接一端只有主动断开连接(先发FIN)或同时关闭才能进入2MSL等待状态,因此一个服务器如果启动后没有建立连接就在原来端口上重启,能立即重新启动。

我们可以将客户程序以相同端口运行两次连接到同一daytime服务器上,并且两次运行的间隔在2MSL内,这是因为daytime服务器在发送完时间和日期后对连接做了主动关闭,因此服务器端的socket处于TIME_WAIT状态,而不是客户端。当服务器端socket处于TIME_WAIT时,如果客户来连接,在Linux 4.2上,如果发送的SYN的序列号比服务器期望接收的要大(另外,如果打开了TCP时间戳选项,则发送的SYN的时间戳也要比服务器期望接收的要大。序列号和时间戳都可能发生回绕),则服务器会从TIME_WAIT状态进入SYN_RECV状态,此时可正常进行三次挥手建立连接,此时第二次握手服务器的序列号会重新生成。

如下过程中:
在这里插入图片描述
在第三行,第三次握手客户发出的ACK和客户数据一起发出,这是允许的,尽管大多实现没有这么做,这样使得服务器在第五行同时确认客户数据和发送它的FIN,比正常情况下少了两个报文段。

四次挥手时,首先一方发送FIN,另一方不将ACK和它的FIN一起发回的原因是:(1)服务器对FIN的确认一般不会被延迟,在FIN到达后TCP模块立即发回确认,而应用还需一些时间接收EOF,之后告诉服务器的TCP关闭连接;(2)服务器收到客户的FIN后,处于半打开状态,服务器还能向客户发送数据。

实际上可能会发生三次挥手:
1.主动关闭方用户进程发起主动关闭(调用shutdown关闭写端,此时还能读)。
2.主动关闭方TCP发送FIN给被动关闭方。
3.被动关闭方TCP收到FIN,询问服务器进程是否还有数据要发送。
4.1.如果没有了,告知TCP执行被动关闭。
4.2.如果有,需要先发送数据,然后再执行被动关闭。
在第3步到第4.1步之间,如果开启了TCP延迟确认机制,就会发生三次挥手。

处于TIME_WAIT状态的主机收到使其进入此状态的重复的FIN时,重复的FIN会得到确认,2MSL定时器重新开始。

处于TIME_WAIT状态的主机收到RST时,会终止TIME_WAIT状态,这叫作TIME_WAIT断开,这有潜在的问题,RFC 1337分析了这个现象并提出了简单的修改,在TIME_WAIT状态时忽略RST段。如果内核参数net.ipv4.tcp_rfc1337为1(默认为0),会丢掉RST报文,否则会提前释放该TIME_WAIT状态的连接。

一旦应用发起一个FIN,应用程序就不能再从这个连接读数据了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值