libevent绑定、监听和读写数据
发布日期:2021-05-08 05:59:08 浏览次数:15 分类:精选文章

本文共 5075 字,大约阅读时间需要 16 分钟。

1. 绑定和监听

在之前的文章中,我们讨论了事件机制,提到了epoll会按顺序调用init和dispatch回调函数。然而,在网络编程中,我们需要先创建socket、绑定socket并监听socket。这些步骤在libevent源代码中体现为evconnlistener.c文件中的操作。

让我们深入了解evconnlistener_new_bind函数。该函数负责创建一个新的监听socket,代码如下:

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;
}
// 绑定socket
if (sa) {
if (bind(fd, sa, socklen) < 0)
goto err;
}
// 创建并返回新的监听回调
listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);
if (!listener) goto err;
return listener;
err:
evutil_closesocket(fd);
return NULL;
}

该函数完成了socket的创建、属性设置、绑定以及监听初始化。从上述代码可以看出,socket的每一步操作都经过了细致的处理,确保在高效的同时保持稳定性。

接下来,evconnlistener_new函数不仅会启动监听,还会为socket注册一个回调函数。该函数的实现如下:

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;
#ifdef LEV_THREADSAFE
EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE);
#endif
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;
}

该函数注册了一个回调函数,用于处理新连接的接受。此外,它还设置了socket的属性,如非阻塞模式、资源检测等,确保socket能够在高效的同时保持稳定。

最后,evconnlistener_cb回调函数的定义如下:

typedef void (*evconnlistener_cb)(struct evconnlistener *listener, 
evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data);

这个回调函数需要由开发者自行实现。在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的核心优势之一在于其基于事件的机制,它能够自动管理socket的读写状态,并通过回调函数灵活处理数据。

在上述回调函数中,我们使用了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);
}

该函数用于注册读写事件的回调。当socket有可读数据时,会调用readcb函数;当有可写数据时,会调用writecb函数;在发生错误时,会调用eventcb函数。

对于writecb回调函数,可以参考sample目录中的实现代码:

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);
}
}

在实际应用中,可以根据需求自定义这些回调函数,处理数据的读写逻辑。通过这种方式,libevent能够高效地管理网络连接的生命周期,从绑定到监听,再到数据的读写处理,所有流程都得到了充分的支持。

上一篇:libevent定时器是怎么实现的
下一篇:libevent的事件机制

发表评论

最新留言

表示我来过!
[***.240.166.169]2025年04月06日 10时56分06秒