SO_REUSEADDR与SO_REUSEPORT平台差异性与测试
前些天,与另外一个项目组的同事聊天的时候,谈到他遇到的一个有意思的BUG。在window上启动服务器,然后客户端连接的时候收到一些奇怪的消息,查证了,原来是他自己的另一个工具也在相同的地址上监听,客户端连接到了后面这个工具程序上。我问他,是相同的IP和端口?他说是的,因为服务器代码和工具程序都设置了SO_REUSEADDR这个socket选项,所以可以在同样的地址上监听。
可是,在我的认知里面, SO_REUSEADDR这个选项并不是说让两个程序在相同地址(相同的IP 和 端口)上监听,而是说可以让处于time_wait状态的socket可以快速复用,搜了一下,看到的这篇文章,也是这么说的:
SO_REUSEADDR allows your server to bind to an address which is in a TIME_WAIT state. It does not allow more than one server to bind to the same address.
看了一下Linux manual,关于这个选项是这么描述的:
SO_REUSEADDR
Indicates that the rules used in validating addresses supplied
in a bind(2) call should allow reuse of local addresses. For
AF_INET sockets this means that a socket may bind, except when
there is an active listening socket bound to the address.
When the listening socket is bound to INADDR_ANY with a
specific port then it is not possible to bind to this port for
any local address. Argument is an integer boolean flag.
manual并没有提到time_wait的事情,但是明确指出,如果一个socket处于listen状态,那么同样的端口(port)是不能再次被绑定的(binding),不能binding,自然也不能再次listen,因此是不可能两个程序在相同的地址(IP PORT)上监听的。
于是自己用python在写了一个小的测试程序:
服务端代码:
# -*- coding: utf-8 -*-
import socket, sys
import time def main():
HOST, PORT = sys.argv[1], 8888 listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# print listen_socket.getsockopt(socket.SOL_SOCKET, socket.SO_EXECLUSIVEADDRUS) listen_socket.bind((HOST, PORT))
listen_socket.listen(10) print 'Serving on host %s port %s ...' %(HOST, PORT)
while True:
client_connection, client_address = listen_socket.accept()
request = client_connection.recv(1024)
print 'client ', request for i in range(5):
http_response = """\
hello
"""
client_connection.sendall(http_response)
time.sleep(3)
client_connection.close() if __name__ == '__main__':
main()
tcp_server.py
客户端代码:
import socket, sys def main():
server_address = ("localhost" if len(sys.argv) == 1 else sys.argv[1],8888)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(server_address)
print s.getpeername()
s.send('I AM CLIENT')
while True:
data = s.recv(1024)
print " %s received %s" % (s.getpeername(),data)
if not data:
print "closing socket ",s.getpeername()
s.close() if __name__ == '__main__':
main()
tcp_client.py
服务端代码设置了SO_REUSEADDR,在Linux下, 确实不能在相同的地址(IP, Port)上监听, 但是在windows上,却又是可以的。于是想到,这个选项可能与平台相关。
平台差异性
本文记录一下这个问答的要点,并用上面的小程序在各个平台(Linux, Mac, Windows)上进行测试。注意,本文只关注TCP、单播,事实上原问答还包括UDP、多播知识,感兴趣的读者可以自行阅读。
第零:一条tcp连接是一个五元祖: {<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
第一:SO_REUSEPORT和SO_REUSEADDR在不同的操作系统上行为是不一样的
第二:默认情况下,任意两个socket都无法绑定到相同的源IP地址和源端口, 0.0.0.0 (即INADDR_ANY )和所有其他地址冲突
第三:BSD系统下
SO_REUSEADDR 使得0.0.0.0 与 其他地址不冲突
SO_REUSEPORT允许你将多个socket绑定到相同的地址和端口, 但第一个启动的socket必须设置SO_REUSEPORT
第四:MacOS IOS 表现同 BSD
第五:Linux
SO_REUSEADDR 只要有socket处于listen状态, 就不能在同样的地址和端口上listen, 0.0.0.0 与其他所有地址冲突
只要监听前设置了SO_REUSEPORT(在Linux3.9版本之后可用) ,就可以在相同的(ip port)上监听
对于SO_REUSEPORT:为了阻止"port 劫持"(Port hijacking)有一个特别的限制,所有希望共享源地址和端口的socket都必须拥有相同的有效用户id(effective user ID);对于TCP监听socket,内核尝试将新的客户连接请求(由accept返回)平均的交给共享同一地址和端口的socket(监听socket)
第六:Android同Linux
第七:Windows
只有SO_REUSEADDR选项,没有SO_REUSEPORT。
设置SO_REUSEADDR 等价于BSD上设定了SO_REUSEPORT和SO_REUSEADDR,而且不管之前的端口是否设定了SO_REUSEADDR(存疑)
上述选项存在风险:因为允许一个应用程序从别的应用程序上"偷取"已连接的端口。因此在windows上加入了另一个socket选项: SO_EXECLUSIVEADDRUSE。设置了SO_EXECLUSIVEADDRUSE的socket确保一旦绑定成功,那么被绑定的源端口和地址就只属于这一个socket,其它的socket不能绑定,甚至他们使用了SO_REUSEADDR也没用。
测试
在后文涉及到的三个平台(Linux 、MacOS、Windows),都涉及到三个IP:127.0.0.1, 0.0.0.0,10.0.0.x(局域网IP)。使用的脚本如上(tcp_server.py, tcp_client.py),运行的时候需要简单修改tcp_server.py中第9、10行的注释,以便测试不同选项下的效果。
MAC
由于没有BSD系统,而且前文提到MacOS和BSD系统的表现是一样的,因此在这里实在MAC上测试
在不使用SO_REUSEADDR (此时未使用SO_REUSEPORT)时:

注意:first指第一条监听的socket,second指第二条希望在同样的端口(port)上监听的连接。兼容指第二条连接可以成功监听,不兼容则指第二条连接不能成功监听。下同
在使用SO_REUSEADDR(此时未使用SO_REUSEPORT)时:

在使用SO_REUSEADDR情况下,如果第一个scoket在0.0.0.0上监听,第二个scoket在127.0.0.1上监听。那么客户端使用127.0.0.1连接的时候会连接到第二个socket;使用10.0.0.x则会连接到第一个socket
使用SO_REUSEPORT(同时使用了SO_REUSEADDR):

如果两个socket都在127.0.0.1上监听,客户端也通过127.0.0.1去连接,那么客户端连接都会发被第二个socket accept, 笔者并发实验了几十次都是这样, 但并没有找到明确的官方文档说明是否是这样。
Linux


从上面两个测试可以看到,在linux下,是否使用SO_REUSEADDR并不影响两个socket的监听
使用SO_REUSEPORT(同时使用了SO_REUSEADDR):

如果两个socket都在127.0.0.1上监听,客户端也通过127.0.0.1去连接, 那么客户端连接会被操作系统分发到两个socket上,具体如下
客户端并发10次连接: for ((a=1;a<=10;a++)) ; do (python tcp_client.py 127.0.0.1 &); done
第一个socket accept了六次, 第二个socket accept了10次。
Windows
前面已经提到,windows下面只有SO_REUSEADDR选项,但其功能类似bsd系统下的SO_REUSEADDR与SO_REUSEPORT
在不使用SO_REUSEADDR时:

比如都在127.0.0.1 上监听时,第二个socket会报错: socket.error: [Errno 10048] 通常每个套接字地址(协议/网络地址/端口)
使用SO_REUSEADDR时:

上面也提到,如果第一个socket使用了SO_EXECLUSIVEADDRUSE选项,那么第二个连接即使使用了SO_REUSEADDR也无济于事,那么是否SO_EXECLUSIVEADDRUSE是默认开启的呢?但是在Python2.7中,socket并没有这个属性
查了一下MSDN,有附图清晰了说明了在window下SO_REUSEADDR与SO_EXECLUSIVEADDRUSE的关系,如下:

但为什么使用Python的时候 效果不一样呢,这个就没细究了
总结
本文测试了一下socket中SO_REUSEADDR与SO_REUSEPORT在各个平台下的差异性,一些结论只是实验结果,并没有查到官方权威定论,如果有差错,还请指正!

references
http://www.unixguide.net/network/socketfaq/4.11.shtml
http://man7.org/linux/man-pages/man7/socket.7.html
http://blog.chinaunix.net/uid-28587158-id-4006500.html
https://msdn.microsoft.com/en-us/library/windows/desktop/cc150667(v=vs.85).aspx
SO_REUSEADDR与SO_REUSEPORT平台差异性与测试的更多相关文章
- Linux下端口复用(SO_REUSEADDR与SO_REUSEPORT)
freebsd与linux下bind系统调用小结: 只考虑AF_INET的情况(同一端口指ip地址与端口号都相同) freebsd支持SO_REUSEPORT和SO_REUSEADDR选项,而l ...
- SO_REUSEADDR 和 SO_REUSEPORT
大部分内容来自stackoverflow上的回答:Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ? Do they m ...
- SO_REUSEADDR和SO_REUSEPORT异同
文章内容来源于stackoverflow上的回答,写的很详细http://stackoverflow.com/questions/14388706/socket-options-so-reuseadd ...
- Cocos2d-x 关于在iOS平台真机测试的一些注意
下面简单记录一下在最近cocos2d-x项目在iOS平台真机测试和模拟器测试中遇到的一些要注意的地方(使用ipod): 1.图片大小 游戏中基本上都是会用到图片,那么在使用图片的时候要特别注意图片的s ...
- Android平台下渗透测试工具大集合
Android平台下渗透测试工具大集合 分享一个google的项目,各种Android下的渗透测试工具. Ad Network Detector (1.2): http://market.androi ...
- tlplayer,wzplayer所有平台通用加密测试视频
此视频文件为通用版本,支持tlplayer,wzplayer,能在ios,android,windows,mac等平台上使用,发布此文件紧为方便用户测试. 下载地址:http://www.coolra ...
- 微服务架构 - 离线部署k8s平台并部署测试实例
一般在公司部署或者真实环境部署k8s平台,很有可能是内网环境,也即意味着是无法连接互联网的环境,这时就需要离线部署k8s平台.在此整理离线部署k8s的步骤,分享给大家,有什么不足之处,欢迎指正. 1. ...
- CentOS 6.7平台nginx压力测试(ab/webbench)
压力测试工具一:webbench 1.安装 wget http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz tar zxvf w ...
- Visual Studio平台安装及测试
一.VS安装 图1.1 图1.2 二.单元测试练习 题目:课本22~25页单元测试练习 1.创建一个c#类(具体如下:打开VS2010,然后点击VS界面上左上角的文件按钮,然后点击文件—新建—项目,就 ...
随机推荐
- [leetcode-566-Reshape the Matrix]
In MATLAB, there is a very useful function called 'reshape', which can reshape a matrix into a new o ...
- DOCKER 从入门到放弃(二)
搜索镜像 从docker官方镜像仓库搜索镜像 docker search [OPTIONS] TERM OPTIONS: --automated :只显示自动创建的镜像,默认值为fasle --fil ...
- JS数组及内置对象
[JS中的数组]1.数组的概念:数组是在内存中连续存储多个有序元素的结构元素的顺序,称为下标,通过下标查找对应元素.2.数组的声明: ① 字面量声明: var arr1 = [];JS中同一数组,可以 ...
- kotlin的一些特性介绍和与java C#的简单对比
前言 这是我之前在知乎上的一些回答的汇总,感觉还是博客园写这些东西方便一点,也算是理下我的一些思路,现将文章整理后,发布在园子里. 为何是kotlin: 很多人对kt没有一个正确的定位,可能大家第一反 ...
- KBEngine简单RPG-Demo源码解析(1)
一:环境搭建1. 确保已经下载过KBEngine服务端引擎,如果没有下载请先下载 下载服务端源码(KBEngine): https://github.com ...
- win7-x64安装mysql5.7.11(官方zip版)
1.下载官方安装包(http://www.mysql.com/downloads/),此zip包是没有安装器的(*.msi),也没有辅助配置的自动程序. 2.解压zip包,将文件放入指定目录,如:D: ...
- git分支的使用
本文章假定你已经接触了一些git的基本概念和基本的操作知识 这里先贴出关于分支的一些常用命令 git branch /*查看所有分支*/git branch <branch-name> / ...
- [CF161D]Distance in Tree-树状dp
Problem Distance in tree 题目大意 给出一棵树,求这棵树上有多少个最短距离为k的点对. Solution 这个题目可以用点分治来做,然而我到现在还是没有学会点分治,所以只好用树 ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- linux DNS 问题
今天准备爬虫51job时候,发现ping不通外网了,ping 了一下IP,都是OK的,只是host不通. 呵呵,就一DNS问题,好的.第一步,开始检查配置文件 cat /etc/sysconfig/n ...