reactor其实没那么神秘
- 背景
- 介绍
- 实现一个单线程的reactor(epoll)
- 单独事件结构体
- reactor总表
- reactor事件增删改
- 总结
- 优点
- 缺点
- 使用到reactor的开源库
背景
高性能服务器的开发需要考虑到3点:I/O事件、定时事件、信号。
对于多并发的场景,我们应该如何高效的管理和操作多个IO端口,日常有两种高效的事件处理模型:reactor和proactor,值得我们借鉴。以下讲的是reactor。高性能网络服务器的必备技术之一。
介绍
reactor模式中文翻译为反应堆,是一种事件驱动机制,简单来说是对io多路复用上的封装,用于管理多个io端口。可以及时对io数据进行处理
reacor中心思想是将所有的事件注册到多路复用器上,等待I/O事件的到来,调用事先注册的事件(这些注册的事件也叫做回调函数),到对应的处理器中。
reactor的3种模式
- 单线程模式
- 多线程模式(单reactor)
- 多线程模式(多reactor)
这篇文章我们主要介绍最简单的单线程模式
reactor内部由3个组件组成:多路复用器+ 事件分发器 + 事件处理器。
小黑板:
- 多路复用器:由操作系统提供,在linux一般就是select、poll、epoll等系统调用
- 事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中
- 事件处理器:处理特定事件的处理函数
实现一个单线程的reactor(epoll)
reactor重 将每个事件封装成一个结构体,而后将这些结构体以下写出reactor一些重要的部分,用于封装epoll模型
单独事件结构体
- 描述
用于管理每一个IO事件
- 代码
struct ntyevent {
int fd; // sockfd
int events; // event事件,即触发的条件
void *arg; // 参数,避免有需要参数传递
int (*callback)(int fd, int events, void *arg); // 回调函数,当触发时使用什么函数处理
int status; // 状态,用于判断该事件是否删除
char buffer[BUFFER_LENGTH]; // 接收或需要发送的数据
int length; // 实际接收或发送数据的长度
long last_active; // 最后活跃的时间,用于关闭一段时间内不活跃的事件
};
reactor总表
- 描述
用于控制整个reactor事件,由所有的io事件组成,这里我们用指针对其集合,当然也可以使用数组或红黑树
- 代码
struct ntyreactor {
int epfd; // 为epoll_create创建的红黑树节点
struct ntyevent *events; // 所有事件的结合
};
reactor事件增删改
// 将信息与事件进行绑定
void nty_event_set(struct ntyevent *ev, int fd, NCALLBACK callback, void *arg) {
ev->fd = fd;
ev->callback = callback;
ev->events = 0;
ev->arg = arg;
ev->last_active = time(NULL);
return ;
}
// 事件添加
int nty_event_add(int epfd, int events, struct ntyevent *ev) {
struct epoll_event ep_ev = {0, {0}};
ep_ev.data.ptr = ev;
ep_ev.events = ev->events = events;
int op;
if (ev->status == 1) {
op = EPOLL_CTL_MOD;
} else {
op = EPOLL_CTL_ADD;
ev->status = 1;
}
if (epoll_ctl(epfd, op, ev->fd, &ep_ev) < 0) {
printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
return -1;
}
return 0;
}
// 事件删除
int nty_event_del(int epfd, struct ntyevent *ev) {
struct epoll_event ep_ev = {0, {0}};
if (ev->status != 1) {
return -1;
}
ep_ev.data.ptr = ev;
ev->status = 0;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_ev);
return 0;
}
总结
优点
- 响应快:不必为单个同步时间所阻塞
- 可扩展性:可以方便的通过增加reactor个数来充分利用CPU资源
- 可复用性:reactor框架本身与具体事件处理逻辑无关,具有很高的复用性
- 编程相对简单:可以最大程度的避免复杂的多线程和同步的问题
缺点
当前线程出现了一个长时间的IO数据读写,则会影响其他的client
使用到reactor的开源库
使用到reactor模式的开源代码:nginx、libevent