分析libevent
- 如何使用 (官方手册,心得体会)
- 源码分析 (event, event_base 为核心)
引言
- 标准c库便有许多参用了回调函数,让用户制定处理过程,比如常用的 qsort(3), bsearch(3)
- 基本的socket编程时阻塞/同步的,线程在默认情况下占2~8M栈空间。posix的select(2)采用了轮循的方法来判断某个fd是否激活?故时间需要O(n),效率并不高
- 进而各个系统提出了异步的callback系统调用。 比如: linux的epoll(2),BSD 的kqueue(2),Windows的IOCP。 进而能够用 O(1)效率查找到激活的fd
libevent就是对这些高效IO的封装,提供统一的API,便于开发。
libevent默认为单线程,可以配置为多线程。
一个线程一个event_base,对应一个 struct event_base 和时间管理器,用 schedule 托管给它一系列的event。
当时间发生后,event_base 会在合适的时间(不一定立即)调用绑定于该事件的函数,直到函数运行完,再返回schedule其余事件。
struct event_base *base = event_base_new();
assert(base != NULL);
event_base 内部阻塞于 epoll / kqueue
每个事件对应一个 struct event ,可以是监听一个 fd / posix 信号量
struct event 使用 event_new 创建绑定, event_add 来启用
补充下epoll的两个触发模式
- Edge Trigger 边缘触发: 状态变化时产生 IO 事件
- Level Trigger 水平(条件)触发: 满足条件就产生 IO 事件
对于server而言,大致过程如下:
- listener = socket() bind() listen() 设置fd为非阻塞non-blocking(fcntl(2)) 或者用libevent封装的evutil_make_socket_nonblocking
- 创建一个 event_base
- event_init() event_set() : 创建一个 event , 将该 socket 托管给event_base , 指定监听类型,绑定相应的回调函数和参数。 对于 listener socket 而言,只需要监听 EV_READ | EV_PERSIST , PERSIST是保持存在的选项
- event_add() : 添加该事件,设置超时时间
- event_dispatch() : 进入事件循环
- (异步) 当有client发起请求 / 事件发生,便会调用该回调函数进行处理
WHY ? 为何不在listen()之后立即调用 accept() ?
如果accept 获得和client通信的sockfd之后,马上进行 recv() / send() , 线程就会阻塞于此。
因此应该创建一个event来托管这个sockfd。
缓冲区管理 bufferevent
从libevent2开始,提供了bufferevent
struct bufferevent 内建立了两个event(read / write)和对应的缓冲区:
- struct evbuffer *input;
- struct evbuffer *output;
当有数据被读入 input 时,read_cb 立刻调用
当output被输出完毕的时候,write_cb 立刻调用
基本库
* I/O 事件 *
void get_time(int fd, short event, struct event *arg)
{
localtime_r(&now, &t);
asctime_r(&t, buf);
write(fd, buf, strlen(buf));
//get system time, then write it
}
void connect_accept(int fd, short event, void *arg)
{
//offer a callback function to the event, accept a connection
struct sockaddr_in socket_in;
socklen_t len = sizeof(socket_in);
int accept_fd = accept(fd, (struct sockaddr *) &socket_in, &len);
struct event *ev = (struct event *)malloc(sizeof(struct event));
event_set(ev, accept_fd, EV_WRITE, (void *)get_time, ev);
event_add(ev, NULL);
}
int main(void)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in socket_in;
bind(sockfd, (struct sockaddr *)&socket_in, sizeof(socket_in));
listen(sockfd, 5);
event_init(); //libevent initialization
struct event ev;
// 设置事件为可读,持续,监听的是sockfd,回调函数connect_accept
event_set(&ev, sockfd, EV_READ|EV_PERSIST, connect_accept, &ev);
//添加事件,未设置超时时间
event_add(&ev, NULL);
//进入libevent主循环,等待事件发生
event_dispatch();
return 0;
}
信号处理事件
static void signal_cb(int fd, short event, void *arg)
{
struct event *signal = arg;
printf("%s: got signal %d\n", __func__, EVENT_SIGNAL(signal)); // __func__ 是调用的函数名
if (called >= 2)
event_del(signal);
called++;
}
int main (int argc, char **argv)
{
struct event signal_int;
event_init(); //libevent initialization
event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, &signal_int);
//设置事件属性为信号触发、持续,回调函数为con_accept()
event_add(&signal_int, NULL); //添加事件
event_dispatch();//进入libevent主循环
return 0;
}
常规超时处理
static void timeout_catch(int fd, short event, void *arg)
{
struct timeval tv;
struct event *timeout = arg;
int newtime = time(NULL);
printf("%s : called at %d\n", __func__, newtime - lasttime);
lasttime = newtime;
evutil_timerclear(&tv);
tv.tv_sec = 1;
event_add(timeout, &tv);
}
int main(void)
{
struct event timeout;
struct timeval tv;
event_init();
evtimer_set(&timeout, timeout_catch, &timeout);
//等价于
event_set(timeout, -1, 0, timeout_catch, &timeout);
evutil_timerclear(&tv);
tv.tv_sec = 1;
event_add(&timeout, &tv);
lasttime = time(NULL);
event_dispath(); //enter the loop
return 0;
}