SO_REUSEADDR和SO_REUSEPORT的行为发生了改变吗?

7

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

Socket选项SO_REUSEADDR和SO_REUSEPORT有什么区别?它们在所有主要操作系统中的含义相同吗?

多播地址

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

MacOS X

从其根本上说,MacOS X只是一个基于BSD代码的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

1
我也遇到了同样的问题,并确认需要设置SO_REUSEPORT以及SO_REUSEADDR。不知道为什么会这样,但我想我要么添加一个针对OS X的特定解决方案,要么在Linux上也设置它。 - ctrlc-root
2个回答

3
在早期版本的Mac OS X中,通配符绑定的工作方式与此处描述的相同。您引用的描述是针对“多播地址”的。您的测试代码没有使用多播地址,因此应用不同的描述(来自同一来源):SO_REUSEPORT是大多数人会期望SO_REUSEADDR具有的功能。基本上,SO_REUSEPORT允许您将任意数量的套接字绑定到完全相同的源地址和端口,只要所有先前绑定的套接字在绑定之前也设置了SO_REUSEPORT即可...

您的测试代码确认了这一点。


我明白了,是的通配符地址不是多播地址... 但早期版本仍然有不同的行为,所以它改变了。 - Alexander Meißner

1

太神奇了!它刚刚发生了。仍然存在使用SO_REUSEADDR而不是SO_REUSEPORT的问题。我认为后者在大多数*nix基本操作系统上都有效。但一旦陷入困境,摆脱这种情况将不容易,特别是在Mac OS上,如果您之前没有阅读答案的话。

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

lsof -i:<Port used by you>

然后它会给你带来一行,其中包括 PID。 用 -9 杀掉它,然后通过 SO_REUSEPORT 更改你的代码。就这样!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接