SO_REUSEADDR和SO_REUSEPORT的行为发生了变化?

时间:2015-09-18 21:10:34

标签: macos sockets

在旧版本的Mac OS X中,通配符绑定的工作方式如下所述:

Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ? Do they mean the same across all major operating systems?

  

多播地址

     

SO_REUSEADDR的含义会更改多播地址,因为它允许将多个套接字绑定到源多播地址和端口的完全相同的组合。换句话说,对于多播地址,SO_REUSEADDR的行为与单播地址的SO_REUSEPORT完全相同。实际上,代码对于多播地址同样对待SO_REUSEADDR和SO_REUSEPORT,这意味着您可以说SO_REUSEADDR对所有多播地址都意味着SO_REUSEPORT,反之亦然。

  

MacOS X

     

在其核心,MacOS X只是一个BSD风格的UNIX,基于BSD代码的相当晚的分支,甚至与Mac OS 10.3版本的FreeBSD 5同步。这就是为什么MacOS X提供与BSD相同的选项,它们的行为方式与BSD相同。

但在10.10.5中,我在测试网络库时发现了一个变化。

两个未绑定(通配符)UDP套接字不能再共享同一个端口(errno = EADDRINUSE),即使设置了SO_REUSEADDR也是如此。 必须在两者上设置SO_REUSEPORT,这对我来说是一个谜。

使用这个简单的测试代码可以重现:

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

int main() {
    for(int p = 0; p < 4; ++p) {
        printf("Flags set: ");
        if(p&1) printf("SO_REUSEADDR ");
        if(p&2) printf("SO_REUSEPORT");
        printf("\n");

        int handles[2];
        bool success = true;
        for(int i = 0; i < sizeof(handles)/sizeof(int); ++i) {
            handles[i] = socket(AF_INET, SOCK_DGRAM, 0);

            int flag = 1;
            if((p&1) && setsockopt(handles[i], SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) {
                printf("Setsockopt %d, SO_REUSEADDR failed with errno\n", i, errno);
                success = false;
                break;
            }
            if((p&2) && setsockopt(handles[i], SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag)) == -1) {
                printf("Setsockopt %d, SO_REUSEPORT failed with errno\n", i, errno);
                success = false;
                break;
            }

            struct sockaddr_in addr;
            memset(&addr, 0, sizeof(addr));
            addr.sin_family = AF_INET;
            addr.sin_port = 2000; // does not matter as long as it is currently free
            addr.sin_addr.s_addr = 0; // wildcard address

            if(bind(handles[i], (struct sockaddr*)&addr, sizeof(addr)) == -1) {
                printf("Bind %d failed with errno %d\n", i, errno);
                success = false;
                break;
            }
        }
        if(success)
            printf("Alright\n");

        for(int i = 0; i < sizeof(handles)/sizeof(int); ++i)
            close(handles[i]);
        printf("\n");
    }

    return 0;
}

哪个输出:

Flags set: 
Bind 1 failed with errno 48

Flags set: SO_REUSEADDR 
Bind 1 failed with errno 48

Flags set: SO_REUSEPORT
Alright

Flags set: SO_REUSEADDR SO_REUSEPORT
Alright

2 个答案:

答案 0 :(得分:2)

  

在旧版Mac OS X中,通配符绑定的工作方式与描述的相同   这里:

     

Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ? Do they mean the same across all major operating systems?

     
    

组播地址
    ...

  

您引用的描述适用于多播地址。您的测试代码不使用multicast address。因此,适用不同的描述(来自同一来源):

  

SO_REUSEPORT

     

SO_REUSEPORT是大多数人所期望的SO_REUSEADDR。   基本上,SO_REUSEPORT允许您绑定任意数量的   只要插槽到完全相同的源地址和端口   所有之前的绑定套接字在绑定之前也设置了SO_REUSEPORT。 ...

您的测试代码确切地证实了这一点。

答案 1 :(得分:1)

多么可恶!只是发生了蜜蜂。使用SO_REUSEADDR代替SO_REUSEPORT仍然存在问题。我认为后者适用于大多数基于* nix的操作系统。但是一旦陷入困境,要想摆脱困境就很难了,如果您以前没有读过答案,尤其是在Mac OS上。

使用套接字时,您当然知道端口号。打开终端,执行以下命令。

lsof -i:<Port used by you>

这将为您带来一行包含PID的行。 只需使用-9杀死它,然后通过SO_REUSEPORT更改代码即可。仅此而已!