libevent, an event notification library
Table of Contents
1. libevent 简介
The libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, libevent also support callbacks due to signals or regular timeouts.
libevent 是各种操作系统下不同 I/O 模型(如 select,poll,epoll,kqueue 等、Windows 的 IOCP 等)的封装,它提供了统一的 API,能简化程序开发过程。一般地,可以认为 libevent 是 Reactor 模式(如果使用 bufferevent 则也可以看作是 Proactor 模式)的实现。
libevent 有很多优点:
- 轻量级,不像 ACE 那么庞大;
- 支持多种 I/O 复用技术,如 select、epoll、poll、dev/poll、select 和 kqueue 等;
- 开源跨平台,支持 Linux、*BSD、Mac OS 和 Windows 等系统;
- 事件驱动方式(event-driven),性能高;
下面这些软件中使用了 libevent:
- Google Chrome: Google's web browser (Mac and Linux versions)
- Memcached: a high-performance, distributed memory object caching system
- Transmission: an open-source BitTorrent client
- ntpd: the Network Time Protocol daemon
- Tor: an anonymous Internet communication system
- tmux: a terminal multiplexer
参考:
http://libevent.org/
libevent Reference: http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/
2. 实例
在 Debian 系列操作系统中可以通过安装 libevent-dev 来准备开发环境:
$ sudo apt-get install libevent-dev
使用 libevent 编程,有三个主要步骤:
(1) Creating an event base (event_base_new()
)
(2) Event notification (event_new()
, event_add()
)
(3) Dispaching events (event_base_dispatch()
)
参考:http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/
什么是事件集合(event base)?
Before you can use any interesting Libevent function, you need to allocate one or more event_base structures. Each event_base structure holds a set of events and can poll to determine which events are active.
Each event_base has a "method", or a backend that it uses to determine which events are ready. The recognized methods are:
- select
- poll
- epoll
- kqueue
- devpoll
- evport
- win32
如何选择上面这些方法呢?使用 event_base_new()
创建 event base 时,会默认选择当前操作系统下最快的方法。
参考:http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html
2.1. 实例:小写字母转大写
下面是一个“小写字母转大写”的服务器实例。程序主要改编自:https://www.felix021.com/blog/read.php?2068
/* * 下面是一个“小字字母转大写”的程序实例 * 程序改编自:https://www.felix021.com/blog/read.php?2068 */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <assert.h> #include <event2/event.h> #include <event2/bufferevent.h> #define LISTEN_PORT 9999 #define LISTEN_BACKLOG 32 void accept_callback(evutil_socket_t listener, short event, void *arg); void read_callback(struct bufferevent *bev, void *arg); void write_callback(struct bufferevent *bev, void *arg); void error_callback(struct bufferevent *bev, short event, void *arg); int main(int argc, char *argv[]) { int ret; evutil_socket_t listener; listener = socket(AF_INET, SOCK_STREAM, 0); assert(listener > 0); evutil_make_listen_socket_reuseable(listener); struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(LISTEN_PORT); if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; } if (listen(listener, LISTEN_BACKLOG) < 0) { perror("listen"); return 1; } printf ("Listening, port is %d\n", LISTEN_PORT); evutil_make_socket_nonblocking(listener); /* 创建事件的集合对象 */ struct event_base *base = event_base_new(); assert(base != NULL); /* 新建事件 * EV_PERSIST设置监听socket的读取事件(即新的客户连接)持续发生 * 注册回调函数 accept_callback */ struct event *listen_event; listen_event = event_new(base, listener, EV_READ|EV_PERSIST, accept_callback, (void*)base); /* 添加事件 */ event_add(listen_event, NULL); /* 启动事件分派主循环 */ event_base_dispatch(base); printf("The End."); return 0; } void accept_callback(evutil_socket_t listener, short event, void *arg) { struct event_base *base = (struct event_base *)arg; evutil_socket_t fd; struct sockaddr_in sin; socklen_t slen = sizeof(sin); fd = accept(listener, (struct sockaddr *)&sin, &slen); if (fd < 0) { perror("accept"); return; } printf("ACCEPT: fd = %u\n", fd); struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); /* 为fd上“读、写和异常”注册不同回调函数 */ bufferevent_setcb(bev, read_callback, write_callback, error_callback, arg); bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST); } void to_upper(char *p, int size) { int i; for (i=0; i<size; i++) { if (p[i] >= 'a' && p[i] <= 'z') { p[i] = p[i] - ('a' - 'A'); } } } void read_callback(struct bufferevent *bev, void *arg) { #define MAX_LINE 256 char line[MAX_LINE+1]; int n; evutil_socket_t fd = bufferevent_getfd(bev); while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) { line[n] = '\0'; printf("fd=%u, read line: %s", fd, line); /* */ to_upper(line, n); bufferevent_write(bev, line, n); } } void write_callback(struct bufferevent *bev, void *arg) {} void error_callback(struct bufferevent *bev, short event, void *arg) { evutil_socket_t fd = bufferevent_getfd(bev); printf("fd = %u, ", fd); if (event & BEV_EVENT_TIMEOUT) { printf("Timed out\n"); //if bufferevent_set_timeouts() called } else if (event & BEV_EVENT_EOF) { printf("connection closed\n"); } else if (event & BEV_EVENT_ERROR) { printf("some other error\n"); } bufferevent_free(bev); }
编译并启动上面服务器程序后(注意编译时传递参数-levent 给链接器),服务端的日志的一个例子如下:
$ ./a.out Listening, port is 9999 ACCEPT: fd = 7 fd=7, read line: dd fd=7, read line: adfdfdf fd=7, read line: 8883 fd=7, read line: fdfdf ACCEPT: fd = 8 fd=8, read line: ldkfdf fd = 8, connection closed fd = 7, connection closed
进行上面测试时,同时使用了两个客户端,其中一个客户端的交互过程为:
$ nc localhost 9999 dd DD adfdfdf ADFDFDF 8883 8883 fdfdf FDFDF ^C
另一个客户端的交互过程为:
$ nc localhost 9999 ldkfdf LDKFDF ^C