【TCP四次挥手】

2023-05-16

文章目录

    • TCP 四次挥手过程是怎样的?
    • 为什么挥手需要四次?
    • 第一次挥手丢失了,会发生什么?
    • 第二次挥手丢失了,会发生什么?
    • 第三次挥手丢失了,会发生什么?
    • 第四次挥手丢失了,会发生什么?
    • 为什么 TIME_WAIT 等待的时间是 2MSL?
    • 为什么需要 TIME_WAIT 状态?
    • TIME_WAIT 过多有什么危害?
    • 服务器出现大量 TIME_WAIT 状态的原因有哪些?
    • 服务器出现大量 CLOSE_WAIT 状态的原因有哪些?
    • 如果已经建立了连接,但是客户端突然出现故障了怎么办?
    • 如果已经建立了连接,但是服务端的进程崩溃会发生什么?

TCP 四次挥手过程是怎样的?

四次挥手流程

  • 第一次挥手:
    • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
  • 第二次挥手:
    • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
    • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
  • 第三次挥手:
    • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 第四次挥手:
    • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
    • 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
    • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

注意是:主动关闭连接的,才有 TIME_WAIT 状态。
在这里插入图片描述

为什么挥手需要四次?

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
  • 在特定情况下,四次挥手是可以变成三次挥手的

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

  • 当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。
  • 如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2状态。

第一次挥手丢失?

  • 如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。
  • 当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。

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

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

第二次挥手丢失?

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

注意

  • 当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 FIN 报文。
  • 对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久
  • 对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭

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

  • 当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。
  • 服务端处于 CLOSE_WAIT 状态时,必须由进程主动调用 close 函数,来触发服务端发送 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。
  • 迟迟收不到这个 ACK,服务端就会重发 FIN 报文
    客户端因为是通过 close 函数关闭连接的,处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。

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

  • 当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态
  • 在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文
  • 客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。

为什么 TIME_WAIT 等待的时间是 2MSL?

  • MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
  • 因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。
  • MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。
  • 2MSL时长 这其实是相当于至少允许报文丢失一次。
  • 当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
  • 2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。

为什么需要 TIME_WAIT 状态?

  • 主动发起关闭连接的一方,才会有 TIME-WAIT 状态。
  • 需要 TIME-WAIT 状态,主要是两个原因:
    • 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
    • 保证「被动关闭连接」的一方,能被正确的关闭;
  • 序列号,是 TCP 一个头部字段,标识了 TCP 发送端到 TCP 接收端的数据流的一个字节,因为 TCP 是面向字节流的可靠协议,为了保证消息的顺序性和可靠性,TCP 为每个传输方向上的每个字节都赋予了一个编号,以便于传输成功后确认、丢失后重传以及在接收端保证不会乱序。序列号是一个 32 位的无符号数,因此在到达 4G 之后再循环回到 0。

  • 初始序列号,在 TCP 建立连接的时候,客户端和服务端都会各自生成一个初始序列号,它是基于时钟生成的一个随机数,来保证每个连接都拥有不同的初始序列号。初始化序列号可被视为一个 32 位的计数器,该计数器的数值每 4 微秒加 1,循环一次需要 4.55 小时。

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

  • 服务端在关闭连接之前发送的 SEQ = 301 报文,被网络延迟了。
  • 接着,服务端以相同的四元组重新打开了新连接,前面被延迟的 SEQ = 301 这时抵达了客户端,而且该数据报文的序列号刚好在客户端接收窗口内,因此客户端会正常接收这个数据报文,但是这个数据报文是上一个连接残留下来的,这样就产生数据错乱等严重的问题。
  • 为了防止历史连接中的数据,被后面相同四元组的连接错误的接收,因此 TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。

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

  • TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
  • 假设客户端没有 TIME_WAIT 状态,而是在发完最后一次回 ACK 报文就直接进入 CLOSE 状态,如果该 ACK 报文丢失了,服务端则重传的 FIN 报文,而这时客户端已经进入到关闭状态了

TIME_WAIT 过多有什么危害?

  • 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
  • 第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为 32768~61000,也可以通过 net.ipv4.ip_local_port_range参数指定范围。
    • 如果客户端(主动发起关闭连接方)的 TIME_WAIT 状态过多,占满了所有端口资源,那么就无法对「目的 IP+ 目的 PORT」都一样的服务端发起连接了
    • 如果服务端(主动发起关闭连接方)的 TIME_WAIT 状态过多,并不会导致端口资源受限,但是 TCP 连接过多,会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等。

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

  • TIME_WAIT 状态是主动关闭连接方才会出现的状态,所以如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务器主动断开了很多 TCP 连接。
  • 什么场景下服务端会主动断开连接呢?
    • 第一个场景:HTTP 没有使用长连接
    • 第二个场景:HTTP 长连接超时
    • 第三个场景:HTTP 长连接的请求数量达到上限

第一个场景:HTTP 没有使用长连接

  • 在 HTTP/1.0 中默认是关闭的,如果浏览器要开启 Keep-Alive,它必须在请求的 header 中添加:Connection: Keep-Alive
  • 从 HTTP/1.1 开始, 就默认是开启了 Keep-Alive
  • 只要客户端和服务端任意一方的 HTTP header 中有 Connection:close 信息,那么就无法使用 HTTP 长连接的机制。
  • 当服务端出现大量的 TIME_WAIT 状态连接的时候,可以排查下是否客户端和服务端都开启了 HTTP Keep-Alive,因为任意一方没有开启 HTTP Keep-Alive,都会导致服务端在处理完一个 HTTP 请求后,就主动关闭连接,此时服务端上就
    会出现大量的 TIME_WAIT 状态的连接。

解决的方式也很简单,让客户端和服务端都开启 HTTP Keep-Alive 机制。

第二个场景:HTTP 长连接超时

  • HTTP 长连接可以在同一个 TCP 连接上接收和发送多个 HTTP 请求/应答,避免了连接建立和释放的开销。
  • 如果客户端完成一个 HTTP 请求后,就不再发起新的请求,此时这个 TCP 连接超过设置的时间,此时服务端上就会出现 TIME_WAIT 状态的连接。

第三个场景:HTTP 长连接的请求数量达到上限

  • Web 服务端通常会有个参数,来定义一条 HTTP 长连接上最大能处理的请求数量,当超过最大限制时,就会主动关闭连接。

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

  • CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。
  • 当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。

一个普通的 TCP 服务端的流程:

  1. 创建服务端 socket,bind 绑定端口、listen 监听端口
  2. 将服务端 socket 注册到 epoll
  3. epoll_wait 等待连接到来,连接到来时,调用 accpet 获取已连接的 socket
  4. 将已连接的 socket 注册到 epoll
  5. epoll_wait 等待事件发生
  6. 对方连接关闭时,我方调用 close

大量CLOSE_WAIT原因

  • 第一个原因:第 2 步没有做,没有将服务端 socket 注册到 epoll,这样有新连接到来时,服务端没办法感知这个事件,也就无法获取到已连接的 socket,那服务端自然就没机会对 socket 调用 close 函数了。
  • 第二个原因: 第 3 步没有做,有新连接到来时没有调用 accpet 获取该连接的 socket,导致当有大量的客户端主动断开了连接,而服务端没机会对这些 socket 调用 close 函数,从而导致服务端出现大量 CLOSE_WAIT 状态的连接。
  • 第三个原因:第 4 步没有做,通过 accpet 获取已连接的 socket 后,没有将其注册到 epoll,导致后续收到 FIN 报文的时候,服务端没办法感知这个事件,那服务端就没机会调用 close 函数了。

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

  • 客户端如果发生了宕机,或者断电的场景。如果服务端一直不会发送数据给客户端,那么服务端是永远无法感知到客户端宕机这个事件的,也就是服务端的 TCP 连接将一直处于 ESTABLISH 状态,占用着系统资源。
  • 为了避免这种情况,TCP 有个保活机制
    • 定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
  • TCP 保活的这个机制检测的时间是有点长,可以在应用层实现一个心跳机制。
    • web 服务软件一般都会提供keepalive_timeout参数,用来指定 HTTP 长连接的超时时间。如果设置了 HTTP 长连接的超时时间是 60 秒,web 服务软件就会启动一个定时器,如果客户端在完成一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,就会触发回调函数来释放该连接。

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

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

文章参考:https://xiaolincoding.com/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【TCP四次挥手】 的相关文章

随机推荐

  • vsphere中的虚拟机配置直通GPU后,启动时出现 模块“DevicePowerOn”打开电源失败 的解决方案

    1 虚拟机配置GPU直通 配置后 xff0c 如果直接启动虚拟机 xff0c 将会出现 模块 DevicePowerOn 打开电源失败 的错误提示 xff0c 解决办法如下 xff1a 在虚拟机设置中的虚拟机选项中的配置参数中添加如下参数即
  • Ubuntu系统中清理DNS缓存

    在下一篇文章中 xff0c 我们将看一看 我们如何在Ubuntu中刷新DNS缓存 DNS被认为是Internet连接的关键部分之一 目的是更快地访问访问的网站 更常见的是 xff0c 我们的机器会跟踪DNS记录 xff0c 或者将其缓存 迄
  • Active Directory账号登陆confluence报773错误解决办法

    confluence集成的Active Directory xff0c 在使用AD账号进行登录时总是登录不上 使用管理账号在后台进行测试时提示如下错误 xff1a 可认证测试用户 span class token builtin class
  • 【Keil5】*** Target ‘xxx‘ uses ARM-Compiler ‘Default Compiler Version 5‘ which is not available.解决方法

    出现这个报错的原因在Keil 5 37以后安装compiler version 6 xff0c 如果要使用compiler version 5 xff0c 需要自己安装 下载链接 官网 https developer arm com dow
  • ubuntu 18.04.6 使用内核源码安装内核

    文章目录 前言一 编译内核以及安装二 编译内核模块总结参考资料 前言 上一篇我在ubuntu 18 04 更换内核版本后 xff0c 这篇我们在ubuntu 18 04上用内核源码编译其它版本的内核 xff0c 并进行安装 ubuntu 1
  • 关于CMMI和敏捷过程改进

    问题 xff1a 如果按照CMMI从1到5的思路 xff0c 建设企业的信息化制度 xff08 不是为了评定等级 xff0c 是为了实现项目规范管理 xff09 xff0c 可行吗 xff1f 需要关注哪些问题点呢 xff1f 公司如果是个
  • 【PX4_BUG】You should uninstall ModemManager as it conflicts with any non-modem serial device

    将编译好的固件下载到无人机 xff0c 需要输入命令 make px4 fmu v2 default upload 这里运行时可能会有报错 WARNING You should uninstall ModemManager as it co
  • 【PX4-AutoPilot教程-2】搭建并运行第一个应用程序

    搭建并运行第一个应用程序 本文主要说明如何搭建并运行你的第一个板载应用程序 Firmware src examples px4 simple app文件夹下默认已经有一个完整的例程 xff0c 如果遇到了问题可以作为参考 如果需要自己重新编
  • 【PX4-AutoPilot教程-1】PX4源码文件目录架构分析

    PX4源码文件目录架构分析 PX4源代码的结构复杂 xff0c 这是源代码的总目录结构 xff08 以v1 13 0为例 xff09 xff1a Firmware boards build cmake Documentation integ
  • 【PX4-AutoPilot教程-3】uORB主题订阅发布机制理解、应用和代码阅读

    uORB主题订阅发布机制 1 PX4 Pixhawk的软件体系结构 PX4 Pixhawk的软件体系结构主要被分为四个层次 xff0c 这可以让我们更好的理解PX4 Pixhawk的软件架构和运作 xff1a 应用程序的API xff1a
  • 2020-11-23

    https blog csdn net guofei fly article details 104136008 utm medium 61 distribute pc relevant none task blog BlogCommend
  • MapReduce原理及简单实现

    MapReduce将数据的处理分成了两个步骤 xff0c Map和Reduce Map将输入的数据集拆分成一批KV对并输出 xff0c 对于每一个 lt k1 v1 gt xff0c Map将输出一批 lt k2 v2 gt xff1b R
  • 深度理解Python迭代器

    我们手动的实现一个for循环 xff1a li1 61 list range 10 iteratorObject 61 iter li1 while 1 try print next iteratorObject except StopIt
  • 关于mysql版本差异导致FIND_IN_SET()查询不到数据的问题

    这次发现的问题 xff0c 是在接手项目的时候 xff0c 和安卓端小伙伴测试时候发现的 xff0c 插入数据之后却查不出来 xff0c 通过排查定位到FIND IN SET 函数 xff0c 也是第一次接触FIND IN SET xff0
  • YOLOv4代码学习笔记一

    YOLOV4代码学习笔记一 YOLOV4简介CSPdarknet py学习 本文是对另一个博主的 睿智的目标检测30 Pytorch搭建YoloV4目标检测平台代码的学习 xff0c 由于我是cv新手 xff0c 很多东西不懂 xff0c
  • 无人机光流模块使用技巧

    无人机光流模块使用技巧 光流模块在无 GPS 环境下 xff0c 课实时检测飞机水平移动距离 xff0c 实现对四轴无人机长时间的稳定悬停 图1显示的是湖南优象LC 302光流模块的功能框图 xff0c 光流摄像头拍摄无人机垂直向下的画面
  • CMMI 2.0 和 1.3

    CMMI2 0与1 3在组织形式区别很大 xff0c 很多PA和之前的不太一样了 xff0c 而且PA在2 0中叫实践域 xff0c 1 3中叫过程域 不过其实核心内容没有大的变化 xff0c 只是相关内容的位置进行了调整 xff0c 部分
  • ROS2的RVIZ2无法启动

    在新安装的 xff32 xff2f xff33 2中启动rviz2 xff0c 启动错误 xff0c 显示 Failed to create an OpenGL context BadValue integer parameter out
  • 【TCP 重传、滑动窗口、流量控制、拥塞控制】

    文章目录 重传机制超时重传快速重传SACK方法Duplicate SACK 滑动窗口流量控制那操作系统的缓冲区 xff0c 是如何影响发送窗口和接收窗口的呢 xff1f 窗口关闭 拥塞控制慢启动拥塞避免拥塞发生快速恢复 重传机制 TCP 实
  • 【TCP四次挥手】

    文章目录 TCP 四次挥手过程是怎样的 xff1f 为什么挥手需要四次 xff1f 第一次挥手丢失了 xff0c 会发生什么 xff1f 第二次挥手丢失了 xff0c 会发生什么 xff1f 第三次挥手丢失了 xff0c 会发生什么 xff