
本文共 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能够高效地管理网络连接的生命周期,从绑定到监听,再到数据的读写处理,所有流程都得到了充分的支持。
发表评论
最新留言
关于作者
