事件驱动reactor 基于epoll与事件回调
reator是高并发网络编程的经典事件驱动模式,核心思想是 事件监控与业务处理解耦:
- reactor线程(主线程):仅做一件事,即通过epoll_wait监控fd的事件(EPOLLIN/EPOLLOUT),不处理具体的业务;
- 回调函数:事件就绪后,主线程调用绑定的回调函数处理具体业务;
- 事件驱动:业务处理完成后,通过set_event切换事件类型(比如读完以后切成写入事件),让reactor线程继续监控;
- 连接管理:统一管理每个连接的fd、缓冲区、回调函数、状态,实现连接的一站式管理。
核心结构体
首先封装一个连接管理结构体
struct conn{
int fd;
char rbuffer[BUFFER_LEN];
int rlen;
char wbuffer[BUFFER_LEN];
int wlen;
RCALLBACK send_callback;
union{
RCALLBACK recv_callback;//clientfd
RCALLBACK accept_callback;//listenfd
}r_action;
int status;
};
fd:连接对应的fd 监听fd 客户端fd
rbuffer[BUFFER_LEN]:读缓冲区 存储客户端请求数据
wbuffer[BUFFER_LEN]:写缓冲区:存储服务器响应数据
RCALLBACK send_callback:写事件(EPOLLOUT)回调函数,所有连接共用send_cb
union{
RCALLBACK recv_callback;//clientfd
RCALLBACK accept_callback;//listenfd
}r_action :客户端fd可读事件和监听fd可读事件EPOLLIN的回调函数联合体,
status:连接状态 0=空闲/完成、1=发送中、2=等待发送 控制写事件的执行逻辑,比如是否继续发送、是否切换回读事件
多端口高并发支持HTTP的reactor模式TCP服务器
- 高并发处理:基于epoll的边缘触发模式
- 事件驱动回调:监听 fd 的 EPOLLIN 事件 → 触发accept_cb(接新连接);
客户端 fd 的 EPOLLIN 事件 → 触发recv_cb(读请求);
客户端 fd 的 EPOLLOUT 事件 → 触发send_cb(发响应);
main函数 reactor主线程入口
main函数只做事件监控和事件分发,完全不管业务逻辑
int main(){
unsigned short port = 2000;
//1.创建epoll实例
//这是reactor事件管理器
epfd = epoll_create(1);
//2.初始化监听端口
int i = 0;
for(i = 0;i < MAX_PORTS; i++){
int sockfd = init_server(port + i);
conn_list[sockfd].fd = sockfd;
conn_list[sockfd].r_action.recv_callback = accept_cb;
set_event(sockfd, EPOLLIN, 1);
}
gettimeofday(&begin, NULL);
//3.mainloop reactor主循环
while(1){
struct epoll_event events[1024] = {0};
//阻塞等待epoll事件就绪 实现事件监控
int nready = epoll_wait(epfd, events, 1024, -1);
//4. 遍历就绪事件 触发对应的回调 实现事件分发
int i = 0;
for(i = 0; i<nready; i++){
int connfd = events[i].data.fd;
//读写同时并存
//读事件就绪
//新客户端连接or客户端发数据->调用recv_callback(accept_cb/recv_cb)
if(events[i].events & EPOLLIN){
conn_list[connfd].r_action.recv_callback(connfd);
}
//写事件就绪
//客户端请求数据,可发响应->调用send_callback(send_cb)
if(events[i].events & EPOLLOUT){
conn_list[connfd].send_callback(connfd);
}
}
}
}
回调函数 业务处理
主要涉及以下五个函数
- init_server:基础TCP初始化
- accept_cb:新客户端连接事件回调
- recv_cb:读事件回调 处理客户端数据(请求数据)
- send_cb:写事件回调 发送响应(发给客户端)
- set_event:epoll 事件管理封装
- init_server:基础TCP初始化
主要作用是初始化监听端口
int init_server(unsigned short port){
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
/*
绑定本地地址 0.0.0.0
三个端口都能接收
*/
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
servaddr.sin_port = htons(port);
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
printf("bind failed: %s\n", strerror(errno));
}
/*
tcp server
两种fd 两种socket
监听fd listen
客户端fd recv
*/
listen(sockfd, 10);
//printf("listen finished: %d\n", sockfd);
return sockfd;
}
- accept_cb:新客户端连接事件回调
客户端fd的回调函数绑定为recv_cb(读数据)和send_cb(发响应);
启用边缘触发(EPOLLET):相比水平触发(LT),ET 模式只会在 “事件状态变化” 时触发一次,减少不必要的回调。
int accept_cb(int fd){
//1.accept新连接
//fd是监听fd 这里是有新的客户端连接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
//printf("accept finished: %d\n",clientfd);
if(clientfd < 0)
return -1;
//这个fd是clientfd 以下为设置新加入的这个fd
//2.初始化客户端连接 依然是用fd作为数组索引
conn_list[clientfd].fd = clientfd;
//读事件绑定recv_cb
conn_list[clientfd].r_action.recv_callback = recv_cb;
//写事件绑定send_cb
conn_list[clientfd].send_callback = send_cb;
//清空读写缓冲区
memset(conn_list[clientfd].rbuffer, 0, BUFFER_LEN);
conn_list[clientfd].rlen = 0;
memset(conn_list[clientfd].wbuffer, 0, BUFFER_LEN);
conn_list[clientfd].wlen = 0;
//3.设置客户端fd的epoll事件
//默认是水平触发,| EPOLLET后是边沿触发
//设置为ET+EPOLLIN(先是可读事件) flag = 1表示ADD
set_event(clientfd, EPOLLIN | EPOLLET, 1);
if((clientfd % 1000) == 0){
struct timeval current;
gettimeofday(¤t, NULL);
int time_used = TIME_SUB_MS(current, begin);
memcpy(&begin, ¤t, sizeof(struct timeval));
//printf("accept finished: %d, timeused: %d\n",clientfd, time_used);
}
return 0;
}
- recv_cb:读事件回调 处理客户端数据(请求数据)
读数据完成后,主动切换事件类型为 EPOLLOUT,让 Reactor 主线程监控 “可写事件”,准备发响应。
int recv_cb(int fd){
//1.清空读缓冲区,准备接收客户端数据
conn_list[fd].rlen = 0;
memset(conn_list[fd].rbuffer, 0, BUFFER_LEN);
int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LEN, 0);
//2.异常&断开连接则移除fd
if(count == 0){
//printf("disconnect: %d\n", fd);
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}else if(count < 0){
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd ,NULL);
return 0;
}
//3.记录读缓冲区数据长度
conn_list[fd].rlen = count;
//4.处理请求
//这里先模拟一下,直接打印出来
memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, count);
conn_list[fd].wlen = count;
printf("recv: %s\n", conn_list[fd].rbuffer);
//5.切换事件为EPOLLOUT(准备发响应)
//受到客户端的请求,接下来准备发送响应了 此时切换事件类型
//flag=0表示MOD
set_event(fd, EPOLLOUT, 0);
return count;
}
- send_cb:写事件回调 发送响应(发给客户端)
根据连接状态控制 “发送逻辑”,发送完成后切换回 EPOLLIN 事件,等待客户端下一次请求,实现监控fd、收发数据的事件循环。
int send_cb(int fd){
//1.处理响应
//2.根据连接状态发送数据
int count = 0;
if(conn_list[fd].status == 1){
//状态1:响应数据发送中
//数据可能一次发送不完 继续监控EPOLLOUT
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlen, 0);
//继续监控写事件 又会调用send_cb回到这个函数
set_event(fd, EPOLLOUT, 0);
}
//状态2:等待发送(暂时不发) 继续监控写事件
else if(conn_list[fd].status == 2){
set_event(fd, EPOLLOUT, 0);
}
//状态0:响应发送完成 或者 空闲
else if(conn_list[fd].status == 0){
if(conn_list[fd].wlen != 0){
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlen, 0);
//printf("send: %d\n", count);
}
//发送完剩余事件后切换回读事件继续监控客户端fd
set_event(fd, EPOLLIN, 0);
}
return count;
}
- set_event:epoll 事件管理封装
封装 epoll_ctl 的复杂调用,对外提供简单的 “添加 / 修改事件” 接口,减少代码冗余
int set_event(int fd, int event, int flag){
if(flag){//add
//flag = 1 添加事件 EPOLL_CTL_ADD
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}else{//mod
//flag = 0 修改事件 EPOLL_CTL_MOD
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
Reactor 模式实现的核心优势 对比基础 epoll
解耦事件监控和业务处理,实现高并发、高扩展、高性能的网络服务器,主要的价值体现在解耦,新增协议 / 功能只需加回调,无需改核心循环。
更多推荐


所有评论(0)