libevent学习使用1
source link: https://segmentfault.com/a/1190000041140431
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
基本使用方法
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
中写就可以了。这样一个简单的服务器就完成了。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK