前言
在网上看到很多人说在socket中设置SO_REUSEADDR选项的用途是端口复用/地址复用,具体来说就是让处于time_wait状态的socket可以快速重新绑定原来的ip+port。对于服务端而言,设置SO_REUSEADDR作用就是在重启服务器时能马上重新绑上原来的端口,方便调试。不然就绑定会失败,提示ADDR已经在使用中——那只好等等再重试了,麻烦。
但我在Windows系统下的试验结果与这种说法不一致,再继续深入搜索下去,发现了关于SO_REUSEADDR与SO_REUSEPORT平台差异性的说法,又在Windows中试验了一下(其他平台没测试),基本一致,现总结如下。
注:以下结论只针对TCP连接。
BSD系统
SO_REUSEADDR选项作用
- 让处于time_wait状态的socket可以快速复用原ip+port(网上广泛流传的说法)
- 使得0.0.0.0(ipv4通配符地址)与其他地址(127.0.0.1和10.0.0.x)不冲突(详见此文章)
SO_REUSEPORT选项作用
能够让多个socket同时绑定完全相同的ip+port,但不能实现负载均衡,似乎请求都是由后面绑定的socket接收(根据[SO_REUSEADDR与SO_REUSEPORT平台差异性]的实验结果(https://www.cnblogs.com/xybaby/p/7341579.html))。
Linux系统
SO_REUSEADDR选项作用
能让处于time_wait状态的socket可以快速复用原ip+port,不能解决监听端(服务端)通配符IP地址的冲突问题,但非监听端(客户端)可以解决。
SO_REUSEPORT选项作用
能够让多个socket同时绑定完全相同的ip+port,且能实现负载均衡。
内核将尝试在这些socket之间平均分配收到的连接请求,即Linux尝试了进行流量分配上的优化。比如一个简单的服务器进程的几个不同实例可以方便地使用SO_REUSEPORT来实现一个简单的负载均衡,而且这个负载均衡有内核负责, 对程序来说完全免费!
以下为本人试验结果,测试代码在这里。
Windows
- windows 下处于time_wait状态的socket默认就可以快速复用原ip+port,无需进行任何设置。即服务器重启时可以立即绑定原ip+port,不用设置socket选项(原因)。
- 没有SO_REUSEPORT选项,会报错。
- 只有SO_REUSEADDR选项,但其效果与BSD系统下的SO_REUSEPORT类似,即作用是同时绑定完全相同的ip+port,但不能实现负载均衡。
都设置SO_REUSEADDR
完全相同的ip+port可以同时绑定成功,但只有最先绑定的socket能接收请求,后面绑定的能监听,但接收不到请求。
不都设置SO_REUSEADDR
完全相同的ip+port无法绑定成功
不设置SO_REUSEADDR
(本机具体IP地址)172.20.10.5:8003
(localhost)127.0.0.1:8003
(通配符IP地址)0.0.0.0:8003
三者可以同时绑定成功
启动三个服务:前两个地址只能接收客户端请求的等于自己地址的连接,0.0.0.0接收不到发给前两个地址的连接。
启动两个服务,0.0.0.0和172.20.10.5:0.0.0.0可以接收127.0.0.1的连接,172.20.10.5自己接收自己的。
启动两个服务,0.0.0.0和127.0.0.1:0.0.0.0可以接收172.20.10.5的连接,127.0.0.1自己接收自己的。
只启动一个服务,最好绑定0.0.0.0:8003,因为0.0.0.0可以接收发给其他两个地址的连接,而其他两个地址都无法接收不等于自己地址的连接(即使只有一个服务)。
由于:
- windows 下处于time_wait状态的socket默认就可以快速复用原ip+port,无需进行任何设置。
- 无论是否设置SO_REUSEADDR,对于完全相同的ip+port,无论同时有多少个客户端请求连接,只有最先绑定的服务端能接收请求,后面想绑定同样ip+port的服务端要么无法绑定,要么绑定了也接收不到请求(除非最先绑定的服务端已经关闭,不再监听,且客户端重新发起一个新的TCP连接)。
所以,个人认为在Windows下对服务端socket设置SO_REUSEADDR没什么意义。
在eventlet.listen源码中找到的佐证
API:eventlet.listen
位置:eventlet库中convenience.py的listen函数
在Windows平台上不设置SO_REUSEADDR选项(估计也是因为觉得没意义),Windows不支持SO_REUSEPORT选项。