考虑以下代码:
在Linux上运行似乎很正常:
#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);
}
非常简单:
- 创建套接字
- 设置
SO_REUSEADDR
- 设置
SO_REUSEPORT
- 绑定到本地端口
23456
- 连接到端口
12345
上的::1
在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
$
在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!
$
我在端口12345
上有一个监听器:
$ nc -6 -l -v -p12345 -k
这不仅限于IPv6,我尝试使用IPv4做了同样的事情 - 结果也是一样的。
有人能解释一下吗?
我之前以为是在bind()
中失败了,但实际上是在connect()
中失败了。
编辑#1
根据SO_REUSEADDR和SO_REUSEPORT有何不同?,这适用于BSD:
因此,如果将两个相同协议的套接字绑定到相同的源地址和端口,并尝试将它们都连接到相同的目标地址和端口,则对于您尝试连接的第二个套接字,
connect()
实际上会失败并显示错误EADDRINUSE
,这意味着已经连接了具有相同五个值元组的套接字。
所以这就解释了为什么这不起作用。但是,在Linux上为什么会起作用就不太合理了?
理想情况下,我当然希望这在MacOS上能够工作,但我目前感觉可能不可能 - 但我仍然希望了解Linux是如何做到的。