TIMEWAIT状态


在这里插入图片描述
    图中可以看到:主动关闭方将进入TIME_WAIT状态;被动关闭方将进入CLOSE_WAIT状态。

    该计时器就是TIME_WAIT计时器,即当主动方A发送FIN给对方B后,被动方B回复ACK后,主动方A将进入TIME_WAIT状态(用来等待B发送FIN+ACK)。如果在计时器时间内没有接收到B发来的FIN+ACK,A将会重启TIME_WAIT计时器继续等待B发来的FIN+ACK。保证全双工管道的安全断开。

TIMEWAIT是友好的

Note1:什么是TIME_WAIT状态?
    客户端在接收到服务器发送的FIN段后,没有立即进入CLOSED状态,而是进入TIME_WAIT状态;在TIME_WAIT状态,客户端连接要等待一段长为2MSL的时间才能完全关闭。

Note2:TIME_WAIT状态存在的2个原因
(1)可靠的终止TCP连接
    假如报文段7(ACK)丢失:那么服务器将会重发报文段6(FIN),客户端需要停留在TIME_WAIT状态以处理重复收到的报文段6(FIN),(补充说明:假如没有2MSL的延时等待,客户端只能以RST段回应服务器,异常终止连接
(2)允许老的重复分解在网络中消失
     假设在12.106.32.254的1500端口和206.168.112.219的21端口之间建立连接,我们关闭这个连接。过段时间后在相同的IP和端口之间建立另一个链接。后一个连接成为前一个连接的化身,因为它们的IP和端口相同。 ==> TCP必须防止来自某个连接的老的重复分组在该连接已经终止后再现,从而被误解成属于同一连接的某个新的化身。==> 为了做到这点,TCP协议栈规定,处于TIME_WAIT状态的连接不能发起新的化身。(又因为,TIME_WAIT状态的持续时间时MSL的2倍,这就足以让发送方向上的分组最多存活MSL秒后被丢弃,让接收应答方向上的分组最多存活MSL也被丢弃,所以,TIME_WAIT状态保证了该连接先前化身的老的重复分组已在网络中消逝了)

大量TIMEWAIT在某些场景中导致的令人头疼的业务问题

    在高并发短连接的TCP服务器上,当服务器处理完请求后立刻按照主动正常关闭连接。这个场景下,会出现大量socket处于TIMEWAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。 ⇒ ⇒ 来解释下这个场景。主动正常关闭TCP连接,都会出现TIMEWAIT。为什么我们要关注这个高并发短连接呢?有两个方面需要注意:

  1. 高并发可以让服务器在短时间范围内同时占用大量端口,而端口有个0~65535的范围,并不是很多,刨除系统和其他服务要用的,剩下的就更少了。
  2. 在这个场景中,短连接表示“业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间”的连接。这里有个相对长短的概念,比如,取一个web页面,1秒钟的http短连接处理完业务,在关闭连接之后,这个业务用过的端口会停留在TIMEWAIT状态几分钟,而这几分钟,其他HTTP请求来临的时候是无法占用此端口的。单用这个业务计算服务器的利用率会发现,服务器干正经事的时间和端口(资源)被挂着无法被使用的时间的比例是 1:几百,服务器资源严重浪费。(说个题外话,从这个意义出发来考虑服务器性能调优的话,长连接业务的服务就不需要考虑TIMEWAIT状态。同时,假如你对服务器业务场景非常熟悉,你会发现,在实际业务场景中,一般长连接对应的业务的并发量并不会很高)

    综合这两个方面,持续的到达一定量的高并发短连接,会使服务器因端口资源不足而拒绝为一部分客户服务。同时,这些端口都是服务器临时分配,无法用SO_REUSEADDR选项解决这个问题。

    一对矛盾:TIMEWAIT既友好,又令人头疼。但是还是要抱着一个友好的态度来看待它,因为它尽它的能力保证了服务器的健壮性。

可行而且必须存在,但是不符合原则的解决方式

  1. linux没有在sysctl或者proc文件系统暴露修改这个TIMEWAIT超时时间的接口,可以修改内核协议栈代码中关于这个TIMEWAIT的超时时间参数,重编内核,让它缩短超时时间,加快回收;
  2. 利用SO_LINGER选项的强制关闭方式,发RST而不是FIN,来越过TIMEWAIT状态,直接进入CLOSED状态。(详细请看socket套接字选项详解的SO_LINGER

为什么说上述两种解决方式我觉得可行,但是不符合原则?
    首先认为,要依靠TIMEWAIT状态来保证服务器程序健壮,网络上发生的乱七八糟的问题太多了,先要服务功能正常。
    那是不是就不要性能了呢?并不是。如果服务器上跑的短连接业务量到了我真的必须处理这个TIMEWAIT状态过多的问题的时候,解决原则是尽量处理,而不是跟TIMEWAIT干上,非先除之而后快:)如果尽量处理了,还是解决不了问题,仍然拒绝服务部分请求,那我会采取分机器的方法,让多台机器来抗这些高并发的短请求。持续十万并发的短连接请求,两台机器,每台5万个,应该够用了吧。一般的业务量以及国内大部分网站其实并不需要关注这个问题,一句话,达不到需要关注这个问题的访问量。
    真正地必须使用上述我认为不合理的方式来解决这个问题的场景有没有呢?答案是有,像淘宝、百度、新浪、京东商城这样的站点,由于有很多静态小图片业务,如果过度分服会导致需要上线大量机器,多买机器多花钱,得多配机房,多配备运维工程师来守护这些机器,成本增长非常严重。这个时候就要尽一切可能去优化。

如何尽量并合理地处理TIMEWAIT过多

  1. sysctl改两个内核参数就行了,如下:
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_tw_recycle = 1
        简单来说,就是打开系统的TIMEWAIT重用和快速回收,至于怎么重用和快速回收,这个问题我没有深究,实际场景中这么做确实有效果。用netstat或者ss观察就能得出结论。
  2. 还有些朋友同时也会打开syncookies这个功能,如下:
    net.ipv4.tcp_syncookies = 1
        打开这个syncookies的目的实际上是:“在服务器资源(并非单指端口资源,拒绝服务有很多种资源不足的情况)不足的情况下,尽量不要拒绝TCP的 syn(连接)请求,尽量把syn请求缓存起来,留着过会儿有能力的时候处理这些TCP的连接请求”。
    如果并发量真的非常非常高,打开这个其实用处不大。
在使用 Boost.Asio 进行 socket 编程时,可以通过设置 SO_LINGER 选项来控制关闭 socket 的行为,从而避免错误关闭。 SO_LINGER 选项用于设置 socket 关闭时的行为。当 SO_LINGER 选项设置为默认值(即 linger 结构体中的 l_onoff 域为 0)时,socket 在关闭时会进入 TIME_WAIT 状态,这是为了确保远程主机收到所有数据包并发送 ACK 确认消息后再关闭连接。如果此时另一端的 socket 也处于 TIME_WAIT 状态,那么这两个 socket 将无法立即重新建立连接,因为 TIME_WAIT 状态需要等待一段时间才会结束。 为了避免长时间处于 TIME_WAIT 状态,可以设置 SO_LINGER 选项,将 linger 结构体中的 l_onoff 域设置为非零值,并将 l_linger 域设置为 0。这样,在关闭 socket 时,操作系统会强制关闭连接,不会等待远程主机的 ACK 确认消息,从而避免进入 TIME_WAIT 状态。 下面是一个示例代码: ```cpp boost::asio::ip::tcp::socket socket(io_service); boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address_v4::from_string("127.0.0.1"), 12345); socket.connect(endpoint); // 设置 SO_LINGER 选项,避免进入 TIME_WAIT 状态 boost::asio::socket_base::linger option(true, 0); socket.set_option(option); // 关闭 socket socket.close(); ``` 在上述代码中,首先创建一个 tcp::socket 对象,并连接到远程主机。然后设置 SO_LINGER 选项,将 linger 结构体中的 l_onoff 域设置为 true,l_linger 域设置为 0,最后调用 close 函数关闭 socket。这样就可以避免长时间处于 TIME_WAIT 状态了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值