参考:
【time_wait问题 cnblog】https://juejin.cn/post/6844903958624878606
【tcl握手 掘金】https://www.cnblogs.com/Mr-10/p/10973854.html
【为什么要持续2MSL】https://zhuanlan.zhihu.com/p/51961509
【time_wait问题 简书】https://www.jianshu.com/p/e892e1908755
仅做个人备份,浏览请看原文
目录
net.ipv4.tcp_tw_reuse : 更安全的设置
TIME_WAIT 的起因
1. 首先,time_wait发生在client端,如果服务器上出现了time_wait,只能说明:
(1) 服务器可能主动调用了close()函数主动发起了关闭连接的挥手操作
(2) 服务器作为了client端调用了其他服务器
2. 为什么client端会产生time_wait?
因为为了使TCP正常断开连接,TCP 发起挥手的一端为了确保最后一个ACK 能够达到被动关闭方,所以会等待 2MSL,此期间就成了time_wait状态。
注意:【MSL (maximum segment lifetime):LINUX 的硬编码字段,名称为 TCP_TIMEWAIT_LEN,值为60s,而一个time_wait 默认等待的时间为 2MSL】
3. 为什么需要2msl的时间:
【原因1】:为了保证客户端发送的最后一个ack报文段能够到达服务器。因为这最后一个ack确认包可能会丢失,然后服务器就会超时重传第三次挥手的fin信息报,然后客户端再重传一次第四次挥手的ack报文。如果没有这2msl,客户端发送完最后一个ack数据报后直接关闭连接,那么就接收不到服务器超时重传的fin信息报(此处应该是客户端收到一个非法的报文段,而返回一个RST的数据报,表明拒绝此次通信,然后双方就产生异常,而不是收不到。),那么服务器就不能按正常步骤进入close状态。那么就会耗费服务器的资源。当网络中存在大量的timewait状态,那么服务器的压力可想而知。
【原因2】:在第四次挥手后,经过2msl的时间足以让本次连接产生的所有报文段都从网络中消失,这样下一次新的连接中就肯定不会出现旧连接的报文段了。也就是防止我们上一篇文章 为什么tcp是三次握手而不是两次握手? 中说的:已经失效的连接请求报文段出现在本次连接中。如果没有的话就可能这样:这次连接一挥手完马上就结束了,没有timewait。这次连接中有个迷失在网络中的syn包,然后下次连接又马上开始,下个连接发送syn包,迷失的syn包忽然又到达了对面,所以对面可能同时收到或者不同时间收到请求连接的syn包,然后就出现问题了。
【概括】:对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。
4. 占用的是什么:
被占用的是一个五元组:(协议,本地IP,本地端口,远程IP,远程端口)。对于 Web 服务器,协议是 TCP,本地 IP 通常也只有一个,本地端口默认的 80 或者 443。只剩下远程 IP 和远程端口可以变了。如果远程 IP 是相同的话,就只有远程端口可以变了。这个只有几万个,所以当同一客户端向服务器建立了大量连接之后,会耗尽可用的五元组导致问题。
TIME_WAIT 的危害
- 内存资源占用,内存资源的占用不是很严重,可暂且忽略
- 端口资源占用,一个TCP连接需要消耗一个本地端口,一般可开启的端口为32768 ~ 61000。
如何优化
net.ipv4.ip_local_port_range
制定主动建联时的端口范围
net.ipv4.tcp_max_tw_buckets
此值默认是 18000,当系统中处于 TIME_WAIT 的连接大于该值后,系统会将所有的 TIME_WAIT 连接重置,并打印出警告信息。这个方法过于暴力,解决的问题比带来的问题多,不建议使用
调低 TCP_TIMEWAIT_LEN,重新编译系统
得编译内核,而且TCP发明至今这些固化到内核的参数都是有一定道理的,不要乱改。
SO_LINGER 设置
在应用程序中设置套接字选项,调用close 或者 shutdown 关闭连接时候的行为。
int setsockopt(int sockfd, int level, int optname, const void *optval,
socklen_t optlen);
struct linger {
int l_onoff; /* 0=off, nonzero=on */
int l_linger; /* linger time, POSIX specifies units as seconds */
}
- l_onoff: linger 的开关
- l_onoff 为 0:关闭linger 选项,默认行为,close 或 shutdown 立即返回,如果在套接字发送缓冲区有数据残留,系统会将试着把这些数据都发送出去
- l_onoff 为 1:打开linger 选项,具体行为看 l_linger
- l_linger 为0:调用close后,立即发送一个RST标志给对端,该TCP跳过四次挥手,直接关闭,这种方式被称为“强行关闭”,这种情况下,排队的数据不会被发送,被动关闭方也不知道对端已经彻底断开,只有当被动关闭方正阻塞在recv() 调用上,接受到 RST 时,会立刻得到一个 “connect reset by peer”的异常。
- l_linger 为1:调用close后,调用close的线程将阻塞,直到数据都被发送出去,或者设置 l_linger 的计时时间到。
net.ipv4.tcp_tw_reuse : 更安全的设置
Linux 对 net.ipv4_tw_reuse的解析如下:
Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.It should not be changed without advice/request of technical experts.
这段话意思是说从协议角度理解如果是安全的话,可以复用处于 TIME_WAIT 的套接字为新的连接所用。
协议角度 的安全是指:
- 只适用与连接的发起方,即客户端
- 对应的TIME_WAIT 状态的连接创建时间超过1s才可以被复用
使用的这个选项的前提,需要打开对TCP时间戳的支持。
即 net.ipv4.tcp_timestamps = 1 (默认即为1),重复的数据包会因为时间戳过期被自然丢弃。
SO_REUSEADDR
这个比较特殊,网上有很多教程都说拿这个解决 TIME_WAIT,其实是对的,但是不是一回事。为什么?
这个是解决端口复用问题的,并不是解决 TIME_WAIT ,这个是告诉内核,即使TIME_WAIT 的套接字,也可以作为新的套接字使用,这是为了避免服务端监听端口时,因为被监听的端口处于 TIME_WAIT 导致服务端无法启动。
其本质是解决 服务端 监听端口时的 TIME_WAIT ,而我们上面一直说的是作为客户端建联时没有足够的随机端口导致的无法建联。
终极解决方案: 长连接
条件允许的,把连接保持住,避免频繁的建连和断开。但是仅限内网这么搞,公网的话,一条连接总是不断,运营商可能会搞些小动作,给你限个速。