一文弄懂TCP断开连接时候的四次挥手

部分内容来源:小林coding


TCP四次挥手过程是怎样的

天下没有不散的宴席,对于 TCP 连接也是这样, TCP 断开连接是通过四次挥手方式

双方都可以主动断开连接,断开连接后主机中的「资源」将被释放,四次挥手的过程如下图

1.客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。

2.服务器收到报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。客户端收到服务器的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。

3.等待服务器处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。

4.客户端收到服务器的 FIN 报文后,回发 ACK 应答报文,之后进入 TIME_WAIT 状态。服务器收到了 ACK 应答报文后,就进入 CLOSE 状态,至此服务器端已经完成连接的关闭。客户端经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。

这里一点需要注意是:主动关闭连接的一方才有 TIME_WAIT 状态


为什么挥手需要四次

再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据但是还能接收数据
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务器端可能还有数据要处理和发送,等服务器端不再发送数据时才发送 FIN 报文给客户端来表示同意现在关闭连接

从上面过程可知,服务器端通常需要等待完成数据的发送和处理,所以服务端的 ACKFIN 一般都会分开发送,因此是需要四次挥手。

但是在特定情况下,四次挥手是可以变成三次挥手的,具体情况可以看这篇:TCP 四次挥手,可以变成三次吗?


第一次挥手丢失了会发生什么?

当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。

正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT_2 状态。

如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制重传 FIN 报文

重发次数由 tcp_orphan_retries 参数控制。

当客户端传输 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,机会在等待一段时间(时间为上一次超时的 2 倍),如果还是没能收到第二次挥手,那么直接进入 close 状态。


第二次挥手丢失了会发生什么

当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。

在前面我们也提了,ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数

这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT_2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 FIN 报文。

close () 函数与 FIN_WAIT_2 状态的时长:

当使用 close () 函数关闭连接时,该连接就无法再发送和接收数据了

为了避免 FIN_WAIT_2 状态持续过长时间占用资源,系统通过 tcp_fin_timeout 这个参数来控制处于该状态下连接的持续时长,默认值是 60 秒。也就是说,超过 60 秒还没收到服务端的 FIN 报文,连接就会进行相应处理(比如释放资源等)

shutdown () 函数与 FIN_WAIT_2 状态

果主动关闭方使用 shutdown () 函数关闭连接,并且指定只关闭发送方向,接收方向并没有关闭,那么主动关闭方仍然可以接收数据

在这种情况下,如果主动关闭方一直没有收到服务端发送的第三次挥手(FIN 报文),那么主动关闭方的连接就会一直处于 FIN_WAIT_2 状态(不会超时关闭),

因为它还在等待接收服务端的数据,所以不会按照 tcp_fin_timeout 设定的时间去结束这个状态

close() 函数

在网络编程中,close() 函数通常用于关闭一个已经建立的 TCP 连接。当调用 close() 函数时,意味着该连接不再用于发送和接收数据。从 TCP 协议的角度来看,调用 close() 会触发 TCP 连接的关闭流程,即发送一个 FIN(Finish)报文给对方,表示本方已经没有数据要发送了

shutdown() 函数

shutdown() 函数也是用于关闭 TCP 连接的,但它比 close() 函数更加灵活。shutdown() 函数可以指定关闭连接的不同方向,即只关闭发送方向、只关闭接收方向或者同时关闭两个方向


第三次挥手丢失会发生什么

当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。

此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。

服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文

同时连接进入 LAST_ACK 状态等待客户端返回 ACK 来确认连接关闭。

如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的


第四次挥手丢失会发生什么

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。

在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。

然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。

如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍的 tcp_orphan_retries 参数控制


为什么需要Time_Wait状态

主动发起关闭连接的一方,才会有 TIME-WAIT 状态

需要 TIME-WAIT 状态,主要是两个原因:

  • 防止历史连接中的数据,被后面相同四元组的连接错误的接收
  • 保证「被动关闭连接」的一方,能被正确的关闭

原因一:防止历史连接中的数据,被后面相同四元组的连接错误的接收

TIME_WAIT状态保持一定时间,防止错误接收数据

为了能更好的理解这个原因,我们先来了解序列号(SEQ)和初始序列号(ISN)。

  • 序列号:是一个头字段,标识了 TCP 发送端到 TCP 接收端的数据流的一字节,因为 TCP 是面向字节流的可靠传输协议,为了确保数据的顺序性和可靠性,TCP 为每个传输方向上的每个字节都赋予了一编号,以便于传输成功后确认、丢失后重传以及在接收端保证不会乱序。序列号是一个 32 位的无符号数,因此在到达 4G 之后会循环到 0。
  • 初始序列号:在 TCP 建立连接的时候,客户端和服务端都会各自生成一个初始序列号,它是基于时钟生成的一个随机数,来保证每个连接都有不同的初始序列号。初始序列号与 32 位的序列号数,该计数器的数值每 4 微秒加 1,循环一次需要 4.55 小时

通过前面我们知道,序列号初始化序列号并不是无限递增的,会发生回绕为初始值的情况

这意味着无法根据序列号来判断新老数据。

假设 TIME-WAIT 没有等待时间或时间过短,被延迟的数据包抵达后会发生什么呢

  • 服务端在关闭连接之前发送的 SEQ = 301 报文,被网络延迟了
  • 接着,服务端以相同的四元组重新打开了新连接,前面被延迟的 SEQ = 301 这时抵达了客户端,而且该数据报文的序列号刚好在客户端接收窗口内,因此客户端会正常接收这个数据报文,但是这个数据报文是上一个连接残留下来的,这样就产生数据错乱等严重的问题。

为了防止历史连接中的数据,被后面相同四元组的连接错误的接收

因此 TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃

使得原来连接的数据包在网络中自然消失,再出现的数据包一定都是新建立连接所产生的


原因二:保证「被动关闭连接」的一方,能被正确的关闭

客户端必须等待足够长的时间,确保服务端能够收到 ACK

TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭

如果客户端(主动关闭方)最后一次 ACK 报文(第四次挥手)在网络中丢失了,那么按照 TCP 可靠性原则,服务端(被动关闭方)会重发 FIN 报文。
假设客户端没有 TIME_WAIT 状态,而是在发完最后一次回 ACK 报文就直接进入 CLOSE 状态,如果该 ACK 报文丢失了,服务端则重传的 FIN 报文,而这时客户端已经进入到关闭状态了,在收到服务端重传的 FIN 报文后,就会回 RST 报文

服务端收到这个 RST 并将其解释为一个错误(Connection reset by peer)

这对于一个可靠的协议来说不是一个优雅的终止方式
为了防止这种情况出现,客户端必须等待足够长的时间,确保服务端能够收到 ACK,如果服务端没有收到 ACK,那么就会触发 TCP 重传机制,服务端会重新发送一个 FIN,这样一来一往刚好两个 MSL 的时间


TIME_WAIT过多会有什么危害? 

过多的 TIME-WAIT 状态主要的危害有两种:

  • 占用系统资源:比如文件描述符、内存资源、CPU 资源、线程资源等;
  • 占用端口资源:端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过 net.ipv4.ip_local_port_range 参数指定范围。

客户端和服务端 TIME_WAIT 过多,造成的影响是不同的。

客户端的 TIME_WAIT 状态过多

客户端(发起连接方)都是和「目的 IP+ 目的 PORT 」都一样的服务端建立连接的话,当客户端的 TIME_WAIT 状态连接过多的话,就会受端口资源限制

如果占满了所有端口资源,那么就无法再跟「目的 IP+ 目的 PORT」都一样的服务端建立连接了。

不过,即使是在这种场景下,只要连接的是不同的服务端,端口是可以重复使用的,所以客户端还是可以向其他服务端发起连接的,这是因为内核在定位一个连接的时候,是通过四元组(源 IP、源端口、目的 IP、目的端口)信息来定位的,并不会因为客户端的端口一样,而导致连接冲突。


服务端(主动发起关闭连接方)的 TIME_WAIT 状态过多

如果服务端(主动发起关闭连接方)的 TIME_WAIT 状态过多,并不会导致端口资源受限

因为服务端只监听一个端口,而且由于一个四元组唯一确定一个 TCP 连接,因此理论上服务端可以建立很多连接,但是 TCP 连接过多,会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等


服务器出现大量 TIME_WAIT 状态的原因有哪些? 

首先要知道 TIME_WAIT 状态是主动关闭连接方才会出现的状态,所以如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务器主动断开了很多 TCP 连接

问题来了,什么场景下服务器端会主动断开连接呢?

  • 第一个场景:HTTP 没有使用长连接
  • 第二个场景:HTTP 长连接超时
  • 第三个场景:HTTP 长连接的请求数量达到上限

如果已经建立了连接,但是客户端突然出现故障了怎么办?

客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景。发生这种情况的时候,如果服务端一直不会发送数据给客户端,那么服务端是永远无法感知到客户端宕机这个事件的,也就是服务端的 TCP 连接将一直处于 ESTABLISH 状态占用着系统资源

为了避免这种情况,TCP 搞了个保活机制。这个机制的原理是这样的:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔发送一个探测报文,该探测报文包含的数据是异常小,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将该错误信息通知给上层应用程序


说一下TCP的保活机制 

TCP保活机制的原理:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔发送一个探测报文,该探测报文包含的数据是异常小,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将该错误信息通知给上层应用程序


如果已经建立了连接,但是服务端的进程崩溃会发生什么?

TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程

我自己做了个实验,使用 kill -9 来模拟进程崩溃的情况

发现在 kill 掉进程后,服务端会发送 FIN 报文,与客户端进行四次挥手

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/896518.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小程序画带圆角的圆形进度条

老的API <canvas id"{{canvasId}}" canvas-id"{{canvasId}}" style"opacity: 0;" class"canvas"/> startDraw() {const { canvasId } this.dataconst query this.createSelectorQuery()query.select(#${canvasId}).bounding…

数据结构:二叉树的链式结构及相关算法详解

目录 一.链式结构的实现 1.二叉树结点基本结构&#xff0c;初始化与销毁&#xff1a; 二.链式结构二叉树的几种遍历算法 1.几种算法的简单区分&#xff1a; 2.前序遍历&#xff1a; 3.中序遍历&#xff1a; 4.后序遍历&#xff1a; 5.层序遍历&#xff08;广度优先遍历B…

WebRTC与PJSIP:呼叫中心系统技术选型指南

助力企业构建高效、灵活的通信解决方案 在数字化时代&#xff0c;呼叫中心系统的技术选型直接影响客户服务效率和业务扩展能力。WebRTC与PJSIP作为两大主流通信技术&#xff0c;各有其核心优势与适用场景。本文从功能、成本、开发门槛等维度为您深度解析&#xff0c;助您精准匹…

cuda-12.4.0 devel docker 中源码安装 OpenAI triton

1&#xff0c;准备 docker 容器 下载docker image: $ sudo docker pull nvidia/cuda:12.6.2-devel-ubuntu20.04 创建容器&#xff1a; sudo docker run --gpus all -it --name cuda_LHL_01 -v /home/hongleili/ex_triton/tmp1:/root/ex_triton/tmp1 nvidia/cuda:12.6…

Zookeeper(67) Zookeeper在HBase中的应用是什么?

Zookeeper 在 HBase 中起到了至关重要的作用&#xff0c;主要用于协调和管理 HBase 集群中的多个组件。具体来说&#xff0c;Zookeeper 在 HBase 中的应用包括以下几个方面&#xff1a; Master 选举&#xff1a;HBase 集群中可以有多个 Master 节点&#xff0c;但只有一个处于…

React antd的datePicker自定义,封装成组件

一、antd的datePicker自定义 需求&#xff1a;用户需要为日期选择器的每个日期单元格添加一个Tooltip&#xff0c;当鼠标悬停时显示日期、可兑换流量余额和本公会可兑流量。这些数据需要从接口获取。我需要结合之前的代码&#xff0c;确保Tooltip正确显示&#xff0c;并且数据…

由 JRE 软链接依赖引发的故障排查

最近工作查到了一个有意思的问题&#xff0c;在这里记录一下。 1. 问题背景 Java应用 SAP 系统自带了一个 jre&#xff0c;但在Ubutnu2004上部分功能不正常。 初步排查发现&#xff0c;该 JRE 版本来源不明&#xff08;用户也说不清是哪里来的&#xff09;。 于是尝试以下方…

2-3文件的属性信息

文章目录 1 file命令2 stat命令 1 file命令 用来识别文件类型 # 参数的位置是任意的 file 文件名 [参数]-b 只显示文件类型和文件编码&#xff0c;不显示文件名-i 显示文件的MIME类型-F 设置输出字符串的分隔符-L 查看软链接文件自身文件属性liyblyb:/tmp$ file xxxtmp.log …

JavaScript 系列之:垃圾回收机制

前言 垃圾回收是一种自动内存管理机制&#xff0c;用于检测和清除不再使用的对象&#xff0c;以释放内存空间。当一个对象不再被引用时&#xff0c;垃圾回收器会将其标记为垃圾&#xff0c;然后在适当的时候清除这些垃圾对象&#xff0c;并将内存回收给系统以供其他对象使用。…

(七)趣学设计模式 之 适配器模式!

目录 一、 啥是适配器模式&#xff1f;二、 为什么要用适配器模式&#xff1f;三、 适配器模式的实现方式1. 类适配器模式&#xff08;继承插座 &#x1f468;‍&#x1f469;‍&#x1f467;‍&#x1f466;&#xff09;2. 对象适配器模式&#xff08;插座转换器 &#x1f50c…

【Java基础】Java中new一个对象时,JVM到底做了什么?

Java中new一个对象时&#xff0c;JVM到底做了什么&#xff1f; 在Java编程中&#xff0c;new关键字是我们创建对象的最常用方式。但你是否想过&#xff0c;当你写下new MyClass()时&#xff0c;Java虚拟机&#xff08;JVM&#xff09;到底在背后做了哪些工作&#xff1f;今天&…

内网穿透:打破网络限制的利器

目录 深入理解内网穿透 内网与外网的奥秘 内网穿透的原理剖析 总结与展望 在如今这个数字化时代&#xff0c;网络已经成为我们生活和工作中不可或缺的一部分。但你是否遇到过这样的困扰&#xff1a;在家办公时&#xff0c;想要访问公司内部的文件服务器&#xff0c;却因为网…

【汽车ECU电控数据管理篇】HEX文件格式解析篇章

一、HEX格式文件是啥 HEX 文件是 Intel 公司提出的一种按地址排列的数据信息格式&#xff0c;通常用于存储嵌入式系统的二进制代码。它以 ASCII 码的形式记录数据&#xff0c;每一行以冒号开头&#xff0c;包含数据长度、地址、记录类型、数据和校验码等信息。HEX 文件常用于程…

深入理解 CSS pointer-events: none:穿透点击的魔法

一、什么是 pointer-events: none&#xff1f; pointer-events: none 是一个强大的 CSS 属性&#xff0c;它控制元素是否响应鼠标/触摸事件&#xff08;如点击、悬停、拖拽&#xff09;。当设置为 none 时&#xff0c;元素会变得“透明”&#xff0c;事件会直接穿透到下方的元…

【AHK】资源管理器自动化办公实例/自动连点设置

此处为一个自动连续点击打开检查的自动化操作案例&#xff0c;没有quicker的鼠键录制&#xff0c;不常用了&#xff0c;做个备份 #MaxThreadsPerHotkey 2 ; 这个是核心&#xff01;&#xff01;&#xff01;&#xff01;确保可以同时运行多个热键或标签global isRunning : tru…

html css js网页制作成品——HTML+CSS甜品店网页设计(5页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

Springboot使用Milvus的基本操作

Milvus 先得保证数据的正确安装并且正确运行 <dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId> </dependency> <dependency><groupId>io.milvus</groupId><artifactId>milvu…

初阶数据结构(C语言实现)——3顺序表和链表(2)

2.3 数组相关面试题 原地移除数组中所有的元素val&#xff0c;要求时间复杂度为O(N)&#xff0c;空间复杂度为O(1)。OJ链接 力扣OJ链接-移除元素删除排序数组中的重复项。力扣OJ链接-删除有序数组中的重复项合并两个有序数组。力扣OJ链接-合并两个有序数组 2.3.1 移除元素 1…

【力扣】2619. 数组原型对象的最后一个元素——认识原型与原型链

【力扣】2619. 数组原型对象的最后一个元素——认识原型与原型链 文章目录 【力扣】2619. 数组原型对象的最后一个元素——认识原型与原型链题目解决方案概述全局上下文函数上下文事件处理程序构造函数上下文类上下文显式 / 隐式绑定绑定方法和永久 this 上下文 方法 1&#xf…

ubuntu终端指令集 shell编程基础(一)

磁盘指令 连接与查看&#xff1a;磁盘与 Ubuntu 有两种连接方式&#xff1b;使用ls /dev/sd*查看是否连接成功&#xff0c;通过df系列指令查看磁盘使用信息。若 U 盘已挂载&#xff0c;相关操作可能失败&#xff0c;需用umount取消挂载。磁盘操作&#xff1a;使用sudo fdisk 磁…