TCP/IP卷一:68---TCP连接管理之(TCP状态转换图、TIME_WAIT状态、静默时间、FIN_WAIT_2状态、同时打开/同时关闭的状态)

一、TCP状态转换图

  • 图中的状态用椭圆表示,而状态之间的转换则用箭头表示
  • TCP连接的每一端都可以在这些状态中进行转换:
    • 有些转换是由于接收到某个控制位字段置位的报文段而引发的(例如,SYN,ACK,FIN)
    • 而有些转换又会要求发送一些控制位字段置位的报文段
    • 另外还有一些转换是由应用程序的动作或计时器超时引发的。上述各种情况都以文本注释的方式标记在转换图的相关箭头旁边
  • 当初始化时,TCP从CLOSED状态启动。通常根据是执行主动打开操作还是被动打开操作,TCP将分别快速转换到SYN_SENT或LISTEN状态

典型的状态

  • 图中值得注意的是只有一部分状态转移被认为是“典型的”
  • 我们已经将普通的客户端状态转移用深黑的实线箭头表示,而普通的服务器状态转换用虚线箭头表示
  • 导向 ESTABLISHED状态的两种转换与打开一个连接相关,从ESTABLISHED状态导出的两种转换则用于终止一个连接。ESTABLISHED是通信双方双向传输数据的状态(后面的文章会详细地介绍该状态)

主动关闭状态(FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT)

  • 图中将FIN_WAIT_1 、 FIN_WAIT_2以及TIME_WAIT状态用一个方框括起来(至少是部分被括起来),称作“主动关闭” 。它们表示当本地应用程序发起一个关闭请求时会进人的状态集合

被动关闭状态(CLOSE_WAIT、LAST_ACK)

  • 另外两个状态(CLOSE_WAIT与LAST_ACK)被一个虚线框括起来,并标记为“被动关闭” 。这些状态与等待一个节点确认一个FIN报文段并进行关闭相关

同时关闭

  • 同时关闭可以视为一种包含两个主动关闭的形式。此外,还使用了CLOSING状态

状态名称

  • 图中的11种状态名称(CLOSED、 LISTEN、 SYN_SENT等)都是基于UNIX、Linux、Windows系统中netstat命令所输出的名称
  • 而这些名称则是参考了[RFCO793]中的名称。CLOSED状态并不能算作一个“官方”的状态,但在图中却被作为一个开始状态点和一个终止状态点

从LISTEN到SYN_SENT

  • 从LISTEN到SYN_SENT的状态转换在TCP协议中是合法的但却不被伯克利套接字所支持,因此比较少见
  • 从SYN_RCVD返回到LISTEN的状态转换只有在SYN_RCVD状态是由LISTEN状态(在正常的场景中)而非SYN_SENT状态转换而来的情况下才是正确 的。这意味着,如果我们执行一个被动打开操作(进人LISTEN状态),接收一个SYN,发送一个带有ACK确认的SYN(进人SYN_RCVD状态),然后收到一个重置消息而非ACK,端点就会返回到LISTEN状态,并且等待另一个连接请求的到来

TCP连接与关闭的简化版本

  • 下图不仅显示了正常TCP连接的建立与终止过程,还详细介绍了客户端与服务器经历的备种状态。它是前面介绍TCP连接与关闭时的简略版本,在显示相关状态的同时省略了选项与初始序列号等细节
  • 假设在图中左侧的客户端执行一个主动打开操作,而右侧的服务器执行一个被动打开操作。虽然根据前文的介绍由客户端负责执行主动关闭操作,但是通信的任何一方都能够进行这项工作

二、TIME_WAIT状态

TIME_WAIT概念

  • 在TCP的状态转换图中我们看到执行主动关闭的那端经历了这个状态。该端点停留在这个状态的持续时间是最长分节生命期(maximum segment lifetime,MSL)的两倍,有时候称之为2MSL

MSL概述

  • 任何TCP实现都必须为MSL选择一个值。RFC 1122[Braden 1989]的建议值是2分钟,不过源自Berkeley的实现传统上改用30秒这个值。这意味着TIME_WAIT状态的持续时间在1分钟到 4分钟之间
  • MSL是任何IP数据报能够在因特网中存活的最长时间。我们知道这个时间是有限的, 因为每个数据报含有一个称为跳限(hop limit)的8位字段(见IPv4的TTL字段和IPv6的跳限字段),它的最大值为255。尽管这是一个跳数限制而不是真正的时间限制,我们仍然假设:具有最大跳限(255)的分组在网络中存在的时间不可能超过MSL秒
  • 这一数值是可以更改的。在Linux系统中,net.ipv4.tcp_fin_timeout的数值记录了2MSL状态需要等待的超时时间(以秒为单位)

分组在网络中的“迷途”现象

  • 分组在网络中“迷途”通常是路由异常的结果某个路由器崩溃或某两个路由器之间的某个链路断开时,路由协议需花数秒钟到数分钟的时间才能稳定并找出另一条通路。在这段时间内有可能发生路由循环(路由器A把分组发送给路由器B,而B再把它们发送回A),我们关心的分组可能就此陷入这样的循环。假设迷途的分组是一个TCP分节,在它迷途期间,发送端TCP 超时并重传该分组,而重传的分组却通过某条候选路径到达最终目的地。然而不久后(自迷途的分组开始其旅程起最多MSL秒以内)路由循环修复,早先迷失在这个循环中的分组最终也被 送到目的地
  • 这个原来的分组称为迷途的重复分组或漫游的重复分组。TCP必须正确处理这些重复的分组

TIME_WAIT状态存在的两个理由:

  • 1.可靠地实现TCP全双工连接的终止
  • 2.允许老的重复分节在网络中消逝

①可靠地实现TCP全双工连接的终止:

  • 第一个理由可以通过查看TCP的分组交换图,我们假设最终的ACK丢失了,服务器将重新发送它的最终那个FIN因此客户必须维护状态信息,以允许它重新发送最终那个ACK。要是客户不维护状态信息,它将响应以一个RST(另外一种类型的TCP分节),该分节将被服务器解释成一个错误
  • 当TCP执行一个主动关闭并发送最终的ACK时,连接必须处于TIME_WAIT状态并持续两倍于最大生存期的时间。这样就能够让TCP重新发送最终的ACK以避免出现丢失的情况。重新发送最终的ACK并不是因为TCP重传了ACK(它们并不消耗序列号,也不会被TCP重传),而是因为通信另一方重传了它的FIN(它消耗一个序列号)。事实上,TCP总是重传FIN,直到它收到一个最终的ACK
  • 如果TCP打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流(即全双工关闭),那么它必须正确处理连接终止序列4个分节中任何一个分节丢失的情况。本例子也说明了为什么执行主动关闭的那一端是处于TIME_WAIT状态的那一端:因为可能不得不重传最终那个ACK的就是那一端

②允许老的重复分节在网络中消逝:

  • 为理解存在TIME_WAIT状态的第二个理由,我们假设在12.106.32.254的1500端口和 206.168.112.219的21端口之间有一个TCP连接。我们关闭这个连接,过一段时间后在相同的IP地址和端口之间建立另一个连接。后一个连接称为前一个连接的化身(incarnation),因为它们的IP地址和端口号都相同。TCP必须防止来自某个连接的老的重复分组在该连接已终止后再现, 从而被误解成属于同一连接的某个新的化身。为做到这一点,TCP将不给处于TIME_WAIT状态的连接发起新的化身。既然TIME_WAIT状态的持续时间是MSL的2倍,这就足以让某个方向上的分组最多存活MSL秒即被丢弃,另一个方向上的应答最多存活MSL秒也被丢弃
  • 通过实施这个规则,我们就能保证每成功建立一个TCP连接时,来自该连接先前化身的老的重复分组都已在网络中消逝了
  • 当TCP处于等待状态时,通信双方将该连接(客户端IP地址、客户端端口号、服务器IP地址、服务器端口号)定义为不可重新使用只有当2MSL等待结束时,或一条新连接使用的初始序列号超过了连接之前的实例所使用的最高序列号时,或者允许使用时间戳选项来区分之前连接实例的报文段以避免混淆时,这条连接才能被再次使用。不幸的是,一些实现施加了更加严格的约束。在这些系统中,如果一个端口号被处于2MSL等待状态的任何通信端所用,那么该端口号将不能被再次使用

SO_REUSEADDR套接字选项

  • 许多实现与API都提供了绕开这一约束的方法。在伯克利套接字API中,SO_REUSEADDR套接字选项就支持绕开操作。它允许调用者为自已分配本地端口号,即使这个端口号是某个处于2MSL等待状态连接的一部分
  • 我们还会发现,即使套接字(地址、端口号对)具有绕开机制TCP的规则仍会防止该端口号被处于2MSL等待状态的同一连接的其他实例重新使用。当一个连接处于2MSL等待状态时,任何延迟到达的报文段都将被丢弃。 因为一条连接是通过地址和端口号的4元组定义的。如果该连接处于2MSL等待状态,那么它在这段时间内将不能被重新使用。当这条正确的连接最终被建立起来后,这条连接之前的实例所传输的延迟报文段是不能被当作新连接的一部分来解读的

TIME_WAIT状态一般是针对于客户端的

  • 对于交互式应用程序而言,客户端通常执行主动关闭操作并进人TIME_WAIT状态,服务器通常执行被动关闭操作并且不会直接进入TIME_WAIT状态,其中的含义如下
  • 如果我们终止一个客户端后立刻重新启动同一客户端,那么新的客户端也不能重新使用相同的本地端口号。通常来说,这并不成问题。因为客户端通常使用的是由操作系统分配的临时端口号,而且它们也并不关心被分配的端口号究竟是什么(回忆一下,实际上出于安全考虑有一种推荐的随机方法[RFC6056])。值得注意的是,由于一个客户端能够快速产生大量的连接(尤其是针对同一个服务器),因此它不得不在临时端口号供应紧张时延迟一会儿来等待其他连接的终止
  • 对于服务器而言,情况则大不相同。它们通常使用一些知名的端口。如果我们终止一个已经建立了一条连接的服务器进程,然后立即尝试重新启动它,服务器不能为该程序的通信端分配对应的端口号(它将会收到一个“地址已占用”的绑定错误)。这是因为当连接进入2MSL等待状态时,端口号仍然是连接的一部分。根据系统对MSL数值的不同设定,服务器可能需要花费1 - 4分钟才能重新启动

三、静默时间的概念

  • 在本地与外地的IP地址、端口号都相同的情况下,2MSL状态能够防止新的连接将前一 个连接的延迟报文段解释成自身数据的状况。然而,上述方法只有在与处于2MSL等待状态的连接相关的主机未关闭的条件下才具有意义
  • 如果一台与处于TIME_WAIT状态下的连接相关联的主机崩溃,然后在MSL内重新启动,并且使用与主机崩溃之前处于TIME_WAIT状态的连接相同的IP地址与端口号,那么 将会怎样处理呢?在上述情况下,该连接在主机崩溃之前产生的延迟报文段会被认为属于主机重启后创建的新连接。这种处理方式将不会考虑在主机重启之后新连接是如何选择初始序列号的

静默时间

  • 为了防止上述情况的发生,[RFCO793]指出在崩溃或者重启后TCP协议应当在创建新的连接之前等待相当于一个MSL的时间该段时间被称作静默时间
  • 然而只有极少数实现遵循了这一点。因为绝大多数的主机在崩溃之后都需要超过一个MSL的时间才能重新启动。 此外,如果上层应用程序自身已采用了校验和或者加密手段,那么此类错误会很容易检测出来

四、FIN_WAIT_2状态

  • 在FIN_WAIT_2状态,某TCP通信端已发送一个FIN并已得到另一端的确认
  • 除非出现半关闭的情况,否则该TCP端将会等待另一端的应用程序识别出自己已接收到一个文件末尾的通知并关闭这一端引起发送FIN的连接。只有当应用程序完成了这一关闭操作(它的FIN已经被接收),正在关闭的TCP连接才会从FIN_WAIT_2状态转移至TIME_WAIT状态。 这意味着连接的一端能够依然永远保持这种状态。另一端也会依然处于CLOSE_WAIT状态,并且能永远维持这一状态直到应用程序决定宣布它的关闭

预防FIN_WAIT_2(计时器)

  • 目前有许多方法都能够防止连接进人FIN_WAIT_2这一无限等待状态:如果负责主动关闭的应用程序执行的是一个完全关闭操作,而不是用一个半关闭来指明它还期望接收数据,那么就会设置一个计时器。如果当计时器超时时连接是空闲的,那么TCP连接就会转移到CLOSED状态
  • 在Linux系统中,能够通过调整变量net.ipv4.tcp_fin_timeout的数值来设置计时器的秒数。它的默认值是60s

五、同时打开与关闭的转换

  • 前文已经分别介绍了在发送与接收SYN报文段时SYN_SENT状态与SYN_RCVD状态 的用途

同时打开详解

  • 下图中,TCP协议经过专门的设计后能够处理同时打开的情况,并且只建立一条连接
    • 当同时打开的情况发生时,TCP的状态迁移过程不同于上面“TCP连接与关闭”的图片所示的样子
    • 通信的两端几乎在相同的时刻都会发送一个SYN报文段,然后它们进入SYN_SENT状态
    • 当它们接收到对方发来的SYN报文段时会将状态迁移至SYN_RCVD,然后重新发送一个新的SYN并确认之前接收到的SYN
    • 通信两端都接收到了SYN与ACK,它们的状态将都会迁移至ESTABLISHED

同时关闭详解

  • 在前面介绍TCP时间戳选项时(https://blog.csdn.net/qq_41453285/article/details/104039845)描述了TCP同时关闭的情况
  • 过程如下:
    • 当应用程序发布关闭连接的消息后,通信两端的状态都会从ESTABLISHED迁移至FIN_WAIT_1
    • 与此同时,它们都会向对方发送一个FIN
    • 在接收到对方发来的FIN后,本地通信端的状态将从FIN_WAIT_1迁移至CLOSING
    • 然后,通信双方还会发送最终的ACK
    • 当接收到最终的ACK后,每个通信端会将状态更改为TIME_WAIT,从而初始化2MSL等待过程

发布了1355 篇原创文章 · 获赞 891 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104041073
今日推荐