epoll单台设备支持百万并发连接
一些概念:
linux下一切接文件,文件描述符fd,文件I/O(包含socket,文本文件等),I/O多路复用,reactor模型,水平触发,边沿触发,多线程模型,阻塞和非阻塞,同步和异步
设备有限,目前实测能达到 11万并发连接。当客户端端口耗尽退出时,服务端异常退出待解决。
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#define BUFFER_LEN 128
#define MAX_CON 1024
#define EVENTS_LEN 128
#define ITEM_LEN 4096
struct sock_item {
int fd;
char *rbuffer;
int rlength;
char *wbuffer;
int wlength;
int event;
void (*recv_cb)(int fd, char *buffer, int length);
void (*send_cb)(int fd, char *buffer, int length);
void (*accept_cb)(int fd, char *buffer, int length);
};
struct eventblock {
struct sock_item *items;
struct eventblock *next;
};
struct reactor {
int epfd;
int blkcnt;
struct eventblock *evblk;
};
int reactor_alloc(struct reactor *r){
if(!r) return -1;
struct eventblock *blk = r->evblk;
while(blk != NULL && blk->next != NULL) blk = blk->next;
struct sock_item *item = (struct sock_item *)calloc(1, ITEM_LEN * sizeof(struct sock_item));
if(!item){
printf("no memory ret:%d %s\n", errno, strerror(errno));
return -1;
}
struct eventblock *block = (struct eventblock *)calloc(1, sizeof(struct eventblock));
if(!block) {
free(item);
printf("no memory ret:%d %s\n", errno, strerror(errno));
return -1;
}
block->items = item;
block->next = NULL;
//blk == NULL 时表示首个节点
if(!blk)
r->evblk = block;
else
blk->next = block;
r->blkcnt++;
printf("create block:%p, cnt:%d\n", block, r->blkcnt);
return 0;
}
struct sock_item* reactor_lookup(struct reactor *r, int sockfd){
if(!r || !r->evblk || sockfd <= 0) return NULL;
int ret;
int blkidx = sockfd / ITEM_LEN;
while(blkidx >= r->blkcnt){
ret = reactor_alloc(r);
if(ret != 0) return NULL;
}
int i = 0;
struct eventblock *blk = r->evblk;
while(i++ < blkidx) blk = blk->next;
return &blk->items[sockfd % ITEM_LEN];
}
void *routine(void *arg){
int ret;
int clientfd = *(int *)arg;
while(1){
unsigned char buf[BUFFER_LEN] = {0};
ret = recv(clientfd, buf, BUFFER_LEN, 0);
if(ret == 0){ //客户端close,这里不做处理会产生 close_wait 状态
close(clientfd);
break;
}
printf("clientfd:%d buf:%s %d\n", clientfd, buf, ret);
ret = send(clientfd, buf, ret, 0);
printf("clientfd send %d\n", ret);
}
}
int main(){
//socket 插座,理解 socketfd 是插,bind sockaddr_in 是座
int ret;
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1) return -1;
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
ret = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(ret == -1) return -2;
//fd 默认阻塞
//设置非阻塞
#if 0
int flag = fcntl(listenfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(listenfd, F_SETFL, flag);
#endif
listen(listenfd, 10); //类似酒店迎宾的人
#if 0
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len);
while(1){
unsigned char buf[BUFFER_LEN] = {0};
ret = recv(clientfd, buf, BUFFER_LEN, 0);
printf("clientfd:%d %s %d\n", clientfd, buf, ret);
ret = send(clientfd, buf, ret, 0);
printf("clientfd send %d\n", ret);
}
#elif 0
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
//类似酒店服务员
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len);
pthread_t tid;
pthread_create(&tid, NULL, routine, &clientfd);
}
#elif 0 //<apue> pg404 select
fd_set rfds, wfds, rset, wset;
FD_ZERO(&rfds);
FD_SET(listenfd, &rfds);
FD_ZERO(&wfds);
int maxfd = listenfd; //对 maxfd 的理解?内核 bitmap 的下标上限?
unsigned char buf[MAX_CON][BUFFER_LEN + 1] = {{0}};
while(1){
rset = rfds;
wset = wfds;
//内核循环上限 可读集合 可写集合 出错 超时时间
select(maxfd + 1, &rset, &wset, NULL, NULL); //只监听listen的读事件
if(FD_ISSET(listenfd, &rset)){
printf("listenfd event:%d\n", listenfd);
//listen 已经有读事件,accpet该连接
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len); //accept会清空listen读事件
FD_SET(clientfd, &rfds); //设置客户端的监听读事件
if(clientfd > maxfd) maxfd = clientfd;
}
int i = 0;
for(i = listenfd + 1; i <= maxfd; ++i){
if(i >= MAX_CON) {
printf("over fd %d\n", i);
continue;
}
if(FD_ISSET(i, &rset)){
ret = recv(i, buf[i], BUFFER_LEN, 0);
if(ret == 0){
close(i);
FD_CLR(i, &rfds);
printf("close fd:%d\n", i);
continue;
}
printf("clientfd:%d %s %d\n", i, buf[i], ret);
FD_SET(i, &wfds); //监听是否可写
}
if(FD_ISSET(i, &wset)){
ret = send(i, buf[i], ret, 0);
printf("clientfd:%d send %d\n", i, ret);
FD_CLR(i, &wfds); //清空监听写
}
}
}
#elif 0 //搜索系统调用 SYSCALL_DEFINE(N) N表示该系统调用的参数个数
int epfd = epoll_create(1);
struct epoll_event ev, events[EVENTS_LEN];
ev.events = EPOLLIN; //这里选择水平还是边沿触发;如果是边沿触发,怎么做?(while accept listenfd 读事件)
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //将listenfd加入到红黑树中, 边沿触发监听listenfd的读事件
char buf[BUFFER_LEN] = {0};
while(1){
int nready = epoll_wait(epfd, events, EVENTS_LEN, -1); //-1阻塞,0立刻返回, 1000为1秒返回
printf("epoll_wait ret ready:%d\n", nready);
int i = 0;
for(i = 0; i < nready; ++i){
int clientfd = events[i].data.fd;
if(listenfd == clientfd){
//accept 建立连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(listenfd, (struct sockaddr *)&client, &len);
printf("accept connfd:%d\n", connfd);
ev.events = EPOLLET | EPOLLIN; //水平触发 和 边沿触发 的区别?
ev.data.fd = connfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}else if(events[i].events & EPOLLIN){
ret = recv(clientfd, buf, BUFFER_LEN, 0);
if(ret == 0){
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &events[i]);
close(clientfd);
printf("clientfd:%d closed\n", clientfd);
//continue;
}
ev.events = EPOLLOUT; //改为监听可写事件
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}else if(events[i].events & EPOLLOUT){
ret = send(clientfd, buf, ret, 0);
if(ret <= 0){
char *err = strerror(errno);
printf("clientfd:%d closed ? ret %d errno %d:%s\n",
clientfd, ret, errno, err == NULL ? "unknow": err);
}
printf("clientfd:%d send:%s num:%d\n", clientfd, buf, ret);
ev.events = EPOLLIN; //改为监听可写事件
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}
}
}
#elif 1 //reactor 百万并发实现?
struct reactor *r = (struct reactor *)calloc(1, sizeof(struct reactor));
if(!r) {
printf("memory failed\n");
return -1;
}
ret = reactor_alloc(r);
if(ret != 0) return -1;
r->epfd = epoll_create(1);
struct epoll_event ev;
struct epoll_event events[EVENTS_LEN]; //准备好的事件缓冲区
ev.events = EPOLLIN; //默认水平触发
ev.data.fd = listenfd;
epoll_ctl(r->epfd, EPOLL_CTL_ADD, listenfd, &ev);
while(1){
int nready = epoll_wait(r->epfd, events, EVENTS_LEN, -1); //-1阻塞,0立刻返回, 1000为1秒返回
printf("epoll_wait ret ready:%d\n", nready);
int i = 0;
for(i = 0; i < nready; ++i){
int clientfd = events[i].data.fd;
if(listenfd == clientfd){
//accept 建立连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(listenfd, (struct sockaddr *)&client, &len);
printf("accept connfd:%d\n", connfd);
if(connfd <= 0) continue;
ev.events = EPOLLIN; //水平触发 和 边沿触发 的区别?
ev.data.fd = connfd;
epoll_ctl(r->epfd, EPOLL_CTL_ADD, connfd, &ev);
#if 0
//填充 sock_item
assert(r->items[connfd].rbuffer == NULL);
r->items[connfd].rbuffer = (char *)calloc(1, BUFFER_LEN);
r->items[connfd].rlength = 0;
assert(r->items[connfd].wbuffer == NULL);
r->items[connfd].wbuffer = (char *)calloc(1, BUFFER_LEN);
r->items[connfd].wlength = 0;
r->items[connfd].event = EPOLLIN;
assert(r->items[connfd].fd == 0);
r->items[connfd].fd = connfd;
#endif
struct sock_item *item = reactor_lookup(r, connfd);
if(item == NULL) {
printf("error lookup item\n");
continue;
}
item->fd = connfd;
assert(item->rbuffer == NULL);
item->rbuffer = calloc(1, BUFFER_LEN);
item->rlength = 0;
assert(item->wbuffer == NULL);
item->wbuffer = calloc(1, BUFFER_LEN);
item->wlength = 0;
printf("get item:%p, cfd:%d\n", item, item->fd);
}else if(events[i].events & EPOLLIN){
struct sock_item *item = reactor_lookup(r, clientfd);
printf("opr recv item:%p, item->fd:%d, connfd:%d\n", item, item->fd, clientfd);
assert(item != NULL && item->fd == clientfd);
char *rbuf = item->rbuffer;
char *wbuf = item->wbuffer;
assert(rbuf != NULL && wbuf != NULL);
ret = recv(item->fd, rbuf, BUFFER_LEN, 0);
if(ret == 0){
epoll_ctl(r->epfd, EPOLL_CTL_DEL, item->fd, &events[i]);
free(item->rbuffer); item->rbuffer = NULL;
free(item->wbuffer); item->wbuffer = NULL;
close(item->fd);
printf("clientfd:%d closed\n", item->fd);
item->fd = 0;
continue;
}
if(ret < 0) {
printf("clientfd:%d recv error ret:%d %s\n", item->fd, ret, strerror(errno));
continue;
}
item->rlength = ret;
memcpy(wbuf, rbuf, item->rlength);
item->wlength = ret;
printf("opr recv item:%p, clientfd:%d send:%s num:%d\n", item, item->fd, rbuf, ret);
ev.events = EPOLLOUT; //改为监听可写事件
ev.data.fd = item->fd;
epoll_ctl(r->epfd, EPOLL_CTL_MOD, item->fd, &ev);
}else if(events[i].events & EPOLLOUT){
struct sock_item *item = reactor_lookup(r, clientfd);
printf("opr send item:%p, item->fd:%d, connfd:%d\n", item, item->fd, clientfd);
assert(item != NULL && item->fd == clientfd);
char *wbuf = item->wbuffer;
assert(wbuf != NULL);
ret = send(item->fd, wbuf, item->wlength, 0);
if(ret <= 0){
char *err = strerror(errno);
printf("clientfd:%d closed ? ret %d errno %d:%s\n",
item->fd, ret, errno, err == NULL ? "unknow": err);
continue;
}
printf("opr send item:%p clientfd:%d send:%s num:%d\n", item, item->fd, wbuf, ret);
ev.events = EPOLLIN; //改为监听可写事件
ev.data.fd = item->fd;
epoll_ctl(r->epfd, EPOLL_CTL_MOD, item->fd, &ev);
}
}
}
#endif
return 0;
}
相关文章:
epoll单台设备支持百万并发连接
一些概念: linux下一切接文件,文件描述符fd,文件I/O(包含socket,文本文件等),I/O多路复用,reactor模型,水平触发,边沿触发,多线程模型,阻塞和非阻塞…...
网络字节序
文章目录网络字节序网络字节序 内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 网络数据流的地址统一按大端处理 发送主机通常将发送缓冲区中的数据按内存地址从低到高的…...

03- SVC 支持向量机做人脸识别 (项目三)
数据集描述: sklearn的lfw_people函数在线下载55个外国人图片文件夹数据集来精确实现人脸识别并提取人脸特征向量数据集地址: sklearn.datasets.fetch_lfw_people — scikit-learn 1.2.1 documentationPCA降维: pca PCA(n_components0.9) 数据拆分: X_train, X_test, y_tra…...

浅谈指向二维数组元素的指针变量
(1)指向数组元素的指针变量 例1.有一个3X4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值. 编写程序 1 #include <stdio.h>2 int main()3 {4 int a[3][4] { 1,3,5,7,9,11,13,15,17,19,21,23 };5 int *p;6 for (p a[0]; p < a[0] 12; p) …...
左右值引用和移动语义
文章首发公众号:iDoitnow 1. 左右值和左右值引用 什么是左值、右值呢?一种极不严谨的理解为:在赋值的时候,能够被放到等号左边的值为左值,放在右边的值为右值。例如: int sum(int x, int y){return x y;…...

一起学习用Verilog在FPGA上实现CNN----(七)全连接层设计
1 全连接层设计 1.1 Layer 进行线性计算的单元layer,原理图如图所示: 1.2 processingElement Layer中的线性计算单元processingElement,原理图如图所示: processingElement模块展开原理图,如图所示,包含…...

tomcat打debug断点调试
windows debug调试 jdk版本:1.8.0_181 tomcat版本:apache-tomcat-9.0.68.0 idea版本:2020.1 方法一 修改catalina.bat 在%CATALINA_HOME%\bin\catalina.bat中找到 set “JAVA_OPTS%JAVA_OPTS% -Djava.protocol.handler.pkgsorg.apache…...
如果持有互斥锁的线程没有解锁退出了,该如何处理?
文章目录如果持有互斥锁的线程没有解锁退出了,该如何处理?问题引入PTHREAD_MUTEX_ROBUST 和 pthread_mutex_consistent登场了结论:如果持有互斥锁的线程没有解锁退出了,该如何处理? 问题引入 看下面一段代码…...

信息论绪论
本专栏针包含信息论与编码的核心知识,按知识点组织,可作为教学或学习的参考。markdown版本已归档至【Github仓库:information-theory】,需要的朋友们自取。或者关注公众号【AIShareLab】,回复 信息论 也可获取。 文章目…...

Buffer Status Reporting(BSR)
欢迎关注同名微信公众号“modem协议笔记”。 以一个实网中的异常场景开始,大概流程是有UL data要发送,UE触发BSR->no UL grant->SR->no UL grant->trigger RACH->RACH fail->RLF->RRC reestablishment:简单描述就是UE触…...

代码随想录LeetCode | 单调栈问题
前沿:撰写博客的目的是为了再刷时回顾和进一步完善,其次才是以教为学,所以如果有些博客写的较简陋,是为了保持进度不得已而为之,还请大家多多见谅。 预:看到题目后的思路和实现的代码。 见:参考…...

C++之可调用对象、bind绑定器和function包装器
可调用对象在C中,可以像函数一样调用的有:普通函数、类的静态成员函数、仿函数、lambda函数、类的非静态成员函数、可被转换为函数的类的对象,统称可调用对象或函数对象。可调用对象有类型,可以用指针存储它们的地址,可…...

MongoDB--》文档查询的详细具体操作
目录 统计查询 分页列表查询 排序查询 正则的复杂条件查询 比较查询 包含查询 条件连接查询 统计查询 统计查询使用count()方法,其语法格式如下: db.collection.count(query,options) ParameterTypeDescriptionquerydocument查询选择条件optio…...

网络协议(六):网络层
网络协议系列文章 网络协议(一):基本概念、计算机之间的连接方式 网络协议(二):MAC地址、IP地址、子网掩码、子网和超网 网络协议(三):路由器原理及数据包传输过程 网络协议(四):网络分类、ISP、上网方式、公网私网、NAT 网络…...

热启动预示生态起航的Smart Finance,与深度赋能的SMART通证
2023年初加密市场的回暖,意味着各个赛道都将在新的一年里走向新的叙事。最近,我们看到GameFi赛道也在市场回暖的背景下,逐渐走出阴霾。从融资数据上看,1月获得融资的GameFi项目共12个,融资突破8000万美元,1…...

提分必练,中创教育PMP全真模拟题分享
湖南中创教育每日五题分享来啦,“日日行,不怕千万里;常常做,不怕千万事。”,每日五题我们练起来! 1、在系统测试期间,按已识别原因的类型或类别记录了失败测试的数量。项目经理首先需要从最大故…...

PID控制算法基础介绍
PID控制的概念 生活中的一些小电器,比如恒温热水器、平衡车,无人机的飞行姿态和飞行速度控制,自动驾驶等等,都有应用到 PID——PID 控制在自动控制原理中是一套比较经典的算法。 为什么需要 PID 控制器呢? 你一定用…...

Ajax 学习笔记
一、Ajax1.1 什么是AjaxAJAX Asynchronous JavaScript and XML(异步的JavaScript和XML)。Ajax是一种在无需加载整个网页的情况下,能够更新部分网页的技术,它不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术…...
力扣解法汇总1234. 替换子串得到平衡字符串
目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:力扣 描述: 有一个只含有 Q, W, E, R 四种字符,且长度为 n 的字符串。 假如在该…...

C++关键字之const、inline、static
C 关键字总结 1.const const是 constant 的缩写,本意是不变的、不易改变的意思。在C中用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数使用如下: //修饰普通类型变量 const int a 7; int ba;…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...