1. 绑定和监听
在上一篇文章中,以epoll为例说到了事件机制,会按顺序调用init和dispatch这两个回调函数,但是,我们回忆一下网络编程的过程,首先是需要创建socket、绑定socket、监听socket的,但目前为止还并没有涉及到,再去看源代码,会发现里面有listener.c,这个文件里面就会去做创建socket的过程。
看evconnlistener_new_bind函数,如下:
struct evconnlistener *
evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb,
void *ptr, unsigned flags, int backlog, const struct sockaddr *sa,
int socklen)
{
struct evconnlistener *listener;
evutil_socket_t fd;
int on = 1;
int family = sa ? sa->sa_family : AF_UNSPEC;
int socktype = SOCK_STREAM | EVUTIL_SOCK_NONBLOCK;
if (backlog == 0)
return NULL;
if (flags & LEV_OPT_CLOSE_ON_EXEC)
socktype |= EVUTIL_SOCK_CLOEXEC;
//调用socket函数
fd = evutil_socket_(family, socktype, 0);
if (fd == -1)
return NULL;
//设置存货检测
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0)
goto err;
//设置地址重用
if (flags & LEV_OPT_REUSEABLE) {
if (evutil_make_listen_socket_reuseable(fd) < 0)
goto err;
}
//设置端口重用
if (flags & LEV_OPT_REUSEABLE_PORT) {
if (evutil_make_listen_socket_reuseable_port(fd) < 0)
goto err;
}
//设置延迟接收
if (flags & LEV_OPT_DEFERRED_ACCEPT) {
if (evutil_make_tcp_listen_socket_deferred(fd) < 0)
goto err;
}
//调用bind函数
if (sa) {
if (bind(fd, sa, socklen)<0)
goto err;
}
//evconnlistener_new函数里面会调用listen函数
listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);
if (!listener)
goto err;
return listener;
err:
evutil_closesocket(fd);
return NULL;
}
上面的代码我加了注释,说的很清楚,从创建、绑定、设置属性一直到监听整个都调用了,这里不再多说。
evconnlistener_new函数不只会调用listen,还会注册一个监听回调函数,如下:
struct evconnlistener *
evconnlistener_new(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
evutil_socket_t fd)
{
struct evconnlistener_event *lev;
#ifdef _WIN32
if (base && event_base_get_iocp_(base)) {
const struct win32_extension_fns *ext =
event_get_win32_extension_fns_();
if (ext->AcceptEx && ext->GetAcceptExSockaddrs)
return evconnlistener_new_async(base, cb, ptr, flags,
backlog, fd);
}
#endif
if (backlog > 0) {
if (listen(fd, backlog) < 0)
return NULL;
} else if (backlog < 0) {
if (listen(fd, 128) < 0)
return NULL;
}
lev = mm_calloc(1, sizeof(struct evconnlistener_event));
if (!lev)
return NULL;
lev->base.ops = &evconnlistener_event_ops;
//注册回调函数,当监听到有新的连接时,就会调用该函数
lev->base.cb = cb;
lev->base.user_data = ptr;
lev->base.flags = flags;
lev->base.refcnt = 1;
lev->base.accept4_flags = 0;
if (!(flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING))
lev->base.accept4_flags |= EVUTIL_SOCK_NONBLOCK;
if (flags & LEV_OPT_CLOSE_ON_EXEC)
lev->base.accept4_flags |= EVUTIL_SOCK_CLOEXEC;
if (flags & LEV_OPT_THREADSAFE) {
EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE);
}
event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,
listener_read_cb, lev);
if (!(flags & LEV_OPT_DISABLED))
evconnlistener_enable(&lev->base);
return &lev->base;
}
evconnlistener_cb回调函数声明如下:
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);
该回调函数需要我们自己实现,看sample目录中hello-world.c中实现的该函数代码如下:
static void
listener_cb(struct evconnlistener *listener, evutil_socket_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));
}
2. 读写数据
libevent是基于事件的,它的很多动作都是调用事先注册好的回调函数来解决的,读写数据也不例外。
看上面第一节中,监听回调函数里面使用了bufferevent_setcb,这个函数会注册读写事件的回调函数,如下:
void
bufferevent_setcb(struct bufferevent *bufev,
bufferevent_data_cb readcb, bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg)
{
BEV_LOCK(bufev);
bufev->readcb = readcb;
bufev->writecb = writecb;
bufev->errorcb = eventcb;
bufev->cbarg = cbarg;
BEV_UNLOCK(bufev);
}
当有可读事件时会调用readcb函数,当有可写事件时调用writecb函数,发生错误时调用eventcb函数。
bufferevent_data_cb声明如下:
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
还是看看sample目录中hello-world.c中对该函数的定义,如下:
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);
}
}
我们自己使用时可以参照sample目录中的例子,这里就不再细说了。