luk*_*ash 6 c sockets macos raw-sockets
考虑这段代码:
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#define SERVADDR "::1"
#define PORT 12345
int main() {
int sd = -1;
if ((sd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
fprintf(stderr, "socket() failed: %d", errno);
exit(1);
}
int flag = 1;
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) {
fprintf(stderr, "Setsockopt %d, SO_REUSEADDR failed with errno %d\n", sd, errno);
exit(2);
}
if(setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag)) == -1) {
fprintf(stderr, "Setsockopt %d, SO_REUSEPORT failed with errno %d\n", sd, errno);
exit(3);
}
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(23456);
if(bind(sd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
fprintf(stderr, "Bind %d failed with errno %d: %s\n", sd, errno, strerror(errno));
exit(4);
}
struct sockaddr_in6 server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, SERVADDR, &server_addr.sin6_addr);
server_addr.sin6_port = htons(PORT);
if (connect(sd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
fprintf(stderr, "Connect %d failed with errno %d: %s\n", sd, errno, strerror(errno));
exit(5);
}
printf("Seems like it worked this time!\n");
close(sd);
}
Run Code Online (Sandbox Code Playgroud)
很简单:
SO_REUSEADDRSO_REUSEPORT23456::1端口12345奇怪的是,在 MacOS 上连续运行此命令会导致以下结果:
$ for i in {1..5}; do ./ipv6; done
Seems like it worked this time!
Connect 3 failed with errno 48: Address already in use
Connect 3 failed with errno 48: Address already in use
Connect 3 failed with errno 48: Address already in use
Connect 3 failed with errno 48: Address already in use
$
Run Code Online (Sandbox Code Playgroud)
虽然在 Linux 上运行它似乎工作得很好:
$ for i in {1..5}; do ./ipv6; done
Seems like it worked this time!
Seems like it worked this time!
Seems like it worked this time!
Seems like it worked this time!
Seems like it worked this time!
$
Run Code Online (Sandbox Code Playgroud)
我在端口上有一个侦听器12345:
$ nc -6 -l -v -p12345 -k
Run Code Online (Sandbox Code Playgroud)
这不限于 IPv6,尝试了与 IPv4 相同的操作 - 相同的行为。
有人能解释一下吗?
我之前以为它失败了,bind()但它失败了connect()。
根据SO_REUSEADDR 和 SO_REUSEPORT 有何不同?,这适用于 BSD:
因此,如果您将相同协议的两个套接字绑定到相同的源地址和端口,并尝试将它们连接到相同的目标地址和端口,
connect()实际上会失败,并显示您尝试连接的第二个套接字的错误EADDRINUSE,这意味着具有相同的五个值元组的套接字已连接。
所以这就是为什么它不起作用的原因。如果这怎么可能在 Linux 上实际工作,那还有什么意义呢?
我当然希望在 MacOS 上完成这项工作,但目前我觉得这可能不太可能——但我仍然想了解 Linux 是如何做到这一点的。
是的,Linux 的实现与大多数其他操作系统不同。您可以在这里找到详尽的解释。引用具体部分:
Linux 3.9 也向 Linux 添加了选项 SO_REUSEPORT。此选项的行为与 BSD 中的选项完全相同,并且只要所有套接字在绑定它们之前设置了此选项,就允许绑定到完全相同的地址和端口号。
然而,其他系统上的 SO_REUSEPORT 仍然有两个区别:
- 为了防止“端口劫持”,有一个特殊的限制:所有想要共享相同地址和端口组合的套接字必须属于共享相同有效用户 ID 的进程!因此一个用户无法“窃取”另一用户的端口。这是一些特殊的魔法,可以在某种程度上补偿缺少的 SO_EXCLBIND/SO_EXCLUSIVEADDRUSE 标志。
- 此外,内核对 SO_REUSEPORT 套接字执行一些在其他操作系统中找不到的“特殊魔法”:对于 UDP 套接字,它尝试均匀分发数据报,对于 TCP 侦听套接字,它尝试分发传入的连接请求(通过调用接受的连接请求) Accept()) 均匀地跨共享相同地址和端口组合的所有套接字。因此,应用程序可以轻松地在多个子进程中打开同一端口,然后使用 SO_REUSEPORT 来获得非常便宜的负载平衡。
| 归档时间: |
|
| 查看次数: |
3353 次 |
| 最近记录: |