1.介绍
SO_REUSEPORT 是一个套接字选项,主要解决多进程/多线程服务程序中端口冲突和性能瓶颈的问题。
它的核心作用是允许多个套接字绑定到完全相同的IP地址和端口。内核会把收到的连接请求(TCP)或数据包(UDP)分散给这些套接字,实现负载均衡。
它与 SO_REUSEADDR 的关键区别:
· SO_REUSEADDR:主要解决重启服务时“Address already in use”的报错,允许复用系统中处于 TIME_WAIT 状态的端口。
· SO_REUSEPORT:解决多个进程同时运行时的绑定问题,让它们同时、平等地监听同一端口。
2.使用它的主要好处是:
1. 消除“惊群”问题:传统多进程监听同一端口,一个新连接会唤醒所有进程去争抢。而使用 SO_REUSEPORT 后,内核会直接精准唤醒一个进程。
2. 提升性能:内核在内核态就完成了请求的分发,避免了用户态锁争抢,对UDP和短连接场景提升尤其明显。
3. 支持热升级:可以启动新版本进程绑定同一端口,再优雅关闭旧进程,实现零停机更新。
在 Linux 3.9 及以上版本,Nginx(1.9.1+)的 reuseport 参数、Redis 等主流软件都已支持此功能。
3.示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BACKLOG 10
void handle_client(int client_fd) {
char buf[1024];
int n = read(client_fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Process %d received: %s\n", getpid(), buf);
const char *resp = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
write(client_fd, resp, strlen(resp));
}
close(client_fd);
}
void run_server() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
exit(1);
}
// 关键:设置 SO_REUSEPORT
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
perror("setsockopt(SO_REUSEPORT)");
exit(1);
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(server_fd, BACKLOG) < 0) {
perror("listen");
exit(1);
}
printf("Process %d listening on port %d\n", getpid(), PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
if (client_fd < 0) {
perror("accept");
continue;
}
handle_client(client_fd);
}
}
int main() {
const int num_processes = 4;
for (int i = 0; i < num_processes; ++i) {
pid_t pid = fork();
if (pid == 0) { // 子进程
run_server();
exit(0);
} else if (pid < 0) {
perror("fork");
exit(1);
}
}
// 父进程等待,或者直接退出(让子进程在后台运行)
for (int i = 0; i < num_processes; ++i) wait(NULL);
return 0;
}
1154

被折叠的 条评论
为什么被折叠?



