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

用 epoll 实现的 Reactor 模式详解(含代码逐块讲解)

在高并发网络编程中,如何高效地处理大量连接是一个核心问题。传统的阻塞 I/O 或者“一连接一线程”的模型,在线程数和上下文切换上会造成巨大的开销。为了解决这些问题,Reactor 模式应运而生,它结合了 I/O 复用技术(如 epoll),成为构建高性能服务器的常见架构。

本文将从 Reactor 原理 入手,结合一段基于 epoll 的 C 语言实现代码,详细讲解 Reactor 是如何工作的

一、Reactor 模式原理

Reactor 模式可以理解为一个 事件驱动模型,它的核心思想是:

  • 将 I/O 事件(如连接请求、数据可读、可写)交给内核去监听;

  • 一旦有事件发生,内核会通知 Reactor;

  • Reactor 再分发事件给对应的处理函数(回调)。

通俗点说:

  • Reactor 就像“事件总线”,它负责把 事件(事件分发)事件处理(回调函数) 解耦。

  • 程序员只需要关注“当某个事件发生时我要干什么”。

对比:

  • 阻塞 I/O:线程被阻塞在 accept/recv 上,浪费资源。

  • 多线程:一个连接一个线程,线程切换开销大。

  • Reactor + epoll:少量线程监听所有事件,事件就绪时再分发处理。

二、代码实现思路

代码整体分为以下几个模块:

  1. 连接对象结构体(conn_list):保存 fd、读写缓冲区、回调函数。

  2. 事件注册函数(set_event/event_register):负责向 epoll 注册、修改事件。

  3. accept 回调(accept_cb):处理新客户端的连接。

  4. recv 回调(recv_cb):处理客户端发来的数据。

  5. send 回调(send_cb):向客户端发送数据。

  6. 主循环(main + epoll_wait):Reactor 的核心,监听并分发事件。

三、代码分块详解

1) 连接上下文结构体 struct conn

struct conn{int fd;char rbuffer[BUFFER_LENGTH];int rlength;char wbuffer[BUFFER_LENGTH];int wlength;RCALLBACK send_callback;union{RCALLBACK recv_callback;RCALLBACK accept_callback;} r_action;
};
  • 每个连接(index 用 fd)维护一个结构:读取缓冲区 rbuffer + 长度 rlength、写缓冲区 wbuffer + 长度 wlength

  • r_action.recv_callback:当该 fd 可读时调用的函数(对监听 socket 这位置复用为 accept_cb)。

  • send_callback:当该 fd 可写时调用的函数(send_cb)。

  • 这样的设计方便事件分派:在 epoll_wait 中直接用 conn_list[fd].r_action.recv_callback(connfd); 调用对应 handler。

2) init_server(port) —— socket 初始化

int init_server(unsigned short port){int sockfd = socket(AF_INET, SOCK_STREAM, 0);// bind、listenreturn sockfd;
}

标准服务器初始化:socketbindlisten

3) set_event(fd, event, flag) —— 封装 epoll_ctl

int set_event(int fd, int event, int flag){if(flag){ // addstruct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);}else{ // modstruct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);}
}
  • flag != 0EPOLL_CTL_ADD(新增);flag == 0EPOLL_CTL_MOD(修改)。

  • ev.data.fd = fd:回传 fd,主循环用来定位 conn_list

4) event_register(fd, event) —— 初始化 conn_list 并 ADD

int event_register(int fd, int event){
conn_list[fd].fd = fd;
conn_list[fd].r_action.recv_callback = recv_cb;
conn_list[fd].send_callback = send_cb;memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);
conn_list[fd].rlength = 0;memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);
conn_list[fd].wlength = 0;set_event(fd, event, 1);
}
  • 初始化一个连接对象(清空读写缓冲区);

  • 设置回调函数;

  • 注册到 epoll。

5) accept_cb(fd) —— 新连接处理

int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
printf("accept finished: %d\n", clientfd);event_register(clientfd, EPOLLIN);
return 0;
}
  • 当监听 socket 可读时,说明有新连接到来;

  • 调用 accept 获取新客户端 fd;

  • 使用 event_register 注册该客户端,关注 EPOLLIN(读事件)。

6) recv_cb(fd) —— 读回调(核心逻辑)

int recv_cb(int fd){int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);if(count == 0){ // 客户端断开close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);return 0;}conn_list[fd].rlength = count;// echo: 拷贝到 wbufferconn_list[fd].wlength = conn_list[fd].rlength;memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);set_event(fd, EPOLLOUT, 0); // 切换为可写监听return count;
}
  • 从客户端读数据,存到 rbuffer

  • 如果 count==0,说明客户端断开,关闭连接;

  • 否则将数据复制到写缓冲区;

  • 把收到的数据长度写入 rlength,并拷贝到 wbuffer,设置 wlength —— 因为后续 send_cb 会使用这些字段发送回去。

  • 最后通过 set_event(fd, EPOLLOUT, 0) 修改该 fd 在 epoll 中关注为可写事件,以便下一轮 epoll_wait 返回时触发写回调。

7) send_cb(fd) —— 写回调

int send_cb(int fd){int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);set_event(fd, EPOLLIN, 0);return count;
}
  • wbufferwlength 字节发送出去(注意:send 可能返回已发送字节数 < wlength,需要处理短写并保存未发送的数据)。

  • 发送完成后修改监听为 EPOLLIN,回到读等待。

8) main 主循环:注册监听 socket 并 dispatch

int sockfd = init_server(port);
epfd = epoll_create(1);// 初始化监听 socket 的 conn_list,绑定 accept_cb
conn_list[sockfd].fd = sockfd;
conn_list[sockfd].r_action.recv_callback = accept_cb;
set_event(sockfd, EPOLLIN, 1);while(1){struct epoll_event events[1024] = {0};int nready = epoll_wait(epfd, events, 1024, -1);for(i=0;i<nready;i++){int connfd = events[i].data.fd;if(events[i].events & EPOLLIN){conn_list[connfd].r_action.recv_callback(connfd);}if(events[i].events & EPOLLOUT){conn_list[connfd].send_callback(connfd);}}
}
  • epoll_wait 阻塞等待事件发生;

  • 遍历所有就绪的 fd,根据事件类型(EPOLLIN/EPOLLOUT)调用回调函数;

0voice · GitHub


文章转载自:

http://WfAtnMnY.rtsdz.cn
http://iHK305JQ.rtsdz.cn
http://jLLflUiD.rtsdz.cn
http://4bZR0nsa.rtsdz.cn
http://g74JjslN.rtsdz.cn
http://w8pcfkF6.rtsdz.cn
http://87vzWFjL.rtsdz.cn
http://R7yHYCM5.rtsdz.cn
http://JTYSFnXr.rtsdz.cn
http://knjpH016.rtsdz.cn
http://kFjt61an.rtsdz.cn
http://FjP5K6eS.rtsdz.cn
http://0gsxxIKs.rtsdz.cn
http://yaSyer5o.rtsdz.cn
http://yDD4KOs7.rtsdz.cn
http://7qaQqeKJ.rtsdz.cn
http://9eLF3lrj.rtsdz.cn
http://Gwgmvq4U.rtsdz.cn
http://GrPWTFlW.rtsdz.cn
http://QHaELwPT.rtsdz.cn
http://Pr1RabwB.rtsdz.cn
http://sY02vCgU.rtsdz.cn
http://RCFYT4dy.rtsdz.cn
http://meS2uquM.rtsdz.cn
http://FQnFdzXy.rtsdz.cn
http://vvzYTLnl.rtsdz.cn
http://RS52rGhw.rtsdz.cn
http://Wtt4etR3.rtsdz.cn
http://2eD0jTYo.rtsdz.cn
http://ziZUPOii.rtsdz.cn
http://www.dtcms.com/a/368235.html

相关文章:

  • Linux ARM64 内核/用户虚拟空间地址映射
  • linux inotify 功能详解
  • C++中虚函数与构造/析构函数的深度解析
  • 工业客户最关心的,天硕工业级SSD固态硬盘能解答哪些疑问?
  • 在宝塔面板中修改MongoDB配置以允许远程连接
  • 84 数组地址的几种计算方式
  • GCC编译器深度解剖:从源码到可执行文件的全面探索
  • OpenSCA开源社区每日安全漏洞及投毒情报资讯| 4th Sep. , 2025
  • Java 操作 Excel 全方位指南:从入门到避坑,基于 Apache POI
  • 多云战略的悖论:为何全局数据“看得见”却“算不起”?
  • 深入剖析Spring动态代理:揭秘JDK动态代理如何精确路由接口方法调用
  • More Effective C++ 条款29:引用计数
  • 人形机器人控制系统核心芯片从SoC到ASIC的进化路径
  • Docker学习笔记(三):镜像与容器管理进阶操作
  • excel里面店铺这一列的数据结构是2C【uniteasone17】这种,我想只保留前面的2C部分,后面的【uniteasone17】不要
  • Qt图片资源导入
  • 苍穹外卖Day10 | 订单状态定时处理、来单提醒、客户催单、SpringTask、WebSocket、cron表达式
  • 01-Hadoop简介与生态系统
  • 如何利用静态代理IP优化爬虫策略?从基础到实战的完整指南
  • 信息安全工程师考点-网络信息安全概述
  • 功能强大的多线程端口扫描工具,支持批量 IP 扫描、多种端口格式输入、扫描结果美化导出,适用于网络安全检测与端口监控场景
  • 自定义格式化数据(BYOFD)(81)
  • 人工智能时代职能科室降本增效KPI设定全流程与思路考察
  • 使用 chromedp 高效爬取 Bing 搜索结果
  • Linux 命令速查宝典:从入门到高效操作
  • 【科研绘图系列】R语言绘制论文合集图
  • 分类、目标检测、实例分割的评估指标
  • 卷积神经网络进行图像分类
  • Java JVM核心原理与面试题解析
  • 【Flutter】RefreshIndicator 无法下拉刷新问题