引言:lighttpd与nginx这些新兴的webserver框架都以支撑大规模的并发而闻名,以下介绍一个我自己实现的并使用的并发事件框架,这个框架不止包括网络事件,也包括其他耗时事件比如IO等都可以在事件框架中运行。
1,监听事件的初始化。
以下是一个事件结构的大致成员,我们以Epoll模型为例子,包括描述符的数组,和事件的回调函数。
初始化要分配以最大值为限制的fdnode的数组。
typedef struct fdevents {
struct server *srv;
fdevent_handler_t type;
fdnode **fdarray;
size_t maxfds;
#ifdef EPLOLL
int epoll_fd;
struct epoll_event *epoll_events;
#endif
int (*reset)(struct fdevents *ev);
void (*free)(struct fdevents *ev);
int (*event_set)(struct fdevents *ev, int fde_ndx, int fd, int events);
int (*event_del)(struct fdevents *ev, int fde_ndx, int fd);
int (*event_get_revent)(struct fdevents *ev, size_t ndx);
int (*event_get_fd)(struct fdevents *ev, size_t ndx);
int (*event_next_fdndx)(struct fdevents *ev, int ndx);
int (*poll)(struct fdevents *ev, int timeout_ms);
int (*fcntl_set)(struct fdevents *ev, int fd);
} fdevents;
2,事件的注册
int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx)
将事件新增进数组fdarray中,这样在事件满足时,就会再相应的条件触发回调函数handler,
这个注册会初始化一个fdnode
3,事件的设置
int fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events)
将对应的fdnode 指定为那种IO事件的响应,IO事件有如下种类
#define FDEVENT_IN BV(0)
#define FDEVENT_PRI BV(1)
#define FDEVENT_OUT BV(2)
#define FDEVENT_ERR BV(3)
#define FDEVENT_HUP BV(4)
#define FDEVENT_NVAL BV(5)
4,事件Poll动作,将所有触发的事件返回到fdndx的链表上,处理逻辑可以对相应的事件做逻辑处理
int fdevent_poll(fdevents *ev, int timeout_ms)
5,fd的回调函数
typedef handler_t (*fdevent_handler)(struct server *srv, void *ctx, int revents);
当有事件发生是,所有逻辑都包括在回调函数中,以下代码是poll后的典型处理
do {
fdevent_handler handler;
void *context;
handler_t r;
fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx);
if (-1 == fd_ndx) break;
//这里的3种回调函数是事件模型对应的真实实现,比如epoll的实现
//则对应的是epoll实现代码中的
//fdevent_event_get_revent获取对应事件
//fdevent_event_get_fd 获取文件描述符
// fdevent_get_handler 获取事件回调函数
// fdevent_get_context 获取事件逻辑数据
revents = fdevent_event_get_revent (srv->ev, fd_ndx);
fd = fdevent_event_get_fd (srv->ev, fd_ndx);
handler = fdevent_get_handler(srv->ev, fd);
context = fdevent_get_context(srv->ev, fd);
switch (r = (*handler)(srv, context, revents)) {
case HANDLER_FINISHED: //针对处理的返回值做不同的处理
case HANDLER_GO_ON:
case HANDLER_WAIT_FOR_EVENT:
case HANDLER_WAIT_FOR_FD:
break;
case HANDLER_ERROR:
SEGFAULT();
break;
default:
log_error_write(srv, __FILE__, __LINE__, "d", r);
break;
}
} while (--n > 0);
5,事件框架,多路复用模型(epoll,select,kquque,FIFO等),具体的数据逻辑相互分离
6,网络事件注册进事件框架(建立连接)
网络事件也作为普通事件之一注册进事件框架
fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket);
fdevent_event_set(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
network_server_handle_fdevent 实际上做了accpet的实现,当网络事件被Poll函数触发是,由network_server_handle_fdevent 来执行建立连接的动作。
7,网络事件注册进事件框架(读写事件)
在network_server_handle_fdevent 中注册读写事件,针对读写来实现对于的操作在connection_handle_fdevent实现
fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);
8,读写事件的逻辑抽象化实现
业务逻辑跟进自己逻辑需要独立开来,对于网络事件来说,只需要关心是否可读写就可以,为此每个连接都要维护一个缓冲区链表,将业务与网络进行隔离。
typedef struct {
chunk *first;
chunk *last;
chunk *unused;
size_t unused_chunks;
array *tempdirs;
off_t bytes_in, bytes_out;
} chunkqueue;