第一个例子位于libevent源码libevent-2.1.12-stable/sample/time-test.c下面,是一个超时事件回调。
int
main(int argc, char **argv)
{
struct event timeout;
struct timeval tv;
struct event_base *base;
int flags;
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
(void)WSAStartup(wVersionRequested, &wsaData);
#endif
if (argc == 2 && !strcmp(argv[1], "-p")) {
event_is_persistent = 1;
flags = EV_PERSIST;
} else {
event_is_persistent = 0;
flags = 0;
}
/* Initialize the event library */
base = event_base_new();
/* Initialize one event */
event_assign(&timeout, base, -1, flags, timeout_cb, (void*) &timeout);
evutil_timerclear(&tv);
tv.tv_sec = 2;
event_add(&timeout, &tv);
evutil_gettimeofday(&lasttime, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
event_base_dispatch(base);
return (0);
}
从main函数看起
event_base结构体指针变量(使用指针是因为event_base结构体是一个库内部实现的结构体,对外只提供操作接口,不暴露实现细节)。event_base_new():通过接口分配一个event_base结构体,同时也代表着事件库的初始化。event_assign:初始化timeout事件,指定管理事件的event_base、事件flag、事件触发的回调以及回调参数。event_add:添加事件并设置事件的超时事件。event_base_dispatch:开始事件循环,等待回调。static void
timeout_cb(evutil_socket_t fd, short event, void *arg)
{
struct timeval newtime, difference;
struct event *timeout = arg;
double elapsed;
evutil_gettimeofday(&newtime, NULL);
evutil_timersub(&newtime, &lasttime, &difference);
elapsed = difference.tv_sec +
(difference.tv_usec / 1.0e6);
printf("timeout_cb called at %d: %.3f seconds elapsed.\n",
(int)newtime.tv_sec, elapsed);
lasttime = newtime;
if (! event_is_persistent) {
struct timeval tv;
evutil_timerclear(&tv);
tv.tv_sec = 2;
event_add(timeout, &tv);
}
}
接着看回调,回调函数的类型都是固定的typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
实验:
./time-test
timeout_cb called at 1702305186: 2.002 seconds elapsed.
timeout_cb called at 1702305188: 2.002 seconds elapsed.
timeout_cb called at 1702305190: 2.002 seconds elapsed.
timeout_cb called at 1702305192: 2.002 seconds elapsed.
这个例子位于libevent-2.1.12-stable/sample/signal-test.c,是一个信号事件回调
int
main(int argc, char **argv)
{
struct event *signal_int = NULL;
struct event_base* base;
int ret = 0;
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
(void) WSAStartup(wVersionRequested, &wsaData);
#endif
/* Initialize the event library */
base = event_base_new();
if (!base) {
ret = 1;
goto out;
}
/* Initialize one event */
signal_int = evsignal_new(base, SIGINT, signal_cb, event_self_cbarg());
if (!signal_int) {
ret = 2;
goto out;
}
event_add(signal_int, NULL);
event_base_dispatch(base);
out:
if (signal_int)
event_free(signal_int);
if (base)
event_base_free(base);
return ret;
}
先看main函数
event_base_new:初始化事件库evsignal_new:初始化超时事件,虽然叫evsignal_new但本质是event_new创建事件event_add:添加信号事件event_base_dispatch:开启事件循环,等待信号事件回调static void
signal_cb(evutil_socket_t fd, short event, void *arg)
{
struct event *signal = arg;
printf("signal_cb: got signal %d\n", event_get_signal(signal));
if (called >= 2)
event_del(signal);
called++;
}
接着看信号事件回调,很简单,就是打印信号并在第三次回调时删除事件
实验:
./signal-test
^Csignal_cb: got signal 2
^Csignal_cb: got signal 2
^Csignal_cb: got signal 2
这个例子位于libevent-2.1.12-stable/sample/hello-world.c,是一个tcp服务器,每次收到新连接后进行回调
int
main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event;
struct sockaddr_in sin = {0};
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin,
sizeof(sin));
if (!listener) {
fprintf(stderr, "Could not create a listener!\n");
return 1;
}
signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
if (!signal_event || event_add(signal_event, NULL)<0) {
fprintf(stderr, "Could not create/add a signal event!\n");
return 1;
}
event_base_dispatch(base);
evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base);
printf("done\n");
return 0;
}
main函数:
event_base_new:初始化事件库evconnlistener_new_bind将socket、event_base和监听回调绑定到一起evsignal_new:初始化超时事件,虽然叫evsignal_new但本质是event_new创建事件event_add:添加信号事件event_base_dispatch:开启事件循环,等待信号事件回调先看简单的信号事件回调,和前一个例子基本一样
static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
struct event_base *base = user_data;
struct timeval delay = { 2, 0 };
printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
event_base_loopexit(base, &delay);
}
再看新的监听事件回调,一旦有新连接到达就会调用该函数:
static void
listener_cb(struct evconnlistener *listener, evutil_scket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = user_data;
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
bufferevent_enable(bev, EV_WRITE);
bufferevent_disable(bev, EV_READ);
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
bufferevent_socket_new:创建基于套接字的buffereventstruct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);
event_base,options 是表示 bufferevent 选项(BEV_OPT_CLOSE_ON_FREE等) 的位掩码, fd是一个可选的表示套接字的文件描述符。如果想以后设置文件描述符,可以设置fd为-1。成功时函数返回一个bufferevent,失败则返回 NULL。BEV_OPT_CLOSE_ON_FREE: 释放 bufferevent时关闭底层传输端口。这将关闭底层套接字,释放底层bufferevent等。bufferevent_setcb:数修改 bufferevent 的一个或者多个回调。void bufferevent_setcb(struct bufferevent *bufev,
bufferevent_data_cb readcb, bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg);
bufferevent_enable:启用bufferevent 上的EV_WRITE事件。bufferevent_disable:禁用bufferevent 上的EV_READ事件。bufferevent_write:向bufferevent的输出缓冲区添加数据接着看bufferevent上的写回调,因为默认写的低水位为0,因此只有当output buffer数据为空时才会触发写回调函数。
static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == 0) {
printf("flushed answer\n");
bufferevent_free(bev);
}
}
BEV_OPT_CLOSE_ON_FREE 标志,并且 bufferevent 有一个套接字或者底层 bufferevent 作为其传输端口,则释放 bufferevent 将关闭这个传输端口。最后看bufferevent上的出错事件回调
static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
} else if (events & BEV_EVENT_ERROR) {
printf("Got an error on the connection: %s\n",
strerror(errno));/*XXX win32*/
}
/* None of the other events can happen here, since we haven't enabled
* timeouts */
bufferevent_free(bev);
}
实验:
# 服务端
./hello-world
new connection come
flushed answer
# 客户端
nc 127.0.0.1 9995
Hello, World!
总结
该例子比较简单,流程就是启动一个服务器,每次收到一个连接,回复Hello, World!后断开连接。但是这个例子并没有向我们展示出conn_writecb和conn_eventcb这两个回调的用法,因为回复一句立马断开这个过程太快了。
可以扩展一下:
这种情况下,服务端会不停地向客户端输出Hello, World!字符串。如果这个时候关闭客户端,那么conn_eventcb这个错误回调也会被调用打印出具体信息。
# 服务端
./hello-world
new connection come
flushed answer
...
flushed answer
flushed answer
Got an error on the connection: Connection reset by peer
# 客户端
nc 127.0.0.1 9995
Hello, World!