一、什么是 Reactor:
Reactor 模式是一种事件驱动的设计模式,它通过管理事件而非管理 IO 操作来响应多个输入/输出请求。在这种模式中,应用程序不需要持续监控或直接处理 IO 操作,而是关注于发生的事件及其响应。这种方式使得应用程序能够高效地管理多个并发的输入/输出源,通过非阻塞的方式提高了系统整体的性能和响应能力。
二、Reactor 模式的核心原则和操作:
-
不同的 IO 事件对应不同回调:
- 在 Reactor 模式中,每种类型的 IO 操作(如读、写、连接等)都关联到特定的事件。每个事件都有相应的回调函数与之对应,当事件发生时,对应的回调函数被触发执行。
-
事件注册与回调机制:
- Register (注册):应用程序向 Reactor 注册感兴趣的事件及其对应的事件处理器(回调函数)。例如,应用程序可能对监听套接字(listenfd)上的输入事件(EPOLLIN)感兴趣,并注册一个接受新连接的回调函数(accept_cb)。
- Callback (回调):当对应的事件被触发时,Reactor 会自动调用之前注册的回调函数来处理该事件。
-
IO 事件转换为回调执行的示例:
listenfd->EPOLLIN->accept_cb
:当监听套接字(listenfd)准备好接收新的连接时(EPOLLIN),执行接受连接的回调函数(accept_cb)。clientfd->EPOLLIN->recv_cb
:当客户端套接字(clientfd)有数据可读时(EPOLLIN),执行接收数据的回调函数(recv_cb)。clientfd->EPOLLOUT->send_cb
:当客户端套接字(clientfd)准备好发送数据时(EPOLLOUT),执行发送数据的回调函数(send_cb)。
三、Reactor 的核心功能:
-
事件与回调的匹配:
- Reactor 维护一个或多个事件处理器(Event Handlers)和事件(Event)的映射表。当事件发生时,Reactor 查找这个映射表,找到相应的事件处理器并执行。
-
每一个 IO 与之对应的参数:
- 每个注册的事件处理器(回调)不仅与特定的事件类型相关联,还可以与特定的 IO 资源(如文件描述符)和附加参数(如用户数据)关联。这使得处理程序能够更灵活地根据上下文执行适当的操作。
Reactor 模式通过将 IO 操作转换为事件驱动的回调执行,极大地提高了应用程序处理并发连接和请求的能力,特别适用于高性能的网络服务和应用程序。
四、代码实践
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/time.h>
#define BUFFER_LENGTH 1024
#define CONNECTION_SIZE 1048567
#define MAX_PORTS 20
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
typedef int (*RCALLBACK)(int fd);
int epfd = 0;
struct timeval begin;
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;
} raction;
};
struct conn conn_list[CONNECTION_SIZE] = {0};
// fd
int set_event(int fd, int event, int flag)
{
if (flag) // non-zero add
{
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}
else // zero mod
{
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
int recv_cb(int fd)
{
int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
if (count == 0)
{
// 客户端关闭了连接
printf("Client closed the connection: %d\n", fd);
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}
conn_list[fd].rlength = count;
// printf("RECV: %s\n", conn_list[fd].rbuffer);
#if 1 // echo
conn_list[fd].wlength = conn_list[fd].rlength;
memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);
#endif
set_event(fd, EPOLLOUT, 0);
}
int send_cb(int fd)
{
int count = send(fd, conn_list[fd].wbuffer, count, 0);
set_event(fd, EPOLLIN, 0);
return count;
}
int event_register(int fd, int event)
{
if (fd < 0)
return -1;
// 新的连接设置不同的回调函数
conn_list[fd].fd = fd;
conn_list[fd].raction.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);
}
// listenfd(sockfd) -->EPOLLIN --> accept_cb
int accept_cb(int fd)
{
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
// printf("accept finshed: %d\n", clientfd);
if (clientfd < 0)
{
printf("accept error: %d --> %s\n", errno, strerror(errno));
return -1;
}
event_register(clientfd, EPOLLIN);
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 finshed: %d\n, time_used: %d\n", clientfd, time_used);
}
return 0;
}
int init_sever(unsigned short port)
{
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(port); // 0-1023
if (-1 == bind(socketfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)))
{
printf("bind failed: %s\n", strerror(errno));
}
listen(socketfd, 10);
printf("listen finshed: %d\n", socketfd);
return socketfd;
}
int main()
{
unsigned short port = 2000;
epfd = epoll_create(1);
int i = 0;
for (i = 0; i < MAX_PORTS; i++)
{
int socketfd = init_sever(port + i);
conn_list[socketfd].fd = socketfd;
conn_list[socketfd].raction.recv_callback = accept_cb;
set_event(socketfd, EPOLLIN, 1);
}
gettimeofday(&begin, NULL);
while (1)
{ // mainloop
struct epoll_event events[1024] = {0};
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for (i = 0; i < nready; i++)
{
int connfd = events[i].data.fd;
#if 0
if (events[i].events & EPOLLIN)
{
conn_list[connfd].raction.recv_callback(connfd);
}
else if (events[i].events & EPOLLOUT)
{
conn_list[connfd].send_callback(connfd);
}
#else // io读写事件并存
if (events[i].events & EPOLLIN)
{
conn_list[connfd].raction.recv_callback(connfd);
}
if (events[i].events & EPOLLOUT)
{
conn_list[connfd].send_callback(connfd);
}
#endif
}
}
}
1、不同的io事件,做不同的action
2、io做到独立