博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SO_REUSEADDR 和 SO_REUSEPORT
阅读量:5795 次
发布时间:2019-06-18

本文共 3367 字,大约阅读时间需要 11 分钟。

    大部分内容来自stackoverflow上的回答:

    由于现有的操作系统上的socket都来自BSD socket,且每种操作系统都后续进行了相应的改变。下面先说 BSD socket中的行为,在最后一节单独介绍linux系统下的socket特定行为。

socket五元组

    一个连接由五元组(src_ip, src_port, protocol, dst_ip, dst_port)来唯一确定,系统通过tcp/udp报头+IP报头中的这些信息来确定数据包来自远端的哪个进程,以及数据包是发往本地的哪个进程。

    对应到socket编程,一个连接由本地和远端的两个socket来确定。
    socket通过bind()指定本地使用的ip和端口(如果没有bind,对于udp或tcp客户端系统会随机分配);tcp客户端使用 connect()连接到远端的ip和端口,tcp服务器通过accept()获得远端的ip和端口,无连接的udp,通过udp报文获得远端的ip和端 口,有连接的udp,通过connect()来确定远端的ip和端口。

任意端口和IP

    在 socket进行bind()操作时,可以指定“任意”ip或端口,其中“任意”ip指系统可选择本机上所有网卡上的ip,或者127.0.0.1(系统 会根据本地到对端之间的路由来合理选择使用哪个网卡上的ip,或127.0.0.1);“任意”端口是指让系统来随机选择一个可用的端口。

TIME_WAIT 和 Linger time

     一个socket有一个发送缓冲区,当调用send()函数成功后,这并不意味着所有数据都真正被发送出去了,它只意味着数据都被送到了发送缓冲区中。对 于UDP socket来说,如果不是立刻发送的话,数据通常也会很快的发送出去,但对于TCP socket,在数据加入到缓冲区和真正被发送出去之间的时延会相当长。这就导致当我们close一个TCP socket的时候,可能在发送缓冲区中保存着等待发送的数据(由于send()成功返回,因此你也许认为数据已经被发送了)。

    所以当我们close一个TCP socket的时候,如果它仍然有数据等待发送,那么该socket会进入TIME_WAIT状态。这种状态将持续到数据被全部发送或者发生超时。
    在内核彻底关闭socket之前等待的总时间(不管是否有数据在发送缓冲区中等待发送)叫做Linger Time。Linger Time在大部分系统上都是一个全局性的配置项而且在默认情况下时间相当长(在大部分系统上是两分钟)。socket选项SO_LINGER可以设置 Linger time,但强烈不建议这样做。

SO_REUSEADDR

    由 于Linger time的存在,当我们close一个TCP socket A之后,再用另一个socket B去bind相同的ip和port,就会出现 EADDRINUSE 错误。这是对socket B在bind之前先去设置 SO_REUSEADDR,就可以绑定成功。注意:谁要去bind,就是谁去设置 SO_REUSEADDR。

 注意,SO_REUSEADDR 只有在socket A close之后,对socket B 设置 SO_REUSEADDR 选项,然后bind才能成功;如果socket A 先bind,然后socket B 在socket 没有close时就bind到相同的端口和ip,仍然会出错! 由于“任意”ip和端口的存在,如何判断两个ip+端口是否相同情况就比较复杂,具体如下表所示(socket A先bind,在未close之前socket B bind,结果):

SO_REUSEADDR socketA socketB Result
ON/OFF 192.168.0.1:21 192.168.0.1:21 Error (EADDRINUSE)
ON/OFF 192.168.0.1:21 10.0.0.1:21 OK
ON/OFF 10.0.0.1:21 192.168.0.1:21 OK
OFF 0.0.0.0:21 192.168.1.0:21 Error (EADDRINUSE)
OFF 192.168.1.0:21 0.0.0.0:21 Error (EADDRINUSE)
ON 0.0.0.0:21 192.168.1.0:21 OK
ON 192.168.1.0:21 0.0.0.0:21 OK
ON/OFF 0.0.0.0:21 0.0.0.0:21 Error (EADDRINUSE)
SO_REUSEPORT

    SO_REUSEADDR 并不能使两个socket同时使用本地相同的ip和端口,SO_REUSEPORT可以实现这个目的。

    为了实现多个socket同时使用相同的ip和端口,要求所有这些socket在bind之前都设置SO_REUSEPORT选项。

connect出现EADDRINUSE错误

    如 果对端有多个socket同时绑定了相同的ip IP1和端口PORT1,如果本地有socket A 和 B都绑定在了相同的本地ip IP2和端口 PORT2,那么socket A 先connect 到对端的(IP1, PORT1),然后socket B再connect到对端的(IP1, PORT1),则会出现 EADDRINUSE错误,因为这是 connection 五元组出现了重复。

多播

    对 多播地址来说,SO_REUSEADDR的含义发生了改变,因为它允许多个socket绑定到完全一样的多播地址和端口,也就是说,对多播地址 SO_REUSEADDR的行为与SO_REUSEPORT对单播地址完全一样。事实上,对于多播地址,对SO_REUSEADDR和 SO_REUSEPORT的处理完全一样,对所有多播地址,SO_REUSEADDR也就意味着SO_REUSEPORT。

linux的 SO_REUSEADDR 和 SO_REUSEPORT

    在linux 3.9之前,只存在选项SO_REUSEADDR。且该选项的行为大体上与BSD一样,除了两个差别:

(1)当一个监听(listening)TCP socket绑定到通配地址和一个特定的端口,无论其它的socket或者是所有的socket(包括监听socket)都设置了 SO_REUSEADDR,其它的TCP socket都无法绑定到相同的端口(BSD中可以),就更不用说使用一个特定地址了。这个限制并不用在非监听TCP socket上,当一个监听socket绑定到一个特定的地址和端口组合,然后另一个socket绑定到通配地址和相同的端口,这样是可行的。
(2)当把SO_REUSEADDR用在UDP socket上时,它的行为与BSD上SO_REUSEPORT完全相同,因此两个UDP socket只要都设置了SO_REUSEADDR,那么它们可以绑定到相同的地址和端口。
    Linux 3.9加入了SO_REUSEPORT。这个选项允许多个socket(TCP or UDP)不管是监听socket还是非监听socket只要都在绑定之前都设置了它,那么就可以绑定到完全相同的地址和端口。为了阻止"port 劫持"(Port hijacking)有一个特别的限制:所有希望共享源地址和端口的socket都必须拥有相同的有效用户id(effective user ID)。因此一个用户就不能从另一个用户那里"偷取"端口
    另外,内核在处理SO_REUSEPORT socket的时候使用了其它系统上没有用到的"特别魔法":
对于UDP socket,内核尝试平均的转发数据报,对于TCP监听socket,内核尝试将新的客户连接请求(由accept返回)平均的交给共享同一地址和端口 的socket(监听socket)。这意味着在其他系统上socket收到一个数据报或连接请求或多或少是随机的,但是linux尝试优化分配。例如:一个简单的服务器程序的多个实例可以使用SO_REUSEPORT socket实现一个简单的负载均衡,因为内核已经把复制的分配都做了。

 

转载地址:http://nbbfx.baihongyu.com/

你可能感兴趣的文章
IIS7/8下提示 HTTP 错误 404.13 - Not Found 请求筛选模块被配置为拒绝超过请求内容长度的请求...
查看>>
http返回状态码含义
查看>>
响应式网站对百度友好关键
查看>>
洛谷P2179 骑行川藏
查看>>
(十八)js控制台方法
查看>>
VB关键字总结
查看>>
android代码生成jar包并混淆
查看>>
一个不错的vue项目
查看>>
屏蔽指定IP访问网站
查看>>
根据毫秒数计算出当前的“年/月/日/时/分/秒/星期”并不是件容易的事
查看>>
python的图形模块PIL小记
查看>>
shell变量子串
查看>>
react报错this.setState is not a function
查看>>
Centos7.x:开机启动服务的配置和管理
查看>>
HTML5 浏览器返回按钮/手机返回按钮事件监听
查看>>
xss
查看>>
iOS:百度长语音识别具体的封装:识别、播放、进度刷新
查看>>
JS获取服务器时间并且计算距离当前指定时间差的函数
查看>>
华为硬件工程师笔试题
查看>>
jquery居中窗口-页面加载直接居中
查看>>