C 中级 - SO_REUSEPORT 和 SO_REUSEADDR
引言 - 问题由来
刚开始学习网络编程时候, 常听到一个词, 先开启 "端口复用 SO_REUSEADDR". 那时很一知半解,
就知道该那么写了. 心里一直有些奇怪, 语义不通呀为啥这么翻译. 后面随着相声听多了, 就明白了些
道理.
倒排索引为啥叫倒排索引? https://www.zhihu.com/question/23202010
(这个梗告诉 wo, 索引和反向索引要比正排索引和倒排索引容易理解好多, 信达雅 : )
随后逛网络帖子恰好看见布道师陈硕介绍 SO_REUSEPORT 时候, 他说的实用起来应该很爽.
Linux 4.5/4.6 中对 SO_REUSEPORT 的改进 https://zhuanlan.zhihu.com/p/25533528
文章简单的通过数据结构来表明 SO_REUSEPORT 开启后, 会将 sock 结构放入 port 为 key 的
hash 结构中. 一联想, 就发现到 epoll 多线程解决方案, 通过 epoll + thread + listen fd epoll 搞.
是不是很有意思.
后面开始搜集 SO_REUSEPORT 资料, 看到这个
浅析套接字中SO_REUSEPORT和SO_REUSEADDR的区别 https://blog.csdn.net/Yaokai_AssultMaster/article/details/68951150
从中提炼几个简单信息. 我们以 linux 行为为基准, 顺带引述 winds 行为.
linux -:
1) . 端口复用 SO_REUSEPORT, 可以顶地址复用 SO_REUSEADDR.
2) . 都有 userID 安全检查
winds -:
1). 只有 SO_REUSEADDR, 轻微像 SO_REUSEPORT 支持多端口绑定.
但只有最后一个绑定的 socket 能够接收数据. 最后一个 closesocket 后, 最后"第二个"顶.
(猜测采用的是 list 结构, 每次 add 到 head, del 到 head. )
2). 没有 userID 安全检查, 依赖它独有安全的选项 SO_EXCLUSIVEADDRUSE
通过上面信息, 不妨写个通用的复用代码 socket_set_reuse 用起来会很舒服.
// socket_set_reuse - 开启端口和地址复用
inline int socket_set_enable(socket_t s, int optname) {
int ov = ;
return setsockopt(s, SOL_SOCKET, optname, (void *)&ov, sizeof ov);
} inline int socket_set_reuse(socket_t s) {
return socket_set_enable(s, SO_REUSEPORT);
}
其中 winds 平台构造了如下定义
#ifdef _MSC_VER #define SO_REUSEPORT SO_REUSEADDR typedef SOCKET socket_t;#endif
上面翻译文章中提供链接挺好
stackoverflow SO_REUSEADDR 和 SO_REUSEPORT differ 回答很有水准
https://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t/14388707#14388707
扯了这么多, 后面会构造代码来验证和表现结果.
正文 - 实验验证
首先从 linux 入手, 写一段 SO_REUSEPORT 验证代码 port.c
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <sys/types.h> #include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/tcp.h> //
// CERR - 打印错误信息
// IF - 条件判断异常退出的辅助宏
//
#define CERR(fmt, ...) \
fprintf(stderr, "[%s:%s:%d][%d:%s]" fmt "\n", \
__FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #define IF(cond) \
if ((cond)) do { \
CERR(#cond); \
exit(EXIT_FAILURE); \
} while() // accept_example - SO_REUSEPORT accept example
void accept_example(void); // times_buf - 时间串缓存
char * times_buf(char buf[BUFSIZ]); //
// SO_REUSEPORT :)
//
int main(int argc, char * argv[]) {
// start 10 pthread run accept_example
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
for (int i = ; i < ; ++i) {
pthread_t tid;
IF(pthread_create(&tid, &attr, (void * (*)(void *))accept_example, NULL));
}
pthread_attr_destroy(&attr); // main accept block
accept_example();
return ;
} // UINT_PORT - 监听端口
#define UINT_PORT (8088) // socket_set_enable - 开始 socket 开关
inline static int socket_set_enable(int s, int optname) {
int ov = ;
return setsockopt(s, SOL_SOCKET, optname, (void *)&ov, sizeof ov);
} // accept_example - SO_REUSEPORT accept example
void
accept_example(void) {
// 构造 TCP socket
int s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
IF(s == ~); // 开启地址复用
IF(socket_set_enable(s, SO_REUSEADDR));
IF(socket_set_enable(s, SO_REUSEPORT)); // 构造地址
struct sockaddr_in addr = {
AF_INET,
htons(UINT_PORT),
{ INADDR_ANY },
};
socklen_t sen = sizeof addr;
const struct sockaddr * pddr = (const struct sockaddr *)&addr; // 绑定地址
IF(bind(s, pddr, sen)); // 开始监听
IF(listen(s, SOMAXCONN)); // 等待链接过来, 最多 3 次
for (int i = ; i < ; ++i) {
char v[BUFSIZ];
struct sockaddr_in cddr;
socklen_t cen = sizeof cddr; printf("[%ld] accept start ... %s\n", pthread_self(), times_buf(v));
int c = accept(s, (struct sockaddr *)&cddr, &cen);
if (s == ~) {
CERR("accept s = %d is error", s);
break;
} // 连接成功打印链接消息
printf("[%ld] [%s:%d] accept success ... %s\n",
pthread_self(),
inet_ntoa(cddr.sin_addr), ntohs(cddr.sin_port),
times_buf(v)); close(c);
} close(s);
} // times_buf - 时间串缓存
char *
times_buf(char buf[BUFSIZ]) {
struct tm m;
struct timespec s; timespec_get(&s, TIME_UTC);
localtime_r(&s.tv_sec, &m); snprintf(buf, BUFSIZ, "%04d-%02d-%02d %02d:%02d:%02d %03ld",
m.tm_year + , m.tm_mon + , m.tm_mday,
m.tm_hour, m.tm_min, m.tm_sec,
s.tv_nsec / );
return buf;
}
立即尝试跑一跑
gcc -g -Wall -o port.out port.c -lpthread
./port.out
看看结果

顺带写个 winds 实验版本 reuseport_test.c (跨平台)
#include <times.h>
#include <socket.h>
#include <thread.h> // UINT_PORT - 监听端口
#define UINT_PORT (8088) // accept_example - SO_REUSEPORT accept example
void accept_example(int id); // reuseport_test - 端口复用测试
void reuseport_test(void) {
// start 10 pthread run accept_example
for (int i = ; i <= ; ++i) {
IF(pthread_async(accept_example, i));
} msleep(); // main accept block
accept_example();
} // accept_example - SO_REUSEPORT accept example
void
accept_example(int id) {
// 构造 TCP socket
socket_t s = socket_stream();
IF(s == INVALID_SOCKET); // 开启地址复用
IF(socket_set_reuse(s)); // 构造地址
sockaddr_t addr = {{ AF_INET, htons(UINT_PORT) }}; // 绑定地址
IF(socket_bind(s, addr)); // 开始监听
IF(socket_listen(s)); // 等待链接过来, 最多 3 次
for (int i = ; i < ; ++i) {
times_t v;
sockaddr_t cddr; printf("[%2d] accept start ... %s\n", id, times_str(v));
socket_t c = socket_accept(s, cddr);
if (s == INVALID_SOCKET) {
CERR("accept s = %lld is error", (long long)s);
break;
} // 连接成功打印链接消息
char ip[INET_ADDRSTRLEN];
printf("[%2d] [%s:%d] accept success ... %s\n", id,
socket_pton(cddr, ip), ntohs(cddr->sin_port),
times_str(v)); socket_close(c);
} socket_close(s);
}
是不是很清爽, 舒服还是 structc 框架给予的 ~ &(左旋转 90 度) 力量
structc https://github.com/wangzhione/structc

玩别人玩剩下的也挺好玩的 哈哈 ~ : )
毕竟写过 -|
后记 - 搬运打杂
错误是难免的, 欢迎指正, 共同提升, 感受纯粹的力量
浪子回头 - http://music.163.com/#/song?id=516728102

C 中级 - SO_REUSEPORT 和 SO_REUSEADDR的更多相关文章
- 浅析套接字中SO_REUSEPORT和SO_REUSEADDR的区别
Socket的基本背景 在讨论这两个选项的区别时,我们需要知道的是BSD实现是所有socket实现的起源.基本上其他所有的系统某种程度上都参考了BSD socket实现(或者至少是其接口),然后开始了 ...
- SO_REUSEADDR和SO_REUSEPORT异同
文章内容来源于stackoverflow上的回答,写的很详细http://stackoverflow.com/questions/14388706/socket-options-so-reuseadd ...
- SO_REUSEADDR与SO_REUSEPORT平台差异性与测试
前些天,与另外一个项目组的同事聊天的时候,谈到他遇到的一个有意思的BUG.在window上启动服务器,然后客户端连接的时候收到一些奇怪的消息,查证了,原来是他自己的另一个工具也在相同的地址上监听,客户 ...
- Linux下端口复用(SO_REUSEADDR与SO_REUSEPORT)
freebsd与linux下bind系统调用小结: 只考虑AF_INET的情况(同一端口指ip地址与端口号都相同) freebsd支持SO_REUSEPORT和SO_REUSEADDR选项,而l ...
- linux socket中的SO_REUSEADDR
Welcome to the wonderful world of portability... or rather the lack of it. Before we start analyzing ...
- RT: TCP REUSEADDR or REUSEPORT
Welcome to the wonderful world of portability... or rather the lack of it. Before we start analyzing ...
- Socket 相关的知识
1.关于PF_INET和AF_INET的区别 在写网络程序的时候,建立TCP socket: sock = socket(PF_INET, SOCK_STREAM, 0);然后在绑定本地地址或连接远程 ...
- unix 网络编程 第七章
1 getsockopt和setsockopt函数 套接字选项粗分为两大基本类型:一是启用或禁止某个特性的二元选项,二是取得并返回特定值的选项,参数都是以指针形式传入的. 2 套接字状 ...
- 从Linux源码看Socket(TCP)的bind
从Linux源码看Socket(TCP)的bind 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看下Server ...
随机推荐
- 【bzoj4425】[Nwerc2015]Assigning Workstations分配工作站 贪心+堆
题目描述 佩内洛普是新建立的超级计算机的管理员中的一员. 她的工作是分配工作站给到这里来运行他们的计算研究任务的研究人员. 佩内洛普非常懒惰,不喜欢为到达的研究者们解锁机器. 她可以从在她的办公桌远程 ...
- Contest 4
A:cf原题.当然是不是也没什么关系. #include<iostream> #include<cstdio> #include<cstdlib> #include ...
- 洛谷 P2763 试题库问题(网络流24题之一)
题目描述 «问题描述: 假设一个试题库中有n道试题.每道试题都标明了所属类别.同一道题可能有多个类别属性.现要从题库中抽取m 道题组成试卷.并要求试卷包含指定类型的试题.试设计一个满足要求的组卷算法. ...
- [您有新的未分配科技点]数位DP:从板子到基础(例题 bzoj1026 windy数 bzoj3131 淘金)
只会统计数位个数或者某种”符合简单规律”的数并不够……我们需要更多的套路和应用 数位dp中常用的思想是“分类讨论”思想.下面我们就看一道典型的分类讨论例题 1026: [SCOI2009]windy数 ...
- [AT2000] [agc002_f] Leftmost Ball
题目链接 AtCoder:https://agc002.contest.atcoder.jp/tasks/agc002_f 洛谷:https://www.luogu.org/problemnew/sh ...
- 【HDU4689】Derangement(动态规划)
[HDU4689]Derangement(动态规划) 题面 Vjudge 给定一个\(+-\)组成的字符串,长度为\(n\). 如果第\(i\)位是\(+\),那么\(p_i>i\),否则\(p ...
- 洛谷 P1278 单词游戏 【状压dp】
题目描述 Io和Ao在玩一个单词游戏. 他们轮流说出一个仅包含元音字母的单词,并且后一个单词的第一个字母必须与前一个单词的最后一个字母一致. 游戏可以从任何一个单词开始. 任何单词禁止说两遍,游戏中只 ...
- 洛谷 P2123 皇后游戏 解题报告
P2123 皇后游戏 题意: 给定\(T\)组长为\(n\)的\(A\),\(B\)数组和\(C\)的计算方法,求一种排列方法,使最大的\(C\)最小化. 数据范围: \(1 \le T \le 10 ...
- Fox
Portal --> broken qwq Description 有n只狐狸在一起聚餐,每只狐狸都有一个年龄.按照狐狸们的习惯,坐在一起的两只狐狸的年龄之和需要是质数.现在这些狐狸们在一些圆桌 ...
- PID控制算法的C语言实现二 PID算法的离散化
上一节中,我论述了PID算法的基本形式,并对其控制过程的实现有了一个简要的说明,通过上一节的总结,基本已经可以明白PID控制的过程.这一节中先继续上一节内容补充说明一下. 1.说明一下反馈控制的原理, ...