系统如何实现Socket端口复用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统如何实现Socket端口复用相关的知识,希望对你有一定的参考价值。

参考技术A 新的linux有这个功能, 建socket的时候带上so_reuseport这个选项

端口复用修改Linux内核参数,解决TCP连接中的TIME-WAIT socket

相同的五元组快速重复建链会提示:Address already in use。
用netstat -na命令发现系统中有大量状态为TIME-WAIT的TCP连接
可以编辑/etc/sysctl.conf文件,增加如下内容:

#vi /etc/sysctl.conf
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1

再执行以下命令,让修改结果立即生效:

sysctl -p

用以下语句看了一下服务器的TCP状态:

netstat -n | awk '/^tcp/ ++state[$NF] END for(key in state) print key,"\\t",state[key]'
返回结果如下:
TIME_WAIT 4654
FIN_WAIT1 576
FIN_WAIT2 91
ESTABLISHED 866
SYN_RECV 654
CLOSING 285
LAST_ACK 78

附录:
内核参数说明:

net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout = 30 表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
net.ipv4.tcp_keepalive_time = 1800 表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.ip_local_port_range = 1024  65000 表示用于向外连接的端口范围。缺省情况下很小:3276861000,改为102465000。
net.ipv4.tcp_max_syn_backlog = 8192 表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_tw_buckets = 5000 表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死(本站正好采用的是squid+apache,修改前面几项参数后time_wait连接数依旧没有减少,最后修改了此参数)。
net.ipv4.route.gc_timeout = 100 路由缓存刷新频率,当一个路由失败后多长时间跳到另一个
默认是300
net.ipv4.tcp_syn_retries = 1 对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。不应该大于255,默认值是5,对应于180秒左右。

TCP状态的变迁图:

状态描述:

CLOSED:无连接是活动的或正在进行
LISTEN:服务器在等待进入呼叫
SYN_RECV:一个连接请求已经到达,等待确认
SYN_SENT:应用已经开始,打开一个连接
ESTABLISHED:正常数据传输状态
FIN_WAIT1:应用说它已经完成
FIN_WAIT2:另一边已同意释放
ITMED_WAIT:等待所有分组死掉
CLOSING:两边同时尝试关闭
TIME_WAIT:另一边已初始化一个释放
LAST_ACK:等待所有分组死掉

TCP中TIME_WAIT的相关原理:

在socket的TIME_WAIT状态结束之前,该socket所占用的本地端口号将一直无法释放。编写过高TCP并发并且采用短连接方式进行通讯的软件程序的人,都可能体会到,这样的通讯系统在高并发高负载下运行一段时间后,就常常会出现做为客户端的程序无法向服务端建立新的socket连接的情况。此时用"netstat-anp"命令查看系统将会发现机器上存在大量处于TIME_WAIT状态的socket连接,并且占用大量的本地端口号。最后,当该机器上的可用本地端口号被占完,而旧的大量处于TIME_WAIT状态的socket尚未被系统回收时,就会出现无法向服务端创建新的socket连接的情况。此时的通讯系统几乎停转,空有再好的性能也发挥不出来。
如果能够在客户端程序主动关闭socket之前,让该socket的接收队列中仍保留一些数据(至少要有多余的一个字节的数据),然后调用close关闭,那么上述的无法向服务端创建新的socket连接的情况将不会出现。这是因为当socket的接收队列中仍有数据未被应用程序读走就被强行关闭时,操作系统(至少在笔者验证过的操作系统上的确如此)的TCP/IP协议栈驱动程序会在底层主动向服务端发送一个要求结束TCP连接的控制包,并将该TCP包头的flag控制字段中的RESET位置位,从而迅速结束了此TCP连接。这其实是操作系统对TCP连接断开的一种异常处理。而正常情况下(socket 的接收队列中无未读数据),当应该程序调用close关闭连接时,底层驱动程序向服务端发送的要求结束TCP连接的控制包头的flag控制字段中是将 FIN位置位,并且严格遵循上图所示的状态转换过程,最终到达TIME_WAIT状态并持续2*MSL的时间。

awk命令解释

netstat -n | awk '/^tcp/ ++state[$NF] END for(key in state) print key,"\\t",state[key]'
/^tcp/
滤出tcp开头的记录,屏蔽udp, socket等无关记录。
state[]
相当于定义了一个名叫state的数组
NF
表示记录的字段数,如上所示的记录,NF等于6
$NF
表示某个字段的值,如上所示的记录,$NF也就是$6,表示第6个字段的值,也就是TIME_WAIT
state[$NF]
表示数组元素的值,如上所示的记录,就是state[TIME_WAIT]状态的连接数
++state[$NF]
表示把某个数加一,如上所示的记录,就是把state[TIME_WAIT]状态的连接数加一
END
表示在最后阶段要执行的命令
for(key in state)
遍历数组
print key,"\\t",state[key]
打印数组的键和值,中间用\\t制表符分割,美化一下。

参考:https://blog.csdn.net/sctq8888/article/details/11180519

以上是关于系统如何实现Socket端口复用的主要内容,如果未能解决你的问题,请参考以下文章

端口复用技术简单了解;重用端口;socket复用端口

C++ UDP Socket端口复用

Java TCP/IP协议的Socket如何设置端口复用?

select实现IO多路复用服务器

TCP是如何实现可靠传输的?

PHP实现系统编程 --- 网络Socket及IO多路复用网摘