SO_REUSEADDR 和 SO_REUSEPORT

大部分内容来自stackoverflow上的回答:Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ? Do they mean the same across all major operating systems?
    由于现有的操作系统上的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实现一个简单的负载均衡,因为内核已经把复制的分配都做了。

时间: 2024-11-03 20:54:33

SO_REUSEADDR 和 SO_REUSEPORT的相关文章

SO_REUSEADDR与SO_REUSEPORT平台差异性与测试

前些天,与另外一个项目组的同事聊天的时候,谈到他遇到的一个有意思的BUG.在window上启动服务器,然后客户端连接的时候收到一些奇怪的消息,查证了,原来是他自己的另一个工具也在相同的地址上监听,客户端连接到了后面这个工具程序上.我问他,是相同的IP和端口?他说是的,因为服务器代码和工具程序都设置了SO_REUSEADDR这个socket选项,所以可以在同样的地址上监听. 可是,在我的认知里面, SO_REUSEADDR这个选项并不是说让两个程序在相同地址(相同的IP 和 端口)上监听,而是说可

SO_REUSEADDR和SO_REUSEPORT异同

文章内容来源于stackoverflow上的回答,写的很详细http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t      虽然不同的系统上socket的实现方式有一些差异,但都来源于对BSD socket的实现,因此在讨论其它系统之前了解BSD socket的实现是非常有益的.首先我们需要了解一些基本知识

socket常见选项之SO_REUSEADDR,SO_REUSEPORT

目录 SO_REUSEADDR time-wait SO_REUSEPORT SO_REUSEADDR 一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用 SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用 server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项 TCP,先调用close()的一方会进入TIME_WAIT状态 SO_REUSEADDR提

SO_REUSEPORT和SO_REUSEADDR与socket编程中那些关于内核自动分配的...

前言: 本文分为三个章节,第一个章节主要是翻译总结汇总一位国外的老兄在Stack Overflow上的回答,但实际上Linux发展这么多年,文中的知识点已经过时且不准确了, 在第二章中通过实验,有更加准确的描述.但是,第一章节也不是全然无用,至少在了解SO_REUSEPORT和SO_REUSEADDR的发展上是有帮助的. 在第三章节中,做实验过程中需要验证一些其他的知识点,因此在这里做一个汇总. wxy:其实就是我研究完才发现文章写的不对,又不想浪费自己的研究成果,哈哈哈哈哈,hianghian

Linux网络编程socket选项之SO_LINGER,SO_REUSEADDR

from http://blog.csdn.net/feiyinzilgd/article/details/5894300 Linux网络编程中,socket的选项很多.其中几个比较重要的选项有:SO_LINGER(仅仅适用于TCP,SCTP), SO_REUSEADDR. SO_LINGER 在默认情况下,当调用close关闭socke的使用,close会立即返回,但是,如果send buffer中还有数据,系统会试着先把send buffer中的数据发送出去,然后close才返回. SO_L

TCP套接字端口复用SO_REUSEADDR

下面建立的套接字都是tcp套接字 1.进程创建监听套接字socket1,邦定一个指定端口,并接受了若干连接.那么进程创建另外一个套接口socket2,并试图邦定同一个端口时候,bind错误返回“Address already in use”(即使使用了SO_REUSEADDR). 2.进程创建监听套接字,邦定一个指定端口,并接受了若干连接,为每个连接创建子进程为连接服务.杀死监听套接字所在进程,然后重新启动.重新启动的进程调用bind重新建立监听套接字.这次邦定只有在bind前指定了SO_REU

linux socket中的SO_REUSEADDR

Welcome to the wonderful world of portability... or rather the lack of it. Before we start analyzing these two options in detail and take a deeper look how different operating systems handle them, it should be noted that the BSD socket implementation

SO_REUSEADDR

服务器socketstreamtcpc 原贴地址:http://topic.csdn.net/u/20090103/16/a0414edb-b289-4c72-84da-39e155e8f4be.html 如下演示程序,程序目的是: 先准备好一个ServerSocket,监听端口8880, 然后建一个ClientSocket(受限于业务需要,必须在ServerSocket准备好后再建Client),也必须绑定同一端口8880, 问题是:为什么对ClientSocket bind(port 888

SO_REUSEADDR的作用

服务器socketstreamtcpc 原贴地址:http://topic.csdn.net/u/20090103/16/a0414edb-b289-4c72-84da-39e155e8f4be.html 如下演示程序,程序目的是: 先准备好一个ServerSocket,监听端口8880, 然后建一个ClientSocket(受限于业务需要,必须在ServerSocket准备好后再建Client),也必须绑定同一端口8880, 问题是:为什么对ClientSocket bind(port 888