0X01

正常情况下TCP连接会通过4次挥手进行拆链(也有通过RST拆除连接的可能,见为什么服务器突然回复RST——小心网络中的安全设备),下图TCP状态机展示了TCP连接的状态变化过程:

我们重点看4次挥手的过程:

  1. 想要拆除连接的一方A发送FIN报文,自身进入到FIN_WAIT_1状态;
  2. 被拆除连接的一方B接收到FIN报文,发ACK,自身进入到CLOSE_WAIT状态;
  3. A收到ACK,进入FIN_WAIT_2状态;
  4. B发送FIN,自身进入LAST_ACK状态;
  5. A收到FIN,发送ACK,自身进入TIME_WAIT状态;
  6. B收到ACK报文,B上的这个socket关闭,端口释放;
  7. A等待2MSL后socket关闭,释放端口。

从以上连接拆除过程我们可以看到:主动发送第一个FIN报文的一方会进入TIME_WAIT状态;进入TIME_WAIT状态的一方需要等待2MSL时间才会释放端口,在2MSL时间内,这个socket对应的四元组(源目IP、源目端口)处于冻结状态。

TIME_WAIT状态的作用主要有两个:

  1. 避免拆链报文在链路中丢失造成连接关闭异常:在第6步,B没有收到ACK报文的时候会认为A没有收到FIN包,进而会重传第4步的FIN,如果这个时候没有TIME_WAIT状态,A侧socket已经关闭,A会针对B发送的FIN包响应RST,有可能导致B连接异常。
  2. 避免乱序到来的业务报文在新生成的socket连接中引发混乱:假设在拆链前有TCP报文由于中间网络传输原因导致在第7步完成之后才到达,如果没有TIME_WAIT状态而A和B又使用同样的4元组新建了一个新的socket,那么迷路的数据包就会进入到新的socket中进行处理,可能导致业务异常。

通过TIME_WAIT状态可以很好的规避上面提到的两个问题,TIME_WAIT状态的老化时间是2MSL,MSL是最大分段生存时间,表示的是一个TCP分段可能在网络上存在的最大时间。2倍MSL的设计可以很好的满足报文在A、B之间一来一回的最大需要消耗的时间,最大程度上避免上述两个问题。在CentOS系统中,MSL的时间一般是30S。

0X02

下图抓包截图展示了一个完整的连接拆除又复用同样的端口新建连接的过程。

在图中server 192.168.221.1运行Web服务,监听82端口,client 192.168.252.2 使用31387端口连接server(抓包截图从挥手前开始截取)。可以看到在编号为3的报文中服务器主动拆除连接,服务器和客户端交互完4个完整的挥手报文后,客户端立即使用相同的源端口和服务器的监听端口建立新的连接。下面逐报文对整个交互过程进行分析:

  1. server→client(PSH、ACK):服务器推送数据最后一个分段给客户端
  2. client→server(ACK):客户端对第1个报文进行接收确认
  3. server→client(FIN、ACK):服务器发送挥手报文给客户端协商断开连接,这是四次挥手的第一步。同时ACK标志位置位,由于第2个报文中没有载荷数据,所以ack值=第2个报文的seq。此时服务器进入FIN_WAIT_1状态
  4. client→server(ACK):客户端对接收到的FIN包进行响应,这是四次挥手的第二步。其中seq值不变,ack=第3个报文的seq+1(因为FIN报文在逻辑上占一个长度)。此时客户端进入CLOSE_WAIT状态,服务器收到ACK报文后进入FIN_WAIT_2状态
  5. client→server(FIN、ACK):客户端给服务器发送FIN包,这是四次挥手的第三步。其seq和ack的值和第4个报文相同。此时客户端进入LAST_ACK状态,等待服务器响应ACK报文后即可关闭连接
  6. server→client(ACK):服务器收到客户端发送的FIN包后会立即给客户端发送ACK包,这是四次挥手的最后一步。其中seq=第3个报文中的seq+1,ack=第5个报文中的seq+1。客户端收到ACK后会立即close该四元组对应的socket,而此时服务器在发送ACK后会进入TIME_WAIT状态,服务器侧对应的TCP四元组会被冻结2MSL
  7. client→server(SYN):客户端连接拆除后立即使用同一个源端口31387向服务器的82端口发起新的SYN连接握手报文
  8. server→client(ACK):通过seq和ack可以看出服务器重传第6个报文。由于服务器对应的四元组仍然在TIME_WAIT状态中,因此对于接受到的报文会认为是迷路的数据包或者客户端没有收到服务器发送的最后一个挥手的ACK报文,所以服务器重新向客户端发送该ACK报文
  9. client→server(RST):客户端向服务器发送一个RST报文,其中seq为server挥手ack包(第6和第8个报文)的ack值。这是因为对服务器侧而言,对应的四元组仍然处于TIME_WAIT状态,而客户端侧并不存在这个四元组的socket信息,客户端正准备使用这个四元组新建连接。这是前文为什么服务器突然回复RST——小心网络中的安全设备中TCP发送RST的第三种情况:TCP接收到一个数据段,但是这个数据段所标识的连接不存在。于是客户端使用ACK报文中的ack值作为seq,发送RST报文给服务器

可以看到当客户端发送完RST后,客户端再次进行了SYN报文的重传,而此次即使仍然复用之前的四元组,客户端和服务器的TCP三次握手正常建立。这是因为当服务器收到RST报文后,无论处在TCP的哪个状态,都会立即进入close状态,进而服务器侧对应被TIME_WAIT状态冻结的四元组得以被释放,客户端侧的复用就成功了。

0X03

如上所述的业务场景是某应用系统使用反向代理地址连接后端服务器的抓包。服务器主动拆链+客户端立即复用源端口,这是一种危险的实现,如果客户端没有RST或者服务器端识别不了RST则很有可能在2MSL时间内,客户端使用被冻结的4元组进行连接建立的操作都会失败。对于服务器主动拆链的场景应该保证终端可用源端口尽可能的多,尽量避免立即端口复用的情况。此外对于服务器主动拆链的场景应该尽可能调短服务器的MSL时间,避免大量TIME_WAIT状态的连接存在影响服务器性能。

TCP状态机:当服务端主动发FIN进TIME_WAIT,客户端源端口复用会发生什么的更多相关文章

  1. 在HTTP通讯过程中,是客户端还是服务端主动断开连接?

    比如说:IE访问IIS,获取文件,肯定是要建立一个连接,这个连接在完成通讯后,是客户端Close了连接,还是服务端Close了连接.我用程序测模拟IE和IIS,都没有收到断开连接的消息,也就是都没有触 ...

  2. QTcpSocket-Qt使用Tcp通讯实现服务端和客户端

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端     本文地址:https:// ...

  3. java Socket通信,客户端与服务端相互发消息

    1.通信过程 网络分为应用层,http.ssh.telnet就是属于这一类,建立在传输层的基础上.其实就是定义了各自的编码解码格式,分层如下: 2.Socket连接 上述通信都要先在传输层有建立连接的 ...

  4. WebSocket安卓客户端实现详解(三)–服务端主动通知

    WebSocket安卓客户端实现详解(三)–服务端主动通知 本篇依旧是接着上一篇继续扩展,还没看过之前博客的小伙伴,这里附上前几篇地址 WebSocket安卓客户端实现详解(一)–连接建立与重连 We ...

  5. netty-2.客户端与服务端互发消息

    (原) 第二篇,客户端与服务端互发消息 与第一篇的例子类似,这里服务端需要三个类,客户端也需要三个类. 服务端关键代码如下:MyServer与上一个例子中的TestServer 差多,这里只列举不同的 ...

  6. 使用SignalR从服务端主动推送警报日志到各种终端(桌面、移动、网页)

    微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. 使用SignalR从服务端主动推送警报日志到各种终端(桌面.移动.网页) 阅读导航 本文背景 ...

  7. NIO【同步非阻塞io模型】关于 NIO socket 的详细总结【Java客户端+Java服务端 + 业务层】【可以客户端间发消息】

    1.前言 以前使用 websocket来实现双向通信,如今深入了解了 NIO 同步非阻塞io模型 , 优势是 处理效率很高,吞吐量巨大,能很快处理大文件,不仅可以 做 文件io操作, 还可以做sock ...

  8. 适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

    本文由“yuanrw”分享,博客:juejin.im/user/5cefab8451882510eb758606,收录时内容有改动和修订. 0.引言 站长提示:本文适合IM新手阅读,但最好有一定的网络 ...

  9. webservice 服务端例子+客户端例子+CXF整合spring服务端测试+生成wsdl文件 +cxf客户端代码自动生成

    首先到CXF官网及spring官网下载相关jar架包,这个不多说.webservice是干嘛用的也不多说. 入门例子 模拟新增一个用户,并返回新增结果,成功还是失败. 大概的目录如上,很简单. Res ...

随机推荐

  1. Simpson公式的应用(HDU 1724/ HDU 1071)

    辛普森积分法 - 维基百科,自由的百科全书 Simpson's rule - Wikipedia, the free encyclopedia 利用这个公式,用二分的方法来计算积分. 1071 ( T ...

  2. [C#] 调试silverlight的时候,总是报“向占位程序传送了空的索引指针”

    这是由于visual studio在调试silverlight的时候,必须和ie一起工作. 按照以下步骤可以把ie设为visual studio的默认浏览器(不用修改操作系统的默认浏览器): 1) 在 ...

  3. Python--day60--建立第一个Djiango项目

  4. C# 输出文件夹下的所有文件

    问题:如何输出给定文件夹目录下面的所有文件的名称? C#代码: using System; using System.IO; namespace MyTest { public class Progr ...

  5. dotnet core 2.1 使用阶梯编译

    在 dotnet core 2.1 可以使用阶梯编译的方法,从 dotnet framework 开始,在代码的所有方法在第一次进入的时候就需要使用 JIT 进行编译为本机的代码.可以看到代码是在第一 ...

  6. Python--day37--多进程

    1,创建多进程(父进程和子进程) import os import time #多进程都要导入multiprocessing from multiprocessing import Process d ...

  7. java 布局管理器

    容器中的组件的排放方式,就是布局. 常见的布局管理器: FlowLayout(流式布局管理器)//目前最常用的 从左到右的顺序排列. Panel默认的布局管理器. BorderLayout(边界布局管 ...

  8. Jenkins 配置自动合并 release 分支到 master 分支

    本文告诉大家如何在 Jenkins 配置合并到 release 的内容自动合并到 gitlab 的 master 分支 首先需要两个仓库,一个是 gitlab 的仓库,另一个是 Jenkins 的仓库 ...

  9. linux Tasklet 实现

    记住 tasklet 是一个特殊的函数, 可能被调度来运行, 在软中断上下文, 在一个系统决 定的安全时间中. 它们可能被调度运行多次, 但是 tasklet 调度不累积; ; tasklet 只 运 ...

  10. Linux 内核设备属性

    sysfs 中的设备入口可有属性. 相关的结构是: struct device_attribute { struct attribute attr; ssize_t (*show)(struct de ...