TCP四次挥手调优

由于TCP是面向连接的,而且允许连接建立后关闭一端的写,故TCP关闭连接的过程需要经过,4次挥手,其流程图如下:
在这里插入图片描述
当主动方调用close函数后,会发送FIN报文并且进入FIN_WAIT1状态;当被动方收到FIN报文后,会立即发送ACK应答报文,被动方进入CLOSE_WAIT状态,主动方收到ACK应答报文后,进入FIN_WAIT2状态;当被动方调用close函数并且发送完数据后,会发送FIN报文,从而进入LAST_ACK状态;当主动方收到被动方的FIN报文后,会发送ACK报文,从而进入TIME_WAIT状态,主动方等待2MSL的时间后,进入CLOSED状态;当被动方收到最后一个ACK报文后,连接就可以进入CLOSED状态了。
从图中可以看出主动方的状态比较多,但是再互联网中往往使用的是HTTP协议,HTTP协议服务端响应完成后,为了节约资源,服务端往往会先关闭连接。故服务端往往优化比较困难。

主动方的优化

1、当主动方调用close函数后,会发送FIN报文并且进入FIN_WAIT1状态,连接变成孤儿连接(变成孤儿连接后,进程就收不到对端的数据了;如果是调用的shutdown(sockfd, SHUT_WR或者SHUT_RDWR)函数,不会进入孤儿连接状态)。但是主动方要是迟迟收不到对方发来的ACK应答报文,那么会重发报文,重发的次数有如下的参数控制:

cat /proc/sys/net/ipv4/tcp_orphan_retries
0

默认值为0,表示重复8次。如果FIN_WAIT1状态的连接过多,可以调低重试次数。正常情况下调低次数已经够用了。
但是服务器调用close函数后,并不会立即发送FIN报文,只有缓冲区中的数据发送完后,才会发送报文,而且如果对方把窗口大小设置为0,也会导致FIN报文发送不出去,恶意的被动方也有可能不回复ACK报文,从而导致孤儿连接过多。为了控制孤儿连接的个数,Linux提供了参数:

cat /proc/sys/net/ipv4/tcp_max_orphans
262144

当超过这个值后,新增的孤儿连接主动方会直接发送RST报文,关闭连接。
2、当被动方收到FIN报文后,会立即发送ACK应答报文并进入CLOSE_WAIT状态,主动方收到ACK应答报文后,进入FIN_WAIT2状态。此状态下,要是主动方是调用的close函数关闭连接的,则linux最长等待对方的FIN报文的时间由如下参数控制

cat /proc/sys/net/ipv4/tcp_fin_timeout
60

默认值为60秒,当超过60秒还收不到对端的FIN报文,则主动端会发送RST报文关闭连接。
3、当主动方收到被动方的FIN报文后,会发送ACK报文,从而进入TIME_WAIT状态,主动方等待2MSL的时间后,进入CLOSED状态。如果主动方是用的shutdown关闭己方的写,那么此时应该调用close函数,因为对方的写也已经关闭了。
那么为什么不直接进入CLOSED状态,而要等2MSL后,连接才进入CLOSED状态呢?主要有如下的原因:
让被动方有足够的时间接收到ACK报文并且允许丢失一次,从而能够正确的关闭连接。
如果被动方没有收到ACK报文,那么会重发FIN 报文。如果直接进入CLOSED状态,再没有同样的TCP四元组的情况下,主动方由于没有了旧连接的上下文信息,故会发送RST报文,从而导致被动方以错误的方式关闭连接。如果新建立了一个同样的TCP四元组的连接,那么可能收到旧连接的分节报文,会导致连接被误关闭,这种网络分节也有可能是其他的数据报文,那么会导致数据错乱。而2MSL时间,可以让报文自动的在网络中消亡。
那么为什么不是3倍或者更多倍的MSL呢?那是因为在丢包率为1%的很差的网络中,联系丢掉2个报文的概率也只有万分之一,概率非常低,解决的代价太大。上文中介绍的tcp_fin_timeout为60,也是基于这2点原因这个原因。
另外需要注意的是TIMEWAIT状态在收到被动方重发的FIN报文会重新开始计时2MSL的时间。
当有大量TIME_WAIT状态的连接时,会占用内存资源(当今服务器内存都比较大,问题不大),更严重的是有可能把端口号占完,这样新的连接就进不来了。TIME_WAIT状态的个数的上限受如下的参数控制,不过不建议调低这个值,因为如果超过这个值,主动方就直接进入CLOSED状态了,从而导致连接异常,相反应该适当调大这个值。

cat /proc/sys/net/ipv4/tcp_max_tw_buckets
262144

那么我们能不能复用处于TIME_WAIT状态的连接的?如果有大量TIMEWAIT状态的主动方,同时也是客户端,会发起连接,那么可以把如下的参数设置为1,其中参数tcp_timestamps双端都要打开

cat /proc/sys/net/ipv4/tcp_tw_reuse
0
cat /proc/sys/net/ipv4/tcp_timestamps
1

TCP协议也在与时俱进,为了复用TIMMEWAIT状态的连接,同时解决旧连接在网络中的分节导致的问题,TCP在可选字段中加入了本方的当前时间戳与对端接收到的报文的最新的时间戳,共8个字节。
其实Linux还提供了如下的参数:

cat /proc/sys/net/ipv4/tcp_tw_recycle
0

当设置为1的时候,Linux会不经过TIME_WAIT状态直接关闭TCP连接。因此不建议打开此参数,高版本的Linux也已经把这个参数删除了。
4、其实linux还提供了通过SO_LINGER选项,来设置调用close或者shutdown函数的行为。其定义如下

struct linger {
 int  l_onoff;    /* 0=off, nonzero=on */
 int  l_linger;    /* linger time, POSIX specifies units as seconds */
}

其作用如下的表格:
在这里插入图片描述
从表格中可以看出,这个选项会导致调用RST关闭连接,不走正常的4次挥手流程,此时可能导致窜包,所以一般不推荐。

被动方的优化

1、当被动方收到主动方的FIN报文后,内核并不会发送FIN报文关闭被动方的连接,因为主动方可能是调用shutdow函数关闭写的。所以被动方需要自己调用close函数关闭本方的写连接。如果主动方是用close函数关闭的连接(大部分情况下都是用的close函数),当被动方收到主动方的FIN报文时(read函数会收到EOF返回0),应该立即调用close函数(也只能调用close函数,因为此时连接已经处于半关闭的状态,在调用shutdown是没有意义的),这是因为主动方调用close函数后,连接就是孤儿连接了,被动方在发送数据是没有意义的,而且被动方只会最长等待对方的FIN60秒。
故如果被动方如果发现CLOSE_WAIT状态的连接过多,应该查看一下应用的代码是否有问题,被动方的负载是否过高。这个问题更多的是需要从代码的层面解决。
2、当被动方调用close函数后,会发送FIN报文,此时被动方进入LAST_ACK状态,等待对方的ACK应答,如果被动方迟迟拿不到对方的ACK应答报文,那么会重试,重试次数同样由如下的参数控制

cat /proc/sys/net/ipv4/tcp_orphan_retries
0

其优化策略与主动方的一致。

特殊请求

1、当被动方收到FIN报文的时候,如果非常迅速的调用close函数,那么被动方的FIN报文可能与发给主动方的ACK报文一块发送给对方,那么此时4次挥手退化为3次,其流程图如下:
在这里插入图片描述
这种情况只是4次挥手的特例,其优化收到类似,这里就不在赘述。
2、如果双方几乎同时关闭连接,那么此时的流程图如下:
在这里插入图片描述
此时双方都只差收到对方的ACK报文,故CLOSING状态与LAST_ACK状态类似,这里也不在赘述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值