evconnlistener机制提供了监听并接受TCP链接的方法。除非特别注明,本章的所有函数和类型都在event2/listener.h中声明。
一:创建或释放evconnlistener
struct evconnlistener *evconnlistener_new(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
evutil_socket_t fd);
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
const struct sockaddr *sa, int socklen);
void evconnlistener_free(struct evconnlistener *lev);
两个evconnlistener_new*函数都是分配并返回一个新的链接监听器对象。链接监听器使用event_base,在给定监听socket上监听新的TCP链接的到来。当一个新的链接到来时,它调用给定的回调函数。
两个函数中,base参数都是监听器用来监听链接的event_base。cb函数是新链接到来时需要调用的回调函数;如果cb为NULL,则直到设置了回调函数为止,监听器相当于被禁用。ptr指针会传递给回调函数。flag参数控制监听器的行为----详见下方。backlog参数表示在任何时刻,网络栈所允许的等待在“未接受”(not-yet-accepted)状态的挂起链接的最大个数。更多细节参考系统listen函数的手册。如果backlog为负数,则Libevent会自行选择一个比较好的backlog值;如果该值为0,则Libevent认为你已经在给定的socket上调用过listen函数了。
这两个函数的区别在于如何设置监听socket。 evconnlistener_new函数假定已经在希望监听的端口上绑定了socket,也就是fd参数。如果希望Libevent分配并绑定自己的socket,则可以调用evconnlistener_new_bind函数,并且传递一个希望绑定的sockaddr地址及其长度。
注意:使用evconnlistener_new函数时,确定已经通过evutil_make_socket_nonblocking或者手动设置socket选项,将监听socket设置为非阻塞模式。如果监听socket处于阻塞模式,则会有未定义的行为发生。
释放一个链接监听器,调用evconnlistener_free函数。
flags标志位
下面是可以传递给evconnlistener_new函数flags标志,可以使用or运算将任意数量的标志绑定在一起。
LEV_OPT_LEAVE_SOCKETS_BLOCKING:默认情况下,当链接监听器接收一个新的到来的socket时,会将其置为非阻塞状态,从而方便Libevent后续的操作。如果设置了该标志,则会禁止这种行为。
LEV_OPT_CLOSE_ON_FREE:如果设置了该标志,则链接监听器会在释放时关闭底层的socket。
LEV_OPT_CLOSE_ON_EXEC:设置该标志,链接监听器会在底层监听socket上设置“执行时关闭”(close-on-exec)标志。详细信息可以参考操作系统手册中的fcntl和FD_CLOEXEC部分。
LEV_OPT_REUSEABLE:默认情况下在某些平台上,当一个监听socket关闭时,只有经过一定时间之后,其他的socket才能绑定到相同的端口上。设置该标志可以使Libevent标志该socket为可重复使用的,因此一旦它关闭了,则其他socket可以在同一个端口上进行监听。
LEV_OPT_THREADSAFE:为监听器分配锁,因此可以在多线程中安全的使用。
LEV_OPT_DISABLED:将监听器初始化为禁止状态。可以通过函数evconnlistener_enable手动将其使能。
LEV_OPT_DEFERRED_ACCEPT:设置该标志,则告知内核,直到接收到对端数据,并且本地socket准备好读取之前,不通知socket接收新链接。如果网络协议并非以客户端传递数据为开始,则不要使用该标志,因为这样有时会使得内核永远不通知新链接的到来。并非所有系统都支持该标志:在那些不支持的系统上,该标志没有任何作用。
链接监听器的回调函数
typedef void (*evconnlistener_cb)(struct evconnlistener *listener,
evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);
当新的链接到来时,就会调用回调函数。其中的Listener参数就是接收链接的链接监听器,sock参数就是新的socket本身。addr和len就是链接对端的地址及其长度。ptr就是用户提供的传递给evconnlistener_new函数的参数。
二:将evconnlistener使能和禁止
int evconnlistener_disable(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);
这些函数可以将evconnlistener暂时的使能或禁止。
三:调整evconnlistener的回调函数
void evconnlistener_set_cb(struct evconnlistener *lev,
evconnlistener_cb cb, void *arg);
该函数改变evconnlistener的回调函数及其参数。
四:监测evconnlistener
evutil_socket_t evconnlistener_get_fd(struct evconnlistener *lev);
struct event_base *evconnlistener_get_base(struct evconnlistener *lev);
这些函数返回监听器的socket和event_base。
五:检测错误
可以在监听器上设置错误回调函数,当accept调用失败时,就会调用该函数。当你遇到一个错误,而且解决该错误之前进程会一直锁住的话,这种机制是很有用的。
typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void*ptr);
void evconnlistener_set_error_cb(struct evconnlistener *lev,
evconnlistener_errorcb errorcb);
如果使用evconnlistener_set_error_cb函数设置了错误回调函数,则在监听器上,每次发生错误时都会调用该函数。监听器会作为第一个参数,传递给evconnlistener_new的ptr作为第二个参数。
六:例子:回显服务器:
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
static void
echo_read_cb(struct bufferevent *bev, void *ctx)
{
/* This callback is invoked when thereis data to read on bev. */
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev);
/* Copy all the data from the inputbuffer to the output buffer. */
evbuffer_add_buffer(output, input);
}
static void
echo_event_cb(struct bufferevent *bev, short events, void *ctx)
{
if (events & BEV_EVENT_ERROR)
perror("Error frombufferevent");
if (events & (BEV_EVENT_EOF |BEV_EVENT_ERROR)) {
bufferevent_free(bev);
}
}
static void
accept_conn_cb(struct evconnlistener *listener,
evutil_socket_t fd, struct sockaddr *address, int socklen,
void *ctx)
{
/* We got a new connection! Set up abufferevent for it. */
struct event_base*base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(
base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);
bufferevent_enable(bev, EV_READ|EV_WRITE);
}
static void
accept_error_cb(struct evconnlistener *listener, void *ctx)
{
struct event_base *base = evconnlistener_get_base(listener);
int err = EVUTIL_SOCKET_ERROR();
fprintf(stderr, "Got an error %d(%s) on the listener. "
"Shutting down. ",err, evutil_socket_error_to_string(err));
event_base_loopexit(base, NULL);
}
int
main(intargc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct sockaddr_in sin;
int port = 9876;
if (argc > 1) {
port = atoi(argv[1]);
}
if (port<=0 || port>65535) {
puts("Invalid port");
return 1;
}
base = event_base_new();
if (!base) {
puts("Couldn't open event base");
return 1;
}
/* Clear the sockaddr before using it,in case there are extra
* platform-specific fields that canmess us up. */
memset(&sin , 0, sizeof(sin));
/* This is an INET address */
sin.sin_family = AF_INET;
/* Listen on 0.0.0.0 */
sin.sin_addr.s_addr = htonl(0);
/* Listen on the given port. */
sin.sin_port = htons(port);
listener =evconnlistener_new_bind(base, accept_conn_cb, NULL,
LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
(struct sockaddr*)&sin,sizeof(sin));
if (!listener) {
perror("Couldn't createlistener");
return 1;
}
evconnlistener_set_error_cb(listener, accept_error_cb);
event_base_dispatch(base);
return 0;
}