cpp
// udp_reuseaddr_multi.cpp
// 编译: g++ -std=c++11 -pthread udp_reuseaddr_multi.cpp -o udp_multi
// 运行: ./udp_multi <num_sockets> [reuseport]
// 参数:
// num_sockets : 要创建的 UDP socket 数量
// reuseport : 可选,若为 1 则启用 SO_REUSEPORT,默认 0(不启用)
// 示例:
// ./udp_multi 5 # 只启用 SO_REUSEADDR,数据全流向最后一个 socket
// ./udp_multi 5 1 # 同时启用 SO_REUSEPORT,数据按四元组哈希分发,每个包不同源端口
#include <iostream>
#include <thread>
#include <vector>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
const int PORT = 8888;
const int BUFFER_SIZE = 1024;
// 创建一个UDP socket,绑定到INADDR_ANY:PORT,根据参数启用选项
int create_and_bind(bool reuseport_flag) {
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return -1;
}
int opt = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt SO_REUSEADDR");
close(sock);
return -1;
}
if (reuseport_flag) {
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
perror("setsockopt SO_REUSEPORT");
close(sock);
return -1;
}
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PORT);
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sock);
return -1;
}
return sock;
}
// 接收线程:不断打印收到的数据
void receive_loop(int sock, const std::string& name) {
char buffer[BUFFER_SIZE];
struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
while (true) {
ssize_t n = recvfrom(sock, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*)&src_addr, &addr_len);
if (n > 0) {
buffer[n] = '\0';
std::cout << "[" << name << "] received from "
<< inet_ntoa(src_addr.sin_addr) << ":"
<< ntohs(src_addr.sin_port) << " -> " << buffer << std::endl;
} else if (n == 0) {
std::cout << "[" << name << "] connection closed?" << std::endl;
break;
} else {
perror("recvfrom");
break;
}
}
}
// 发送线程:每次发送都创建一个新的临时 socket(自动获得不同的源端口)
void sender_loop() {
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr);
for (int i = 0; ; ++i) {
int send_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (send_sock < 0) {
perror("sender socket");
return;
}
std::string msg = "self_msg_" + std::to_string(i);
ssize_t sent = sendto(send_sock, msg.c_str(), msg.size(), 0,
(struct sockaddr*)&dest, sizeof(dest));
if (sent < 0) {
perror("sendto");
}
close(send_sock); // 关闭后下次循环会创建新socket,新源端口
sleep(1); // 每秒发送一条
}
}
int main(int argc, char* argv[]) {
if (argc < 2 || argc > 3) {
std::cerr << "Usage: " << argv[0] << " <num_sockets> [reuseport]" << std::endl;
std::cerr << " reuseport: 0 (default) or 1" << std::endl;
return 1;
}
int num_sockets = atoi(argv[1]);
if (num_sockets <= 0 || num_sockets > 100) {
std::cerr << "Error: num_sockets must be between 1 and 100." << std::endl;
return 1;
}
bool reuseport_enabled = false;
if (argc == 3) {
reuseport_enabled = (atoi(argv[2]) != 0);
}
std::cout << "=== UDP Socket Test ===" << std::endl;
std::cout << "Number of sockets: " << num_sockets << std::endl;
std::cout << "SO_REUSEPORT: " << (reuseport_enabled ? "ENABLED" : "DISABLED") << std::endl;
std::cout << "=========================" << std::endl;
std::vector<int> sockets;
sockets.reserve(num_sockets);
for (int i = 0; i < num_sockets; ++i) {
int sock = create_and_bind(reuseport_enabled);
if (sock < 0) {
for (int fd : sockets) close(fd);
return 1;
}
sockets.push_back(sock);
std::cout << "Socket " << i << " created, fd=" << sock << std::endl;
}
std::cout << "=========================" << std::endl;
std::cout << "Sending test messages every second with different source ports..." << std::endl;
std::cout << "Observe which socket(s) receive the data." << std::endl;
std::cout << "=========================" << std::endl;
std::vector<std::thread> threads;
for (int i = 0; i < num_sockets; ++i) {
std::string name = "Socket" + std::to_string(i);
threads.emplace_back(receive_loop, sockets[i], name);
}
std::thread sender(sender_loop);
sender.detach();
for (auto& t : threads) {
t.join();
}
for (int fd : sockets) close(fd);
return 0;
}
bash
g++ -std=c++11 -pthread udp_reuseaddr_multi.cpp -o udp_multi
1️⃣ 仅 SO_REUSEADDR(默认)
bash
./udp_multi 5
结果:所有数据包都被最后一个创建的 socket(Socket4)接收。
2️⃣ 启用 SO_REUSEPORT
bash
./udp_multi 5 1
结果 :因为发送端每次使用不同的源端口 ,内核根据四元组哈希将数据包分发到不同的接收 socket。你会看到输出类似:
text
[Socket2] received from 127.0.0.1:45678 -> self_msg_0
[Socket0] received from 127.0.0.1:45679 -> self_msg_1
[Socket4] received from 127.0.0.1:45680 -> self_msg_2
[Socket1] received from 127.0.0.1:45681 -> self_msg_3
...
(具体哪个 socket 收到由哈希决定,但多个 socket 都会出现)
🔍 原理说明
-
SO_REUSEPORT会创建一个接收组 ,内核为每个到达的 UDP 数据包计算(源IP, 源端口, 目标IP, 目标端口)的哈希值,然后选择组内的一个 socket 递送。 -
发送端每次新建 socket → 自动获得不同的临时源端口 → 每个数据包的四元组都不同 → 哈希结果分散到各个接收 socket,从而实现负载均衡。
通过这个示例,你可以亲手验证 SO_REUSEPORT 的真正作用。
cpp
// udp_reuseaddr_multi.cpp
// 编译: g++ -std=c++11 -pthread udp_reuseaddr_multi.cpp -o udp_multi
// 运行: ./udp_multi <num_sockets> [reuseport]
// 参数:
// num_sockets : 要创建的 UDP socket 数量
// reuseport : 可选,若为 1 则启用 SO_REUSEPORT,默认 0(不启用)
// 示例:
// ./udp_multi 5 # 只启用 SO_REUSEADDR,数据全流向最后一个 socket
// ./udp_multi 5 1 # 同时启用 SO_REUSEPORT,数据按四元组哈希分发
#include <iostream>
#include <thread>
#include <vector>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
const int PORT = 8888;
const int BUFFER_SIZE = 1024;
// 创建一个UDP socket,绑定到INADDR_ANY:PORT,根据参数启用选项
// reuseport_flag: 1表示启用SO_REUSEPORT,0表示不启用
int create_and_bind(bool reuseport_flag) {
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return -1;
}
// 设置 SO_REUSEADDR(总是启用)
int opt = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt SO_REUSEADDR");
close(sock);
return -1;
}
// 根据参数决定是否启用 SO_REUSEPORT
if (reuseport_flag) {
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
perror("setsockopt SO_REUSEPORT");
close(sock);
return -1;
}
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PORT);
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sock);
return -1;
}
return sock;
}
// 接收线程:不断打印收到的数据
void receive_loop(int sock, const std::string& name) {
char buffer[BUFFER_SIZE];
struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
while (true) {
ssize_t n = recvfrom(sock, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*)&src_addr, &addr_len);
if (n > 0) {
buffer[n] = '\0';
std::cout << "[" << name << "] received from "
<< inet_ntoa(src_addr.sin_addr) << ":"
<< ntohs(src_addr.sin_port) << " -> " << buffer << std::endl;
} else if (n == 0) {
std::cout << "[" << name << "] connection closed?" << std::endl;
break;
} else {
perror("recvfrom");
break;
}
}
}
// 发送线程:向本机端口持续发送数据
void sender_loop() {
int send_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (send_sock < 0) {
perror("sender socket");
return;
}
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr);
for (int i = 0; ; ++i) {
std::string msg = "self_msg_" + std::to_string(i);
ssize_t sent = sendto(send_sock, msg.c_str(), msg.size(), 0,
(struct sockaddr*)&dest, sizeof(dest));
if (sent < 0) {
perror("sendto");
}
sleep(1); // 每秒发送一条
}
close(send_sock);
}
int main(int argc, char* argv[]) {
// 解析命令行参数
if (argc < 2 || argc > 3) {
std::cerr << "Usage: " << argv[0] << " <num_sockets> [reuseport]" << std::endl;
std::cerr << " reuseport: 0 (default) or 1" << std::endl;
return 1;
}
int num_sockets = atoi(argv[1]);
if (num_sockets <= 0) {
std::cerr << "Error: num_sockets must be a positive integer." << std::endl;
return 1;
}
if (num_sockets > 100) {
std::cerr << "Error: num_sockets too large (max 100)." << std::endl;
return 1;
}
bool reuseport_enabled = false;
if (argc == 3) {
reuseport_enabled = (atoi(argv[2]) != 0);
}
std::cout << "=== UDP Socket Test ===" << std::endl;
std::cout << "Number of sockets: " << num_sockets << std::endl;
std::cout << "SO_REUSEPORT: " << (reuseport_enabled ? "ENABLED" : "DISABLED") << std::endl;
std::cout << "=========================" << std::endl;
// 动态创建socket
std::vector<int> sockets;
sockets.reserve(num_sockets);
for (int i = 0; i < num_sockets; ++i) {
int sock = create_and_bind(reuseport_enabled);
if (sock < 0) {
for (int fd : sockets) close(fd);
return 1;
}
sockets.push_back(sock);
std::cout << "Socket " << i << " created, fd=" << sock << std::endl;
}
std::cout << "=========================" << std::endl;
std::cout << "Sending test messages every second..." << std::endl;
std::cout << "Observe which socket(s) receive the data." << std::endl;
std::cout << "=========================" << std::endl;
// 启动接收线程
std::vector<std::thread> threads;
for (int i = 0; i < num_sockets; ++i) {
std::string name = "Socket" + std::to_string(i);
threads.emplace_back(receive_loop, sockets[i], name);
}
// 启动发送线程(分离)
std::thread sender(sender_loop);
sender.detach();
// 等待接收线程结束(实际不会结束)
for (auto& t : threads) {
t.join();
}
// 清理(不会执行到这里)
for (int fd : sockets) close(fd);
return 0;
}