C语言实现简单的Reactor

Reactor

介绍

Reactor模式网上有很多讲解,我这里不想过多介绍。其核心共有三个。

  1. 抽象的事件
  2. 事件多路分发器
  3. 一个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的结构。

  1. listenfd的fd值是必然要保存的。
  2. listenfd要加入到epoll中,是关注读事件还是写事件?——就需要设置event。
  3. 当listenfd就绪的时候,是回调那个函数。要记录那个函数的地址。——就需要设置call_back。
  4. 当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

我的github

总结

以上就是简单的利用c来实现Reactor,下一篇我将使用c++来实现,并抽象出Reactor中的核心三大类来。Reactor也是muduo和libevent中的基本思想。如果感兴趣的话可以读一下libevent库的源码。

上一篇:网络基础(二)c#编写简单的网络程序,服务端部分


下一篇:URAL 1932 The Secret of Identifier 题解