基本使用方法
libevent
是一个使用事件驱动模型的网络网络库,网络开发,可以通过使用这个库,非常简单、清晰的代码做出一个支持I/O复用的程序。工作中需要使用到此库,所以记录一下学习进度。
基本使用可以参考源码的sample/
目录下的使用示例,根据示例名称,我首先看一下 hello-workd.c
这个程序:代码不少,但是单个函数拆分来看,还是分清晰的。
首先是main
函数中:
//......
base = event_base_new();
//......
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin, sizeof(sin));
//......
/// 注册了一个信号事件,该事件处理函数 signal_cb 处理的Ctrl+C信号
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);
首先调用 event_base_new
创建了一个 event_base
结构体,也不知道是干嘛的,但是并没有给他提供任何网络相关的参数,暂时先不管它。
之后调用了 evconnlistener_new_bind
函数,给他传入了网络的sockaddr
结构信息,结合文件名看来就是在这里开始创建套接字和监听了。进入头文件listener.h
中也能看到关于该函数的介绍:
/**
Allocate a new evconnlistener object to listen for incoming TCP connections
on a given address.
@param base The event base to associate the listener with. event 会和 event_base 关联
@param cb A callback to be invoked when a new connection arrives. If the
callback is NULL, the listener will be treated as disabled until the
callback is set. 链接来的socket的处理函数
@param ptr A user-supplied pointer to give to the callback. 参数指针
@param flags Any number of LEV_OPT_* flags
@param backlog Passed to the listen() call to determine the length of the
acceptable connection backlog. Set to -1 for a reasonable default.
@param sa The address to listen for connections on.
@param socklen The length of the address.
*/
可以看出 listener_cb
是处理链接客户端的socket
,LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE
可以根据listen.h
中找到解释,设置了socket地址的可重用和关闭时释放, sa 是地址信息。
然后在 listener_cb
中,应该是处理链接的socket
的数据的函数了,
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
///设置bufferevent的读写事件处理函数
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
/// 使能写事件
bufferevent_enable(bev, EV_WRITE);
/// 失能读事件
bufferevent_disable(bev, EV_READ);
///向bufferevent写入数据
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
如上面代码,根据头文件中的描述这个bufferevent
结构的bev
变量是和套接字fd
关联的,然后使用bufferevent_setcb
有绑定了两个回调函数,写函数和异常处理函数。并enable
了写,diable
读,并向bev中写入了"Hello, World!\n"
。
进入两个回调函数中看一下,第一个
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);
}
}
这里有些看不明白的是,在之前的 listener_cb
当中已经调用了一次bufferevent_write
写了数据,这里的写回调是有什么用处呢,
函数里面通过获取event_buffer
的output
的buffer
并判断其长度,如果为0,就释放资源,根据前面建立连接时的flag参数可以知道这里释放了资源就相当于关闭链接了。那就是给客户端写了一个hello world就退出链接了。。。(后来想到,这里的conn_writecb
是为了保证outputt
的数据已经写完了,再关闭链接的用途)。
static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
if (events & BEV_EVENT_EOF) { /// 写到结束即socket收到FIN后返回0
printf("Connection closed.\n");
} else if (events & BEV_EVENT_ERROR) { /// socket出错
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);
}
conn_eventcb应该就是异常处理和结束处理的函数,没什么看的了。
看了代码之后,根据之气那的分析看一下实际执行是不是这样子了:
我们先执行一下编译的可执行程序,执行后在新窗口执行telnet 127.0.01 9995
后可以看到输出如下:
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello, World!
Connection closed by foreign host.
执行 hello-word
的窗口显示如下:
root@DESKTOP-RMCD5EP:/mnt/d/code/libevent/build/bin# ./hello-world
flushed answer
可以看到hello-world
执行的流程是:hello-world
程序监听9995
端口,telnet
链接上之后,hello-world
程序直接显示了 “flushed answer”
并给客户端的链接套接字写入了 “Hello, World!"
(不知道先后顺序),之后把客户端链接套接字关闭了。导致telnet
程序退出了。
除了 socket
处理,后面还有一个信号事件处理,基本上也是一样的流程,但是信号处理中先是使用 evsignal_new
函数新建一个事件,并同时将事件处理的信号类型以及函数指针传入,得到一个 event
结构,之后需要调用 evnet_add
,这个函数将没有找到详细的说明,不过看起来像是把新建的 event
加入到唯一的 event_base
中。需要深入分析。之后等到信号传入时, hello-world
程序就会自动调用自定义的信号处理函数了。
总结一下
使用libevent
,首先初始化一个event_base
结构体,然后创建event
结构,创建tcp
服务器使用evconnlistener_new_bind
函数,他返回一个evnet
结构,之后将处理客户端链接的处理函数指针传入evconnlistener_new_bind
,这个函数帮我们完成了从创建socket
到connect
的处理,我们只需要将处理客户但链接的函数指针传入就可以了。之后调用event_base_dispatch
函数 libevnet
就自动开始监听了。
再处理客户端链接的时候,libevent
提供了bufferevent
,我们可以将一个客户端链接socket
绑定bufferevent
,这个bufferevent
替我们做了读写socket
获取传输内容的操作。当接收时调用bufferevent_read
函数,当发送时直接调用 bufferevent_write
函数往befferevnet
中写就可以了。这样一个简单的服务器就完成了。