主动方的优化

注:主动关闭连接的一方才有TIME_WAIT状态。
主动关闭方和被动关闭方的优化思路也不同,下面将进行分别讨论:
主动方的优化
关闭连接的方式通常有两种,分别是 RST 报文关闭和 FIN 报文关闭。
如果进程异常退出了,内核就会发送 RST 报文来关闭,它可以不走四次挥手流程,是一个暴力关闭连接的方式。
安全关闭连接的方式必须通过四次挥手,它由进程调用 close 和 shutdown 函数发起 FIN 报文(shutdown 参数须传入 SHUT_WR 或者 SHUT_RDWR 才会发送 FIN)。
1、close 和 shutdown 有什么区别
调用了 close 函数意味着完全断开连接,完全断开不仅指无法接收数据,而且也不能发送数据。
此时,调用了 close 函数的一方的连接叫做「孤儿连接」,如果你用 netstat -p 命令,会发现连接对应的进程名为空。
使用 close 函数关闭连接是不优雅的。于是,就出现了一种优雅关闭连接的 shutdown 函数,它可以控制只关闭一个方向的连接。

其中第二个参数决定断开连接的方式,主要有三种:
(2)SHUT_WR(1)
关闭连接的「写」这个方向,这就是常被称为「半关闭」的连接。如果发送缓冲区还有未发送的数据,将被立即发送出去,并发送一个 FIN 报文给对端。
(3)SHUT_RDWR(2)
相当于 SHUT_RD 和 SHUT_WR 操作各一次,关闭套接字的读和写两个方向。
2、FIN_WAIT1状态的优化
如果 FIN_WAIT1 状态连接很多,我们就需要考虑降低 tcp_orphan_retries 的值。当重传次数超过 tcp_orphan_retries 时,连接就会直接关闭掉(即:新增的孤儿连接将不再走四次挥手,而是直接发送 RST 复位报
文强制关闭)。
注:tcp_max_orphans 参数,定义了「孤儿连接」的最大数量。
3、FIN_WAIT2状态的优化
当主动方收到 ACK 报文后,会处于 FIN_WAIT2 状态,就表示主动方的发送通道已经关闭,接下来将等待对方发送 FIN 报文,关闭对方的发送通道。
这时,如果连接是用 shutdown 函数关闭的,主动方连接可以一直处于 FIN_WAIT2 状态,因为它可能还可以发送或接收数据。
但对于 close 函数关闭的孤儿连接,由于无法再发送和接收数据,所以这个状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒(与 TIME_WAIT 状态持续的时间是相同
的)。它意味着对于孤儿连接(调用 close 关闭的连接),如果在 60 秒后还没有收到 FIN 报文,连接就会直接关闭。
4、TIME_WAIT状态的优化
TIME_WAIT的状态尤其重要,主要是两个原因

(1)防止收到历史数据,从而导致数据错乱的问题
若TIME_WAIT等待时间过短,被延迟的数据包抵达后会发生什么呢?

TIME_WAIT设计为2MSL,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
注:
MSL 全称是 Maximum Segment Lifetime,它定义了一个报文在网络中的最长生存时间(报文每经过一次路由器的转发,IP 头部的 TTL 字段就会减 1,减到 0 时报文就被丢弃,这就限制了报文的最长存活时间)。
(2)为什么是 2 MSL 的时长呢?
这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方᯿发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。
在Linux系统中,MSL的值固定为30秒。
等待足够的时间以确保最后的ACK能让被动关闭方接收,从而帮助其正确关闭
(3)假设TIME_WAIT没有等待或等待的时间过短,断开连接会造成什么?

如上图红色框框客户端四次挥手的最后一个 ACK 报文如果在网络中被丢失了,此时如果客户端 TIMEWAIT 过短或没有,则就直接进入了 CLOSE 状态了,那么服务端则会一直处在 LAST-ACK 状态。
当客户端发起建立连接的 SYN 请求报文后,服务端会发送 RST 报文给客户端,连接建立的过程就会被终止。
注:
Linux 提供了 tcp_max_tw_buckets 参数,当 TIME_WAIT 的连接数量超过该参数时,新关闭的连接就不再经历 TIME_WAIT 而直接关闭。查看系统的TIME_WAIT的连接数量:

但tcp_max_tw_buckets 也不是越大越好,毕竟内存和端口都是有限的。
还有一种方式是复用处于TIME_WAIT状态的连接,那就是打开 tcp_tw_reuse 参数。
但是需要注意,该参数是只用于客户端(建立连接的发起方),因为是在调用 connect() 时起作用的,而对于服务端(被动连接方)是没有用的。
tcp_tw_reuse 从协议角度理解是安全可控的,可以复用处于 TIME_WAIT 的端口为新的连接所用。
什么是协议角度理解的安全可控呢?
主要有两点:
(1)只适用于连接发起方,也就是 C/S 模型中的客户端;
(2)对应的 TIME_WAIT 状态的连接创建时间超过 1 秒才可以被复用。
使用这个选项,还有一个前提,需要打开对 TCP 时间戳的支持(对方也要打开)。
由于引入了时间戳,带来了一些好处:
全站分卷索引 · 230 卷
- 1代码随想录知识星球精华(最强八股文)第五版(计算机基础篇)
- 2DNS
- 3DNS解析过程
- 4递归查询和迭代查询
- 5HTTP特性与简述
- 6HTTP版本演变
- 7HTTP/2
- 8HTTP1.1协议的性能问题
- 9兼容HTTP1.1
- 10二进制帧
- 11并发传输
- 12服务器主动推送资源
- 13总结
- 14HTTP/3
- 15HTTP/2的缺点
- 16QUIC协议的特点
- 17HTTP/3协议
- 18HTTP缓存
- 19协商缓存
- 20HTTPS
- 21HTTPS的特点
- 22非对称加密
- 23摘要算法+数字签名
- 24数字证书
- 25证书信任链
- 26HTTPS是怎么建立连接的
- 27TCP与UDP头部格式
- 28TCP
- 29列举TCP选项
- 30TCP三次握手
- 31为什么需要三次握手?
- 32什么是半连接队列
- 33什么是SYN攻击
- 34为什么挥手需要四次
- 35TIME_WAIT 过多有什么危害?
- 36TCP的滑动窗口
- 374. 程序是如何表示发送方的四个部分的呢?
- 386. 接收窗口和发送窗口的大小是相等的吗?
- 391. 慢启动
- 403. 拥塞发生
- 414. 快速恢复
- 42IP基础
- 43IP地址
- 44在浏览器中输入URL并按下回车之后会发生什么
- 45TCP 抓包实践
- 46TCP连接与断开优化
- 47TCP内核参数
- 48主动方的优化
- 49被动方的优化
- 50Linux是如何收发网络包的
- 51收发流程
- 52面试题
- 53TCP/IP四层网络模型
- 54五层网络体系结构
- 55HTTP响应报文
- 56HTTP常见字段
- 57什么是TCP粘包问题?如何解决TCP粘包问题?
- 58UDP会不会产生粘包问题呢?
- 59请求报文
- 60时间消耗
- 61HTTP缓存有几种
- 62HTTP1.0和HTTP1.1的区别?
- 63HTPP1.1和HTTP1.0有什么区别
- 64HTTP/3
- 65HTTPS是如何建立连接的
- 66DNS是什么
- 67递归查询和迭代查询
- 68TCP与UDP的区别
- 69TCP和UDP分别在什么场景使用?
- 70三次握手的过程
- 71Keep-Alive的优缺点
- 72CDN的工作流程
- 73Session的工作原理
- 74TCP重传机制是怎么实现的?
- 75什么是SS0
- 76HTTP优化方案
- 77浏览器地址栏输入URL回车后涉及到的流程
- 781.5 浏览器发送HTTP/HTTPS请求到web服务器
- 79SYN攻击
- 80操作系统
- 81操作系统的功能
- 82进程和线程
- 83线程基础
- 84进程调度
- 85进程调度方式
- 86进程通信
- 87消息传递(消息队列)
- 88信号量
- 89信号
- 90线程通信
- 91多线程
- 92进程通信
- 93消息队列
- 94信号
- 95守护进程
- 96互斥与同步
- 974. 互斥锁解锁
- 983. 处理方法
- 99读写锁
- 1002. 销毁读写锁
- 1014. 写锁定
- 1021. 创建条件变量
- 1034. 阻塞线程
- 104信号量
- 1054. V操作
- 1062. 读者-写者问题
- 107存储系统
- 108虚拟内存
- 109内存分段
- 110内存分页
- 111虚拟内存
- 112文件系统
- 1135. 文件访问
- 114目录
- 1154. 目录操作
- 1163. 目录的实现
- 1177. 虚拟文件系统
- 1184. 文件系统性能
- 119磁盘编址
- 120三层抽象
- 121外设管理
- 122Linux
- 123select/poll/epoll的区别和联系
- 124Linux 内存管理
- 125Linux虚拟内存
- 126Linux 信号
- 127Linux设计
- 1283. 各种IO模型的比较
- 129Linux网络
- 130Linux中的软链接和硬链接有什么区别?
- 131什么是中断和异常
- 132信号和信号量有什么区别
- 133Linux中如何查看一个进程
- 134进程、线程的区别?
- 135进程之间的通信方式
- 136如何解除死锁
- 137分段和分页的区别
- 138IO多路复用
- 139MySQL表连接
- 140过滤条件
- 141执行一次update语句,发生了什么
- 142用户账号存储
- 143用户账号删除
- 144索引
- 145索引有哪些类别
- 1462. 按照存储分类
- 1475. 按照字段个数分类
- 148索引区分度
- 149索引什么时候会失效
- 150事务的四大特性ACID
- 151并行事务会出现什么问题
- 152幻读是如何解决的
- 153事务隔离的实现
- 154读已提交是怎么实现的
- 155锁
- 1561、全局锁
- 1573、行级锁
- 158锁的划分
- 1592.、程序员角度
- 160Innodb使用表锁还是行锁
- 161什么是binlog
- 162执行引擎
- 163MySQL常见问题
- 1642. 事务
- 1653. 脏页
- 1664. 表结构
- 1676. 查询效率
- 168MySQL分库分表
- 169垂直分表
- 170水平分库
- 171分库分表的问题
- 172数据库排序思路
- 173MySQL主从复制
- 1745. 主从复制模式
- 175面试题
- 176执行一条select语句,发生了什么?
- 177你对关系范式有哪些了解
- 178数据库
- 179Redis基础
- 180Redis单线程为什么还这么快
- 181Redis底层数据结构
- 182Redis持久化
- 183AOF日志
- 184RDB快照
- 185Redis功能
- 1862. 哨兵模式
- 1873. 切片集群
- 188Redis哨兵机制是什么
- 189什么是集群脑裂
- 190过期删除策略有哪些
- 191Redis的LRU算法和LFU算法有什么区别
- 192什么是缓存击穿
- 193如何保证删除缓存操作一定能成功?
- 194为什么Redis是单线程
- 195Redis底层使用了什么数据结构
- 196Redis实现分布式锁
- 197缓存中的热点数据和冷数据是什么
- 198缓存雪崩、击穿、穿透和解决办法【常问】
- 199Redis过期删除策略有哪些
- 200关键概念
- 201单例模式的应用场景
- 202工厂模式
- 203实际应用
- 204抽象工厂模式
- 205概念
- 206应用
- 207实现步骤
- 208主要角色
- 209代理模式应用
- 210策略模式
- 211示例
- 212装饰模式
- 213主要角色
- 21423种设计模式的概念
- 215创建型模式
- 216行为型模式
- 217设计原则
- 218迪米特法则
- 219你在实际开发中用过哪些设计模式
- 220依赖倒置
- 221代理模式和适配器模式有什么区别
- 222固定长度编码
- 223冯诺依曼模型
- 224总线
- 225输入/输出设备
- 226编译系统如何工作
- 227处理器解释指令
- 228场景题 & 系统设计
- 229如何设计一个本地缓存
- 230如何设计一个RPC框架