目录
定义三个事件对应的回调函数1.accept_cb() 2.recv_cb() 3.send_cb
Reactor 模式是一种事件驱动的设计模式,核心是通过一个中心组件(Reactor)监听多种事件(如网络 I/O 事件、定时器事件等),当事件发生时,将事件分发给对应的处理器处理。在 C 语言中,常基于操作系统的 I/O 多路复用机制(如 epoll
、select
、kqueue
)实现,用于高效处理高并发场景。
Reactor 模式核心组件(C 语言视角)
事件循环(Event Loop):循环监听事件,调用操作系统的 I/O 多路复用接(如 epoll_wait
),阻塞等待事件就绪。
事件处理器(Event Handler):定义事件处理的回调函数,如读事件处理函数、写事件处理函数。
事件注册模块:将需要监听的事件(如文件描述符的读 / 写事件)注册到 Reactor 中,关联对应的处理器。
#if 1
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#define BUFFER_LENGTH 1024
typedef int(*RCALLBACK)(int fd);
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;
};
int epfd=0;
struct conn_item connList[1024]={0};
#if 0
struct reactor{
int epfd;
struct conn_item*connList;
};
#else
#endif
int set_event(int fd,int event,int flag){
if(flag){ //1.add 0.mod
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);
int recv_cb(int fd);
int send_cb(int fd );
// 回调函数
//listenFd
int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
if (clientfd<0) return -1;
set_event(clientfd,EPOLLIN,1);
connList[clientfd].fd=clientfd;
bzero(connList[clientfd].rbuffer,sizeof (connList[clientfd].rbuffer));
connList[clientfd].rlen=0;
connList[clientfd].recv_t.recv_callback= recv_cb;
connList[clientfd].send_callback= send_cb;
return clientfd;
}
//clientFd
int recv_cb(int fd){
char *buffer = connList[fd].rbuffer;
int idx =connList[fd].rlen;
int count = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
if (count == 0) {
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
return -1;
}
set_event(fd,EPOLLOUT,0);
connList[fd].rlen+=count;
#if 1
memcpy(connList[fd].wbuffer,connList[fd].rbuffer,connList[fd].rlen);
connList[fd].wlen=connList[fd].rlen;
#endif
return count;
}
int send_cb(int fd ){
char *buffer = connList[fd].wbuffer;
int idx =connList[fd].wlen;
int count =send(fd, buffer, idx, 0);
struct epoll_event ev;
ev.events = EPOLLIN ;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
set_event(fd,EPOLLIN,0);
return count;
}
// tcp
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;
}
int reuse =1; //允许重用
int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
if (ret==-1) perror("setsockopt");
listen(sockfd, 10);
connList[sockfd].fd=sockfd;
connList[sockfd].recv_t.accept_callback = accept_cb;
epfd = epoll_create(1); // int size
//pthread_create();
set_event(sockfd,EPOLLIN,1);
struct epoll_event revents[1024] = {0};
while (1) {
int nready = epoll_wait(epfd, revents, 1024, -1);
int i = 0;
for (i = 0;i < nready;i++) {
int connfd = revents[i].data.fd;
if (revents[i].events & EPOLLIN) {
int count =connList[connfd].recv_t.recv_callback(connfd);
printf("recv count%d <-- buffer: %s\n",count,connList[connfd].rbuffer);
}else if (revents[i].events & EPOLLOUT){
int count =connList[connfd].send_callback(connfd);
printf("send --> buffer: %s\n", connList[connfd].wbuffer);
}
}
}
getchar();
//close(clientfd);
}
#endif
这是基于epoll实现的一个reactor模式
实现reactor模式的时候我们要包含相关系统调用的头文件
#include <sys/socket.h>
#include <sys/epoll.h>
我们先通过TCP三次握手 绑定一个自动分配的IP地址 和 2048端口号的服务器
rector模式的本质是把响应IO 转变 为 响应事件 通过对事件的响应调用不同的回调函数实现不同的功能
定义conn_item结构体
#define BUFFER_LENGTH 1024
typedef int(*RCALLBACK)(int fd);
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;
};
定义一个 conn_item结构体 该结构体包含的成员分别有
int fd :对应的文件描述符
char rbuffer :接收客户端发送的数据并保存
char wbuffer:发送给客户端的数据并保存
三个函数指针分别为
指向 accept_cb的函数指针accept_callback
指向recv_cb的函数指针recv_callback
指向send的函数指针send_callback
其中前两个函数指针是进行了union联合响应
设置事件监听函数set_event()
int set_event(int fd,int event,int flag){
if(flag){ //1.add 0.mod
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);
}
}
我们通过传入fd文件描述符 和 我们需要传入的事件状态event 如EPOLLIN EPOLLOUT 读和写操作
定义一个flag用于我们区分是否要添加还是转移
定义三个事件对应的回调函数1.accept_cb() 2.recv_cb() 3.send_cb
// 回调函数
//listenFd
int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
if (clientfd<0) return -1;
set_event(clientfd,EPOLLIN,1);
connList[clientfd].fd=clientfd;
bzero(connList[clientfd].rbuffer,sizeof (connList[clientfd].rbuffer));
connList[clientfd].rlen=0;
connList[clientfd].recv_t.recv_callback= recv_cb;
connList[clientfd].send_callback= send_cb;
return clientfd;
}
//clientFd
int recv_cb(int fd){
char *buffer = connList[fd].rbuffer;
int idx =connList[fd].rlen;
int count = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
if (count == 0) {
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
return -1;
}
set_event(fd,EPOLLOUT,0);
connList[fd].rlen+=count;
#if 1
memcpy(connList[fd].wbuffer,connList[fd].rbuffer,connList[fd].rlen);
connList[fd].wlen=connList[fd].rlen;
#endif
return count;
}
int send_cb(int fd ){
char *buffer = connList[fd].wbuffer;
int idx =connList[fd].wlen;
int count =send(fd, buffer, idx, 0);
struct epoll_event ev;
set_event(fd,EPOLLIN,0);
return count;
}
当有客户端连接的时候事件响应自动调用accept_cb函数
通过accept和客户端建立连接 并获取和客户端进行通信的文件描述符clientfd
我们通过set_event接口把clientfd加入到监听集合中
我们通过函数指针把clientfd传入到recv_cb回调函数中
我们通过rbuffer字符数组 读取客户端发送到本服务器的数据
读完数据之后我们通过set_event接口把该文件描述符的监听状态转移成写操作
因为写就绪是一直触发的 当我们第一次触发写就绪事件的时候我们进入到了send_cb函数中
写完之后立马把通过set_event把写状态改成读状态
结果展示
Reactor 模式优势
高效处理并发:基于 I/O 多路复用,单线程可管理大量文件描述符。
解耦事件与处理:事件监听和处理逻辑分离,代码结构清晰,扩展性强。
资源利用率高:仅在事件就绪时执行处理,减少无效等待,适合网络服务器、高性能 I/O 应用等场景