最近看到一个面试问题:多个服务器进程能否同时侦听一个TCP端口号?
有经验的同学应该遇到过这样的报错”Address already in use” ,所以我的答案是:IP不一样就能、IP一样就不能
但是看作者的答案是:默认(Default)情况下不可以,但是如果配置SO_REUSEADDR,是可以的。
翻阅多篇文章会发现SO_REUSEADDR、SO_REUSEPORT经常一起出现,这两个参数分别作用于服务端bind阶段和listen阶段。且Linux和Unix(FreeBSD)这两个选项有不一样的作用故写下这篇博客以作记录。
网络编程中一个TCP连接分为服务端和客户端,服务端需要四步,客户端需要两步。
服务端四步为:
- 调用socket函数,建立一个套接字
- 调用bind函数,将套接字绑定到一个IP+PORT地址(不执行也行,系统随机绑定端口)
- 调用listen函数,申请和初始化全连接队列和半连接队列,监听连接请求
- 调用accept函数,复制套接字处理请求
客户端两步为:
- 调用socket函数,建立一个套接字
- 调用connect函数使用该套接字与服务器进行连接
首先我们先来看下Linux环境下这两个参数的作用。
Linux下SO_REUSEADDR、SO_REUSEPORT选项作用
查看man 7 socket
中SO_REUSEADDR、SO_REUSEPORT含义如下:
1 | SO_REUSEADDR |
下面通过实验来验证上面的含义
Linux实验环境:
1 | $ uname -a |
不同SO_REUSEADDR、SO_REUSEPORT值进程Bind Socket情况
脚本:
Go语言中net.Listen包括了socket创建、地址绑定、开启监听三个阶段不方便测试只bind不listen的情况,所以选择使用Python语言。
1 | import sys, socket, time |
结果
SocketA | SocketB | SO_REUSEADDR | SO_REUSEPORT | Result |
---|---|---|---|---|
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 0 | OK |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 0 | OK |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 0 | OK |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
注意:
- SO_REUSEADDR、SO_REUSEPORT的1指socketA和socketB要同时设置为1
- Result是指后一个socket bind时会不会失败。
不同SO_REUSEADDR、SO_REUSEPORT值进程Listen Socket情况
脚本:
1 | import sys, socket |
结果
SocketA | SocketB | SO_REUSEADDR | SO_REUSEPORT | Result |
---|---|---|---|---|
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 0 | ADDR_ALREADY_IN_USE |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
FreeBSD下SO_REUSEADDR、SO_REUSEPORT选项作用
查看man setsockopt
中SO_REUSEADDR、SO_REUSEPORT含义如下:
1 | SO_REUSEADDR indicates that the rules used in validating addresses |
下面通过实验来验证上面的含义
FreeBSD实验环境:
1 | $ uname -a |
不同SO_REUSEADDR、SO_REUSEPORT值进程Bind Socket情况
脚本:
1 | import sys, socket, time |
结果
SocketA | SocketB | SO_REUSEADDR | SO_REUSEPORT | Result |
---|---|---|---|---|
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 0 | ADDR_ALREADY_IN_USE |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 0 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
不同SO_REUSEADDR、SO_REUSEPORT值进程Listen Socket情况
脚本:
1 | import sys, socket |
结果
SocketA | SocketB | SO_REUSEADDR | SO_REUSEPORT | Result |
---|---|---|---|---|
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 1 | 0 | ADDR_ALREADY_IN_USE |
0.0.0.0:8080 | 0.0.0.0:8080 | 1 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 0.0.0.0:8080 | 1 | 0 | OK |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 1 | Ok |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 1 | Ok |
172.22.147.210:8080 | 172.22.147.210:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
0.0.0.0:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
172.22.147.210:8080 | 0.0.0.0:8080 | 0 | 0 | ADDR_ALREADY_IN_USE |
结论
Linux:
设置SO_REUSEADDR可以使多个进程bind到同一个ip+port
- 设置SO_REUSEPORT可以使多个进程listen同一个ip+port,内核做负载均衡分配到具体进程。
FreeBSD:
设置SO_REUSEADDR 则表示0.0.0.0、172.22.147.210是不同的ip地址,可以同时listen 0.0.0.0:port和单个ip:port,但是不能listen同一个ip+port
- 设置SO_REUSEPORT可以使多个进程listen同一个ip+port
参考:
深入理解Linux端口重用这一特性
一个进程绑定了端口号后,创建子进程(fork),子进程是不是和父进程绑定了同一个端口号?
TCP协议细节系列(9):深入解析Linux下so_reuseaddr和so_reuseport选项