Linux网络:传输层协议TCP(二)三次挥手四次握手详解

news2024/11/23 8:44:25

目录

一、TCP的连接管理机制

1.1三次握手 

 1.2四次挥手

二、理解 TIME_WAIT 状态

2.1解决TIME_WAIT 状态引起的 bind 失败的方法

三、理解CLOSE_WAIT状态

 


一、TCP的连接管理机制

在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接

1.1三次握手 

三次握手顾名思义就是在刚开始建立连接时,一端向另一端发送SYN连接请求(一般时客户端向服务器),服务器收到后做出应答,也向客户端发送SYN连接请求顺便捎带ACK应答,客户端收到后再次向服务器发送ACK应答,至此三次握手完成,双方连接建立完毕。

前两次握手只要有一次没有成功那么连接都是无法建立的双方都是可以清楚的知道连接是没有建立的,那如何保证第三次握手的ACK对方收到了呢?

一般情况下如果前两次握手成功,第三次出现失败的概率很小,如果真的出现,比如客户端给服务器发的携带ACK应答的报头在中途丢包了,那么此时服务器就会进入超时等待,那么此时客户端会认为连接已经建立好了,可能就会直接进行数据报文的传输,此时服务器收到报文,但是对于服务器端,连接还未建立好,此时服务器就会对客户端做出应答,将报头中的RST置为1,表示重置。

此时就会进行重置,双方重新进行三次握手。所以RST主要就是解决连接问题。

所以为什么要三次握手?

1、因为建立连接维护连接是有成本的,如果连接只需要单次握手就能建立,那客户端疯狂的单方面给服务器发送连接最终就会导致服务器花费资源来维护大量连接,这种情况服务器是很容易被攻击的。而这种大量发送SYN的情况,我们也将其称为SYN洪水攻击。而三次握手我们可以发现,客户端也需要进行一次接收和一次应答,付出的代价和服务器是对等的,而在上图的逻辑中,服务器也就倒逼着客户端,服务器和客户端建立连接的前提是客户端先建立连接!所以三次握手虽然不能保证绝对的安全,但是起码可以避免遭受单机攻击就被搞挂掉,这也是为什么不一次两次握手的主要理由。

2、三次握手,client和server双方,都会有确定的一次收发,三次握手也是最小成本确认全双工的方式,确保双方OS是健康且愿意通信的,其实三次握手本质上也是4次握手,只不过中间两次被变成一次捎带应答了。

 1.2四次挥手

一般情况下,依旧是客户端率先起手发送携带FIN的报头,表示断开连接的请求,此时是第一次挥手 ,当客户端发送携带FIN标志位的报头时 ,此时就表明用户层不会再有数据往发送缓冲区进行写入了,因为FIN往往就对应我们使用的close(fd),此时我们在代码中调用了close关闭了对应的文件描述符,那么我们就肯定无法再往对应的fd中去进行写入了。同样的对于服务器端也是一样的,所以双方发送FIN本质上也就是在用户层调用close(),去将对应缓冲区的数据冲刷干净。

而四次挥手,也是双方OS自动完成的(写到这里博主真的是有苦在心口难开啊,明明不需要博主去实现但还是要拿出100%的精力去学,因为唯有真正了解,才能运用自如啊!)。 而这里为什么不使用捎带应答将两次挥手变成一次呢?

因为与建立连接不同,建立连接时双方都是处于空闲,且目的都是与彼此建立连接,而断开时,客户端数据发送完毕调用close,而服务器此时不一定将所有的数据都完整传输给客户端了,所以无法直接无脑二合一,因此才要分为四次。而close也只是用户层的我们将文件描述符关闭,实际上,真正关闭也是等到对方的ACK以后才正式关闭,也可以调用shutdown来选择性关闭文件描述符的读/写功能。

三次握手与四次挥手就像一场凄美的爱情一样,在一起时总是迫不及待,轰轰烈烈,两步并一步奔向彼此,对未来充满向往,而最终因为现实也好,缘尽也罢,终会有一方先挥手作别,尽管再依依不舍,一次次的wait换来的还是双方的分离。

 服务端状态转化:

[CLOSED -> LISTEN] 服务器端调用 listen 后进入 LISTEN 状态, 等待客户端连接;

[LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入 内核等待队列中, 并向客户端发送 SYN 确认报文.

[SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文, 就进入 ESTABLISHED 状态, 可以进行读写数据了.

[ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用 close), 服务 器会收到结束报文段, 服务器返回确认报文段并进入 CLOSE_WAIT;

[CLOSE_WAIT -> LAST_ACK] 进入 CLOSE_WAIT 后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用 close 关闭连接时, 会向客户端发送 FIN, 此时服务器进入 LAST_ACK 状态, 等待最后一个 ACK 到来(这个 ACK 是客户 端确认收到了 FIN)

[LAST_ACK -> CLOSED] 服务器收到了对 FIN 的 ACK, 彻底关闭连接.

客户端状态转化:

[CLOSED -> SYN_SENT] 客户端调用 connect, 发送同步报文段;

[SYN_SENT -> ESTABLISHED] connect 调用成功, 则进入 ESTABLISHED 状 态, 开始读写数据; [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用 close 时, 向服务器发送结 束报文段, 同时进入 FIN_WAIT_1;

[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入 FIN_WAIT_2, 开始等待服务器的结束报文段;

[FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入 TIME_WAIT, 并发出 LAST_ACK;

[TIME_WAIT -> CLOSED] 客户端要等待一个 2MSL(Max Segment Life, 报文 最大生存时间)的时间, 才会进入 CLOSED 状态.

二、理解 TIME_WAIT 状态

现在做一个测试,首先启动 server,然后启动 client,然后用 Ctrl-C 使 server 终止,这时马 上再运行 server, 结果是:

 这是因为,虽然 server 的应用程序终止了,但 TCP 协议层的连接并没有完全断开,因此不 能再次监 听同样的server 端口. 我们用 netstat 命令查看一下:

 • TCP 协议规定,主动关闭连接的一方要处于 TIME_ WAIT 状态,等待两个 MSL(maximum segment lifetime)的时间后才能回到 CLOSED 状态.

我们使用 Ctrl-C 终止了 server, 所以 server 是主动关闭连接的一方, 在 TIME_WAIT 期间仍然不能再次监听同样的 server 端口;

MSL 在 RFC1122 中规定为两分钟,但是各操作系统的实现不同, 在 Centos7 上 默认配置的值是 60s;

可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看 msl 的值;

想一想, 为什么是 TIME_WAIT 的时间是 2MSL?

MSL 是 TCP 报文的最大生存时间, 因此 TIME_WAIT 持续存在 2MSL 的话

就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服 务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错 误的);

同时也是在理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失, 那么 服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然 可以重发 LAST_ACK);

2.1解决TIME_WAIT 状态引起的 bind 失败的方法

在 server 的 TCP 连接没有完全断开之前不允许重新监听, 某些情况下可能是不合理的

服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是 每秒都有很大数量的客户端来请求).

这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务 器端主动清理掉), 就会产生大量 TIME_WAIT 连接.

由于我们的请求量很大, 就可能导致 TIME_WAIT 的连接数很多, 每个连接都会 占用一个通信五元组(源 ip, 源端口, 目的 ip, 目的端口, 协议). 其中服务器的 ip 和端 口和协议是固定的. 如果新来的客户端连接的 ip 和端口号和 TIME_WAIT 占用的链 接重复了, 就会出现问题.

使用 setsockopt()设置 socket 描述符的 选项 SO_REUSEADDR 为 1, 表示允许创建端 口号相同但 IP 地址不同的多个 socket 描述符

三、理解CLOSE_WAIT状态

当你看到CLOSE_WAIT状态时,说明此时只是断开了一个方向上的连接,四次挥手并没有完成。下面可以做一个实验,我们让客户端主动断开连接,服务端处在一个死循环中或者服务端不调用close函数。

假设现在客户端和服务端建立好连接,服务端一旦建立连接服务端到客户端方向上的连接,就会处于ESTABLISHED状态。

对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题。

服务端结束服务以后一定要调用close函数,否则会发生内存泄漏

发生CLOSE_WAIT 的原因,大致有两点猜想:

  • 没有处理 read 返回值为  0 的情况。一般read 返回值为0 ,认为对端连接断开;read 返回值小于0,说明读取出错。
  • 主要原因可能还是客户端关闭了连接,然而此时服务端忙于处理其他 IO ,没有关闭连接。

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

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

相关文章

vue import from

vue import from 导入文件,从XXXX路径;引入文件 import xxxx from “./minins/resize” import xxxx from “./minins/resize.js” vue.config.js 定义 : resolve(src);就是指src 目录 import xxxx from “/utils/auth” im…

vue3知识

目录 基础vue开发前的准备vue项目目录结构模板语法属性绑定条件渲染列表渲染通过key管理状态事件处理事件传参事件修饰符数组变化侦测计算属性Class绑定style绑定侦听器表单输入绑定模板引用组件组成组件嵌套关系组件注册方式组件传递数据Props(父传子)组件传递多种数据类型组件…

怎么批量加密U盘?U盘批量加密的方法有哪些?

加密U盘是保护U盘数据安全的重要方法。而当需要加密的U盘数量较多时,我们需要批量加密U盘。那么,U盘怎么批量加密呢?下面我们就来了解一下。 U盘内存卡批量只读加密专家 U盘内存卡批量只读加密专家是一款专业的U盘加密软件,适用于…

什么牌子的充电宝又好又耐用?认准这几个充电宝品牌!错过就吃亏

在 2024 年,充电宝已然成为我们生活中不可或缺的电子配件。但面对市场上琳琅满目的充电宝产品,如何挑选出一款适合自己的,却让许多人感到困惑。充电宝要怎么挑?这可不是一个简单的问题。不同的使用场景、不同的设备需求&#xff0…

02 MySQL数据库管理

目录 1.数据库的结构 sql语言主要由以下几部分组成 2. 数据库与表的创建和管理 1,创建数据库 2,创建表并添加数据 3,添加一条数据 4,查询数据 5,更新数据 6,删除数据 3.用户权限管理 1.创建用户 …

3万多有分类的成语词典ACCESS\EXCEL数据库

今天最后发一个成语词典的数据库了,因为成语词典的数据库太多了导致我自己都有些糊涂了,今天这份数据库应该说是最好的成语词典了,不但包含了3级分类,而且还有级别(不要太较真)字段。 数据库包含多个表&…

利用 Databend 生态构建现代数据湖工作流

数据是洞察力的基石,越来越多的企业开始建设以数据资产为中心的存储和分析一体化方案,这要求 Data Infra 架构能够提供可扩展、灵活且统一的数据工作流。现代数据湖架构同时兼顾数据湖的可扩展性和数据仓库的性能,满足对大规模数据处理的需求…

视频文件怎么压缩到最小 视频文件怎么压缩到最小内存 4个简单的方法工具分享简单步骤

如何压缩大视频文件以减小其大小?在分享或存储大视频文件时,有效压缩是关键,以降低文件大小且不显著牺牲视觉和听觉质量。视频文件的大小直接影响传输、分享和存储的成本与便捷性。掌握压缩视频的技能对于数字内容处理至关重要,能…

【Android】linux

android系统就是跑在linux上的系统。Linux层里面包含系统和硬件驱动等一些本地代码的环境。 linux的目录 mount: 用于查看哪个模块输入只读,一般显示为: [rootlocalhost ~]# mount /dev/cciss/c0d0p2 on / type ext3 (rw) proc on /proc type proc (…

SpringBoot 实现图形验证码

一、最终结果展示 二、前端代码 2.1 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8"><title>验证码</title><style>#inputCaptcha {height: 30px;vertical-align: middle;}#verifica…

(leetcode学习)236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它自己的祖…

Q238. 除自身以外数组的乘积

思路 一开始想到的是按位乘 看了题解&#xff0c;思路是存i左边的乘积和 与 i右边的乘积和 代码一&#xff1a; 需要三次循环,需要额外空间 left和right数组 代码&#xff1a; public int[] productExceptSelf(int[] nums) {int[] left new int[nums.length];int[] right …

python题解

空间三角形 输入在三维空间的三角形三个顶点A&#xff0c;B&#xff0c;C的坐标&#xff08;x,y,z&#xff09;&#xff0c;计算并输出三角形面积。不考虑不能构成三角形的特殊情况。 格式 输入格式&#xff1a; 依次输入三个顶点A&#xff0c;B&#xff0c;C的坐标&#xff…

CISSP,信息安全圈公认的高含金量证书

在数字化和信息化迅速发展的时代&#xff0c;信息安全的重要性愈发突出。 网络攻击、数据泄露和隐私问题频发&#xff0c;使得企业和组织对信息安全专业人士的需求不断增加。 CISSP&#xff08;Certified Information Systems Security Professional&#xff09;作为信息安全领…

文字描边效果

文字描边效果可以通过text-shadow来实现&#xff0c;也可以通过-webkit-text-stroke来实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, i…

MySQL数据库练习(5)

1.建库建表 # 使用数据库 use mydb16_trigger;# 表格goods create table goods( gid char(8) primary key, name varchar(10), price decimal(8,2), num int);# 表格orders create table orders( oid int primary key auto_increment, gid char(10) not null, name varchar(10…

MYSQL第五次作业

1、触发器 建立两个表:goods(商品表)、orders(订单表) mysql> use mydb16_trigger; Database changed mysql> create table goods-> (-> gid char(8) primary key,-> name varchar(10),-> price decimal(8,2),-> num int-> ); Query O…

MySQL零散拾遗(四)--- 使用聚合函数时需要注意的点点滴滴

聚合函数 聚合函数作用于一组数据&#xff0c;并对一组数据返回一个值。 常见的聚合函数&#xff1a;SUM()、MAX()、MIN()、AVG()、COUNT() 对COUNT()聚合函数的更深一层理解 COUNT函数的作用&#xff1a;计算指定字段在查询结果中出现的个数&#xff08;不包含NULL值&#…

C++操作Smgp协议的相关教程

SGIP是中国网通为实现短信业务而制定的一种通信协议&#xff0c;用于在短消息网关&#xff08;SMG&#xff09;和服务提供商&#xff08;SP&#xff09;之间、短消息网关&#xff08;SMG&#xff09;和短消息网关&#xff08;SMG&#xff09;之间通信。 Perl的IO::Async模块提…

SAP PP学习笔记31 - 计划运行的步骤2 - Scheduling(日程计算),BOM Explosion(BOM展开)

上一章讲了计划运行的5大步骤中的前两步&#xff0c;计算净需求和计算批量大小。 SAP PP学习笔记30 - 计划运行的步骤1 - Net requirements calculation 计算净需求(主要讲了安全库存要素)&#xff0c;Lot-size calculation 计算批量大小-CSDN博客 本章继续讲计划运行的后面几…