当前位置: 首页 > article >正文

5.9-selcct_poll_epoll 和 reactor 的模拟实现

5.9-select_poll_epoll

本文演示 select 等 io 多路复用函数的应用方法,函数具体介绍可以参考我过去写的博客。

先绑定监听的文件描述符

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2052);if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)))
{perror("bind");return -1;
}listen(sockfd, 10);

1. select

select 函数适用于 win/linux 平台,但是使用时每次都需要检查位图内所有客户端的状态变化情况,属于针对于每一个文件进行处理而非针对事件处理,效率较低。打个比方:如果客户端是小区内的住户,那么 selcet 作为快递员,会从快递仓库中挑选出要被配送到该小区指定住户的快递,并对于每一个住户是否有快递/寄快递。

演示如下:

fd_set rfds, rset;FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);int maxfd = sockfd;
while (1)
{rset = rfds;int nready = select(maxfd + 1, &rset, NULL, NULL, NULL);if (FD_ISSET(sockfd, &rset)){struct sockaddr_in clientaddr;socklen_t len = sizeof(struct sockaddr_in);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd: %d\n", clientfd);FD_SET(clientfd, &rfds);maxfd = clientfd;}int i = 0;for (i = sockfd + 1; i <= maxfd; i++){if (FD_ISSET(i, &rset)){char buffer[128] = { 0 };int count = recv(i, buffer, 128, 0);if (0 == count){printf("disconnect\n");close(i);FD_CLR(i, &rfds);break;}send(i, buffer, count, 0);printf("clientfd: %d, count: %d, buffer: %s\n", i, count, buffer);}}
}

2. poll

poll 函数适用于 Linux 平台,效率相比于 select 有所提升。如果 selcet 是快递员要对于每一个住户确定一次需求,poll 则是可以直接锁定不同客户的需求。

演示如下:


struct pollfd fds[1024] = { 0 };fds[sockfd].fd = sockfd;
fds[sockfd].events = POLLIN;int maxfd = sockfd;while (1)
{int nready = poll(fds, maxfd + 1, -1);if (fds[sockfd].revents & POLLIN){struct  sockaddr_in clientaddr;int len = sizeof(struct sockaddr_in);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("hello, %d\n", clientfd);fds[clientfd].fd = clientfd;fds[clientfd].events = POLLIN;maxfd = clientfd;}int i = 0;for (i = sockfd + 1; i <= maxfd; i++){if (fds[i].revents & POLLIN){char buffer[128] = { 0 };int count = recv(fds[i].fd, buffer, 128, 0);if (count == 0){printf("disconnect\n");close(i);fds[i].fd = -1;fds[i].events = 0;continue;}send(i, buffer, count, 0);printf("clientfd: %d, sount: %d, lbuffer: %s\n", i, count, buffer);}}
}

3.1 epoll

在支持 Linux 的函数中,epoll 是最高效的。还是上面的比方,epoll 则是在一定程度上结合了前两者的优点,并且底层使用红黑树,查找速度更快。

演示如下:

int epfd = epoll_create(1);struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);struct epoll_event events[1024] = { 0 };while (1)
{int nready = epoll_wait(epfd, events, 1024, -1);int i = 0;for (i = 0; i < nready; i++){int curfd = events[i].data.fd;if (sockfd == curfd){struct sockaddr_in clientaddr;socklen_t len = sizeof(struct sockaddr_in);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);ev.events = EPOLLIN;ev.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);printf("clientfd: %d\n", clientfd);}else if (events[i].events & EPOLLIN){char buffer[10] = { 0 }; // 只要有数据就会一直触发,因此会回复多次int count = recv(curfd, buffer, 10, 0);if (count == 0){printf("disconnect\n");close(curfd);epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);continue;}send(curfd, buffer, count, 0);printf("clientfd: %d, sount: %d, buffer: %s\n", curfd, count, buffer);}}}

3.2 基于 epoll 模拟实现面对事件的 reactor 的底层原理

定义结构体,为了方便,直接把 epfd 定位全局变量

#define BUFFER_LENGTH	1024typedef int (* RCALLBACK)(int fd);// save buffer data
struct conn_item
{int fd;char rbuffer[BUFFER_LENGTH];int rlen;char wbuffer[BUFFER_LENGTH];int wlen;union // 联合,在后续代码中会用到{RCALLBACK accept_callback;RCALLBACK recv_callback;}recv_t;RCALLBACK send_callback;
};struct conn_item connlist[1024];#if 1
int epfd;
#elif
struct reactor
{int epfd;struct conn_item connlist[1024];
};
#endif

reactor 的模拟借助以下回调函数实现,可以简化代码。

int set_cb(int fd, int event, int flag);
int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);

具体实现如下,体现出面对事件的处理思想。

/*
1. listenfd 触发 EPOLLIN 事件 -> 执行 accept_cb
2. client 触发 EPOLLIN 事件 -> recv_cb
3. client 触发 EPOLLOUT 事件 -> send_cb
*/// ADD: flag == 1 else 0
int set_cb(int fd, int event, int flag)
{if (flag){struct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);}else{struct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);}
}int accept_cb(int fd)
{struct sockaddr_in clientaddr;socklen_t len = sizeof(struct sockaddr_in);int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);set_cb(clientfd, EPOLLIN, 1);// set connlistconnlist[clientfd].fd = clientfd;memset(connlist[clientfd].wbuffer, 0, BUFFER_LENGTH);connlist[clientfd].wlen = 0;memset(connlist[clientfd].rbuffer, 0, BUFFER_LENGTH);connlist[clientfd].rlen = 0;// set callbackconnlist[clientfd].recv_t.recv_callback = recv_cb;connlist[clientfd].send_callback = send_cb;printf("clientfd: %d\n", clientfd);return clientfd;
}int recv_cb(int fd)
{char * buffer = connlist[fd].rbuffer;int index = connlist[fd].rlen;int count = recv(fd, buffer + index, BUFFER_LENGTH - index, 0);if (count == 0){printf("disconnect\n");epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);close(fd);return -1;}connlist[fd].rlen += count;#if ENABLE_HTTP_RESPONSE// 此处可以自行修改,使对应不同输入实现特定输出,如 http 相应:
http_response(&connlist[fd]);#else// 不做处理直接发送返回
memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, connlist[fd].rlen);
connlist[fd].wlen = connlist[fd].rlen;#endif// eventset_cb(fd, EPOLLOUT, 0);return count;
}int send_cb(int fd)
{char * buffer = connlist[fd].wbuffer;int index = connlist[fd].wlen;int count = send(fd, buffer, index, 0);// 事件来一次执行一次set_cb(fd, EPOLLIN, 0);return count;
}

mainloop 部分

int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port = htons(2048);if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))){perror("bind");return -1;}listen(sockfd, 10);connlist[sockfd].fd = sockfd;connlist[sockfd].recv_t.accept_callback = accept_cb;// epoll 边缘触发/*对于 IO 处理:随着时间增多会越来越复杂if(listenfd){...}else // clientfd{...}对于事件处理 -> reactor 对事件反应更缓和if (events & EPOLLIN){...}else if (events & EPOLLOUT){...}*/epfd = epoll_create(1); // int sizeset_cb(sockfd, EPOLLIN, 1);struct epoll_event events[1024] = { 0 };while (1) // main loop{int nready = epoll_wait(epfd, events, 1024, -1);int i = 0;for (i = 0; i < nready; i++){int curfd = events[i].data.fd;// 由于结构体内 accept_callback() 与 recv_callback() 共享同一块内存,故此处 if 条件判断可以省略。// if (sockfd == curfd)// {// 	// accept_cb()// 	// int clientfd = accept_cb(sockfd);// 	int clientfd = connlist[sockfd].recv_t.accept_callback(sockfd);// 	printf("client: %d\n", clientfd);// }if (events[i].events & EPOLLIN){// int count = recv_cb(curfd);int count = connlist[curfd].recv_t.recv_callback(curfd);if (count != -1)printf("recv <- clientfd: %d, count: %d, buffer: %s\n", curfd, count, connlist[curfd].rbuffer);}else if (events[i].events & EPOLLOUT){// int count = send_cb(curfd);int count = connlist[curfd].send_callback(curfd);printf("send -> clientfd: %d, count: %d, buffer: %s\n", curfd, count, connlist[curfd].wbuffer);}}}return 0;
}

附:本文的 http_response() 函数定义,以供测试使用。

#include <time.h>typedef struct conn_item connection_t;int http_response(connection_t *conn)
{const char *html_body = "<html><head><title>chipen.com</title></head><body><h1>chipen</h1></body></html>";int content_length = strlen(html_body);// 生成符合 HTTP 标准格式的 Date 字符串time_t now = time(NULL);struct tm *gmt = gmtime(&now);char date_str[128];strftime(date_str, sizeof(date_str), "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", gmt);// 构建完整的 HTTP 响应conn->wlen = snprintf(conn->wbuffer, BUFFER_LENGTH,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Content-Length: %d\r\n""Accept-Ranges: bytes\r\n""%s""\r\n"  // Header 与 Body 的分隔符"%s",   // HTML 内容content_length,date_str,html_body);return conn->wlen;
}

相关文章:

5.9-selcct_poll_epoll 和 reactor 的模拟实现

5.9-select_poll_epoll 本文演示 select 等 io 多路复用函数的应用方法&#xff0c;函数具体介绍可以参考我过去写的博客。 先绑定监听的文件描述符 int sockfd socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(struc…...

《算法导论(第4版)》阅读笔记:p17-p27

《算法导论(第4版)》学习第 10 天&#xff0c;p17-p27 总结&#xff0c;总计 11 页。 一、技术总结 1. insertion sort (1)keys The numbers to be sorted are also known as the keys(要排序的数称为key)。 第 n 次看插入排序&#xff0c;这次有两个地方感触比较深&#…...

软考错题集

一个有向图具有拓扑排序序列&#xff0c;则该图的邻接矩阵必定为&#xff08;&#xff09;矩阵。 A.三角 B.一般 C.对称 D.稀疏矩阵的下三角或上三角部分包含非零元素&#xff0c;而其余部分为零。一般矩阵这个术语太过宽泛&#xff0c;不具体指向任何特定性 质的矩阵。对称矩阵…...

T2I-R1:通过语义级与图像 token 级协同链式思维强化图像生成

文章目录 速览摘要1 引言2 相关工作统一生成与理解的 LMM(Unified Generation and Understanding LMM.)用于大型推理模型的强化学习(Reinforcement Learning for Large Reasoning Models.)3 方法3.1 预备知识3.2 语义级与令牌级 CoT语义级 CoT(Semantic-level CoT)令牌级…...

Dockers部署oscarfonts/geoserver镜像的Geoserver

Dockers部署oscarfonts/geoserver镜像的Geoserver 说实话&#xff0c;最后发现要选择合适的Geoserver镜像才是关键&#xff0c;所以所以所以…&#x1f437; 推荐oscarfonts/geoserver的镜像&#xff01; 一开始用kartoza/geoserver镜像一直提示内存不足&#xff0c;不过还好…...

【脑机接口临床】脑机接口手术的风险?脑机接口手术的应用场景?脑机接口手术如何实现偏瘫康复?

脑机接口的应用 通常对脑机接口感兴趣的两类人群&#xff0c;一类是适应症患者 &#xff0c;另一类是科技爱好者。 1 意念控制外部设备 常见的外部设备有&#xff1a;外骨骼、机械手、辅助康复设备、电刺激设备、电脑光标、轮椅。 2 辅助偏瘫康复或辅助脊髓损伤患者意念控制…...

扩增子分析|微生物生态网络稳定性评估之鲁棒性(Robustness)和易损性(Vulnerability)在R中实现

一、引言 周集中老师团队于2021年在Nature climate change发表的文章&#xff0c;阐述了网络稳定性评估的原理算法&#xff0c;并提供了完整的代码。自此对微生物生态网络的评估具有更全面的指标&#xff0c;自此网络稳定性的评估广受大家欢迎。本系列将介绍网络稳定性之鲁棒性…...

Client 和 Server 的关系理解

client.py 和 server.py 是基于 MCP&#xff08;Multi-Component Protocol&#xff09;协议的客户端-服务端架构&#xff0c;二者的关系如下&#xff1a; 1. 角色分工 server.py&#xff1a;服务端&#xff0c;负责注册和实现各种“工具函数”&#xff08;如新闻检索、情感分…...

【含文档+PPT+源码】基于微信小程序的社区便民防诈宣传系统设计与实现

项目介绍 本课程演示的是一款基于微信小程序的社区便民防诈宣传系统设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套…...

Kafka的核心组件有哪些?简要说明其作用。 (Producer、Consumer、Broker、Topic、Partition、ZooKeeper)

Kafka 核心组件解析 1. 基础架构图解 ┌─────────┐ ┌─────────┐ ┌─────────┐ │Producer │───▶ │ Broker │ ◀─── │Consumer │ └─────────┘ └─────────┘ └────────…...

Java中对象集合转换的优雅实现【实体属性范围缩小为vo】:ListUtil.convert方法详解

1.业务场景 在开发电商系统时&#xff0c;我们经常需要处理订单信息的展示需求。例如&#xff1a;订单详情页需要显示退款信息列表&#xff0c;而数据库中存储的RefundInfo实体类包含敏感字段&#xff0c;直接返回给前端存在安全风险。此时就需要将RefundInfo对象集合转换为Or…...

【MySQL】存储引擎 - ARCHIVE、BLACKHOLE、MERGE详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

代码随想录第41天:图论2(岛屿系列)

一、岛屿数量&#xff08;Kamacoder 99&#xff09; 深度优先搜索&#xff1a; # 定义四个方向&#xff1a;右、下、左、上&#xff0c;用于 DFS 中四向遍历 direction [[0, 1], [1, 0], [0, -1], [-1, 0]]def dfs(grid, visited, x, y):"""对一块陆地进行深度…...

Vue插槽(Slots)详解

文章目录 1. 插槽简介1.1 什么是插槽&#xff1f;1.2 为什么需要插槽&#xff1f;1.3 插槽的基本语法 2. 默认插槽2.1 什么是默认插槽&#xff1f;2.2 默认插槽语法2.3 插槽默认内容2.4 默认插槽实例&#xff1a;创建一个卡片组件2.5 Vue 3中的默认插槽2.6 默认插槽的应用场景 …...

中国古代史1

朝代歌 三皇五帝始&#xff0c;尧舜禹相传。 夏商与西周&#xff0c;东周分两段。 春秋和战国&#xff0c;一统秦两汉。 三分魏蜀吴&#xff0c;二晋前后延。 南北朝并立&#xff0c;隋唐五代传。 宋元明清后&#xff0c;皇朝至此完。 原始社会 元谋人&#xff0c;170万年前…...

vue +xlsx+exceljs 导出excel文档

实现功能&#xff1a;分标题行导出数据过多&#xff0c;一个sheet表里表格条数有限制&#xff0c;需要分sheet显示。 步骤1:安装插件包 npm install exceljs npm install xlsx 步骤2&#xff1a;引用包 import XLSX from xlsx; import ExcelJS from exceljs; 步骤3&am…...

nginx之proxy_redirect应用

一、功能说明 proxy_redirect 是 Nginx 反向代理中用于修改后端返回的响应头中 Location 和 Refresh 字段的核心指令&#xff0c;主要解决以下问题&#xff1a;协议/地址透传错误&#xff1a;当后端返回的 Location 包含内部 IP、HTTP 协议或非标准端口时&#xff0c;需修正为…...

在 Flink + Kafka 实时数仓中,如何确保端到端的 Exactly-Once

在 Flink Kafka 构建实时数仓时&#xff0c;确保端到端的 Exactly-Once&#xff08;精确一次&#xff09; 需要从 数据消费&#xff08;Source&#xff09;、处理&#xff08;Processing&#xff09;、写入&#xff08;Sink&#xff09; 三个阶段协同设计&#xff0c;结合 Fli…...

Qt 中基于 spdlog 的高效日志管理方案

在开发 Qt 应用程序时,日志记录是一项至关重要的功能,它能帮助我们追踪程序的运行状态、定位错误和分析性能。本文将介绍如何在 Qt 项目中集成 spdlog 库,并封装一个简单易用的日志管理类 QtLogger,实现高效的日志记录和管理。 为什么选择 spdlog? spdlog 是一个快速、头…...

VUE CLI - 使用VUE脚手架创建前端项目工程

前言 前端从这里开始&#xff0c;本文将介绍如何使用VUE脚手架创建前端工程项目 1.预准备&#xff08;编辑器和管理器&#xff09; 编辑器&#xff1a;推荐使用Vscode&#xff0c;WebStorm&#xff0c;或者Hbuilder&#xff08;适合刚开始练手使用&#xff09;&#xff0c;个…...

Linux 学习笔记2

Linux 学习笔记2 一、定时任务调度操作流程注意事项 二、磁盘分区与管理添加新硬盘流程磁盘管理命令 三、进程管理进程操作命令服务管理&#xff08;Ubuntu&#xff09; 四、注意事项 一、定时任务调度 操作流程 创建脚本 vim /path/to/script.sh # 编写脚本内容设置可执行权…...

JS DOM操作与事件处理从入门到实践

对于前端开发者来说&#xff0c;让静态的 HTML 页面变得生动、可交互是核心技能之一。实现这一切的关键在于理解和运用文档对象模型 (DOM) 以及 JavaScript 的事件处理机制。本文将带你深入浅出地探索 DOM 操作的奥秘&#xff0c;并掌握JavaScript 事件处理的方方面面。 目录 …...

Java EE初阶——初识多线程

1. 认识线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。 基本概念&#xff1a;一个进程可以包含多个线程&#xff0c;这些线程共享进程的资源&#xff0c;如内存空间、文件描述符等&#xff0c;但每个线程都有自己独…...

如何删除网上下载的资源后面的文字

这是我在爱给网上下载的音效资源&#xff0c;但是发现资源后面跟了一大段无关紧要的文本&#xff0c;但是修改资源名称后还是有。解决办法是打开属性然后删掉资源的标签即可。...

深入解析C++11委托构造函数:消除冗余初始化的利器

一、传统构造函数的痛点 在C11之前&#xff0c;当多个构造函数需要执行相同的初始化逻辑时&#xff0c;开发者往往面临两难选择&#xff1a; class DataProcessor {std::string dataPath;bool verbose;int bufferSize; public:// 基础版本DataProcessor(const std::string&am…...

Python中的事件循环是什么?事件是怎么个事件?循环是怎么个循环

在Python异步编程中&#xff0c;事件循环&#xff08;Event Loop&#xff09;是核心机制&#xff0c;它通过单线程实现高效的任务调度和I/O并发处理。本文将从事件的定义、循环的运行逻辑以及具体实现原理三个维度展开分析。 一、事件循环的本质&#xff1a;协程与任务的调度器…...

FPGA图像处理(5)------ 图片水平镜像

利用bram形成双缓冲&#xff0c;如下图配置所示&#xff1a; wr_flag 表明 buffer0写 还是 buffer1写 rd_flag 表明 buffer0读 还是 buffer1读 通过写入逻辑控制(结合wr_finish) 写哪个buffer &#xff1b;写地址 进而控制ip的写使能 通过状态缓存来跳转buffer的…...

[python] 类

一 介绍 具有相同属性和行为的事物的通称,是一个抽象的概念 三要素: 类名,属性&#xff0c;方法 格式: class 类名: 代码块 class Pepole:name "stitchcool"def getname(self):return self.name 1.1 创建对象(实例化) 格式: 对象名 类名() p1 Pepole()…...

day21python打卡

知识点回顾&#xff1a; LDA线性判别PCA主成分分析t-sne降维 还有一些其他的降维方式&#xff0c;也就是最重要的词向量的加工&#xff0c;我们未来再说 作业&#xff1a; 自由作业&#xff1a;探索下什么时候用到降维&#xff1f;降维的主要应用&#xff1f;或者让ai给你出题&…...

Android开发-Activity启停

在Android应用开发中&#xff0c;Activity是构建用户界面的基本组件之一。它代表了一个单一的、专注的操作&#xff0c;比如查看一张图片或者撰写一封电子邮件。每个Activity都有其生命周期&#xff0c;从创建到销毁&#xff0c;会经历一系列的状态变化。了解并正确管理这些状态…...