【问题标题】:Behavior of SO_REUSEADDR and SO_REUSEPORT changed?SO_REUSEADDR 和 SO_REUSEPORT 的行为改变了吗?
【发布时间】:2015-12-16 03:19:32
【问题描述】:

在旧版本的 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 中,我在测试我的网络库时发现了一个变化。

即使设置了 SO_REUSEADDR,两个未绑定(通配符)UDP 套接字也不能再共享同一个端口(errno=EADDRINUSE)。 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

【问题讨论】:

  • 我刚刚遇到了同样的问题,可以确认除了SO_REUSEADDR 之外,您还需要设置SO_REUSEPORT。不知道为什么会这样,但我想我要么添加一个特定于 OS X 的解决方法,要么也将它设置在 Linux 上。

标签: macos sockets


【解决方案1】:

在旧版本的 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。 …

您的测试代码完全证实了这一点。

【讨论】:

  • 我明白了,是的,通配符地址不是多播地址...但是早期版本仍然有不同的行为,所以它改变了。
【解决方案2】:

真是个黑客!它只是被招致了。使用SO_REUSEADDR 而不是SO_REUSEPORT 仍然存在问题。我认为后者适用于大多数基于 *nix 的操作系统。但是一旦你陷入困境,如果你之前没有阅读过答案,尤其是在 Mac OS 上,摆脱这种情况并不容易。

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

lsof -i:<Port used by you>

然后它会为您带来一条包含PID 的行。 只需使用-9 杀死它,然后通过SO_REUSEPORT 更改您的代码。就是这样!

【讨论】:

    猜你喜欢
    • 2023-03-03
    • 2013-01-01
    • 2019-01-30
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    • 2020-07-15
    • 1970-01-01
    相关资源
    最近更新 更多