Reactor
介绍
Reactor模式网上有很多讲解,我这里不想过多介绍。其核心共有三个。
- 抽象的事件
- 事件多路分发器
- 一个Reactor用来管理整个流程
reactor by c
Reactor是我们从代码中抽取出来方便我们进行管理的。但这里我们完全不去管它。很暴力地使用面向过程的思想去做。
- 抽象的事件——直接被我们具体化为fd
- 事件多路分发器——直接被我们具体化为epoll
代码思路
服务器端的常规设置。socket-bind-listen,但要注意设置成为nonblock。
现在我们就得到了listenfd。我们希望当有客户连接到来的时候,其自动帮助我们去回调accept_conn函数。该怎么办呢?
借助epoll
epoll的作用是当文件描述符就绪的时候,通知epoll的使用者该文件描述符就绪。
很明显,我们将listenfd注册到epoll中后,当有连接到来时,epoll就会通知我们listenfd已经就绪。按照以前的思路,我们会去做一个if判断,如果返回的fd是listenfd就去调用accept_conn。如果是其它fd,就去做其它的事情。
但这里我们将利用回调函数的思想。当有事件就绪(即有fd就绪时候),我们无脑回调一个叫做call_back的函数。(很明显,这是一个函数指针)
int nfd = epoll_wait(g_efd, events, MAX_EVENTS + 1, 1000);
my_error(nfd, "epoll_wait error");
for(i = 0; i < nfd; ++i)
{
struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
if((events[i].events & EPOLLIN) && (ev->events & EPOLLIN))
ev->call_back(ev->fd, events[i].events, ev->arg);
if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
ev->call_back(ev->fd, events[i].events, ev->arg);
}
抽象事件
这里想到我的老师龙哥说过的一句话,编程的本质就是状态的变换和消息的通知。那么单纯地用fd来表述事件的状态已经不够了。
我们需要如下的一个结构体。
typedef void (*Callback_t)(int , int , void*);
struct myevent_s{
int fd;
int events; // 记录所关怀的事件类型,读事件还是写事件
Callback_t call_back; // 回调函数
void *arg; // 指向自己的指针
int status; // 记录自己是否在epoll树上
char buf[BUFFLEN]; // 一个缓冲区
int len; // 缓冲区有效数据的长度
//long last_active; 暂时忽略
};
以上前数据结构中前四项是必须具备的。后几项是帮助完成更多的功能。
listenevent
我们要将listenfd做成listenevent。列举下我们要做的事,你就明白我们为什么要如此设置event的结构。
- listenfd的fd值是必然要保存的。
- listenfd要加入到epoll中,是关注读事件还是写事件?——就需要设置event。
- 当listenfd就绪的时候,是回调那个函数。要记录那个函数的地址。——就需要设置call_back。
- 当listenfd就绪的时候,我们希望立即返回那个就绪事件的event指针。这样我们就可以通过指针去回调它的成员。
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
struct epoll_event{
_uint32_t events;
epoll_data_t data;
};
data是一个联合体,我们经常使用的是其fd成员,这次我们使用ptr成员。将其注册到epoll中。这样当fd就绪的时候。通过这个指针我们就获得event事件。
如果不明白arg怎么指向自己,看如下的伪代码。
// 伪代码
void accept_conn(...)
{...}
event_set()
{
listen_event.fd = listenfd;
listen_event.event = EPOLLIN;
listen_event.call_back = accept_conn;
listen_event.arg = &listen_event;
}
accept_conn
现在每当有连接到来时,就会回调到accept_conn中来。我们通过accept就可以获得connfd。
接下来就是将connfd再次包装成一个事件注册到epoll中。有了上面使用事件的经历,我们很容易可以写出处理事件读写逻辑的代码来。
github
总结
以上就是简单的利用c来实现Reactor,下一篇我将使用c++来实现,并抽象出Reactor中的核心三大类来。Reactor也是muduo和libevent中的基本思想。如果感兴趣的话可以读一下libevent库的源码。