libevent源码剖析-evconnlistener
1 简介
evconnlistener 是 libevent 提供的一个用于处理传入连接的接口。它可以简化监听套接字的处理,使用户能够方便地在事件循环中接收客户端连接,并为每个新连接关注读写事件。
其主要功能如下:
- 监听新连接: 通过监听套接字,接受传入的客户端连接。
- 回调处理: 为每一个新连接自动调用用户提供的回调函数处理连接事件。
- 错误处理: 当监听过程中发生错误时,也会触发用户提供的错误回调函数。
1.1 主要API
1.1.1 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);
- 功能: 创建并绑定监听器,监听传入的连接。
- 参数:
• base: 事件基础对象,event_base。
• cb: 监听到新连接时的回调函数。
• ptr: 传递给回调函数的自定义参数。
• flags: 设置监听器的标志(如 LEV_OPT_REUSEABLE 允许地址重用)。
• backlog: 套接字监听的最大队列长度。
• sa: 指定绑定的地址。
• socklen: 地址长度
1.1.2 evconnlistener_free
定义:
void evconnlistener_free(struct evconnlistener *listener);
1.1.3 evconnlistener_set_error_cb
定义:
void evconnlistener_set_error_cb(struct evconnlistener *lev, evconnlistener_errorcb errorcb);
功能: 设置错误回调函数,当发生错误时调用该回调。
1.2 例子
以下是一个使用 evconnlistener 的简单 TCP 服务端示例:
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>// 处理每个客户端连接的回调函数
void echo_read_cb(struct bufferevent *bev, void *ctx) {// 读取客户端发送的数据struct evbuffer *input = bufferevent_get_input(bev);struct evbuffer *output = bufferevent_get_output(bev);// 将接收到的数据直接回送给客户端evbuffer_add_buffer(output, input);
}// 处理客户端断开连接或发生错误的回调函数
void echo_event_cb(struct bufferevent *bev, short events, void *ctx) {if (events & BEV_EVENT_ERROR) {perror("Error from bufferevent");}if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {bufferevent_free(bev);}
}// 处理新客户端连接的回调函数
void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx) {struct event_base *base = (struct event_base *)ctx;// 创建 bufferevent 来处理该客户端连接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);
}// 错误回调函数
void accept_error_cb(struct evconnlistener *listener, void *ctx) {struct event_base *base = (struct event_base *)ctx;int err = EVUTIL_SOCKET_ERROR();fprintf(stderr, "Got an error %d (%s) on the listener. Shutting down.\n",err, evutil_socket_error_to_string(err));event_base_loopexit(base, NULL); // 停止事件循环
}int main(int argc, char **argv) {struct event_base *base;struct evconnlistener *listener;struct sockaddr_in sin;// 初始化事件基础结构base = event_base_new();if (!base) {fprintf(stderr, "Could not initialize libevent!\n");return 1;}// 设置服务器监听的地址和端口memset(&sin, 0, sizeof(sin));sin.sin_family = AF_INET;sin.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网络接口sin.sin_port = htons(9999); // 监听端口 9999// 创建并绑定 evconnlistenerlistener = evconnlistener_new_bind(base, accept_conn_cb, (void *)base,LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,(struct sockaddr *)&sin, sizeof(sin));if (!listener) {perror("Could not create a listener!");return 1;}// 设置错误处理回调evconnlistener_set_error_cb(listener, accept_error_cb);// 开始事件循环event_base_dispatch(base);// 释放资源evconnlistener_free(listener);event_base_free(base);return 0;
}
值得一提的是,libevent底层屏蔽了各平台所支持的IO多路复用的细节,对外提供了统一的接口,若在某个平台下指定使用某个多路复用,可在分配event_base实例时指定配置即可。比如在win下要使用IOCP完成端口,详情参见 libevent源码剖析之iocp_iocp 源码-CSDN博客 一文。
2 源码剖析
evconnlistener 是 libevent 中用于处理传入连接的抽象层,它简化了服务端监听 socket 的实现,并将新连接的处理交由用户定义的回调函数。它的实现原理基于 socket 和 accept 系统调用,并结合 libevent 的事件机制,将监听和新连接的处理集成到事件循环中。
2.1 evconnlistener 的工作流程
2.1.1 socket 绑定和监听
当调用 evconnlistener_new_bind 函数时,它会创建一个监听 socket,并绑定到指定的 IP 地址和端口。然后,通过 listen 系统调用将该 socket 置于监听状态,准备接收客户端连接。
evconnlistener 将监听 socket 注册到事件循环中,监听 EV_READ 事件(表示有新连接到达)。
2.1.2 处理新连接
当有新的连接到达时,监听的 socket 变为可读,这时事件循环会触发 evconnlistener 监听的回调,内部调用 accept 系统调用来接受客户端连接。
接受到的新连接会通过用户指定的回调函数 evconnlistener_cb 传递给用户,用户可以对这个连接创建 bufferevent 等进一步处理。
2.1.3 事件管理
evconnlistener 使用 Libevent 的 event_base,通过事件机制自动处理 socket 的 EV_READ(有新连接时)事件。当有新的连接到达时,它会执行用户定义的连接处理回调函数。
如果在监听过程中发生了错误,evconnlistener 会触发错误回调函数,通常是由 evconnlistener_set_error_cb 来设置的。
2.2 evconnlistener 的内部结构
evconnlistener 的核心结构如下:
struct evconnlistener_ops {int (*enable)(struct evconnlistener *);int (*disable)(struct evconnlistener *);void (*destroy)(struct evconnlistener *);void (*shutdown)(struct evconnlistener *);evutil_socket_t (*getfd)(struct evconnlistener *);struct event_base *(*getbase)(struct evconnlistener *);
};struct evconnlistener {// 将evconnlistener相关操作封装起来const struct evconnlistener_ops *ops;void *lock;// 当accept成功后回调此函数通知user侧evconnlistener_cb cb;// accept错误通知evconnlistener_errorcb errorcb;void *user_data;unsigned flags;short refcnt;int accept4_flags;unsigned enabled : 1;
};
2.2.1 fd 和 listener_event
fd 是监听 socket 的文件描述符,通过 socket() 创建。
listener_event 是监听 socket 的事件,监听 EV_READ 事件,即有新连接到来时,事件循环触发 accept 操作。
2.2.2 回调函数
cb:当有新连接到来时调用,用于处理新连接。
errorcb:在监听过程中发生错误时调用,处理错误事件。
2.3 evconnlistener 的实现原理
2.3.1 evconnlistener_new_bind
创建一个新的 evconnlistener,并将 socket 绑定到指定的地址和端口。
使用 socket() 创建 socket,bind() 绑定地址,listen() 进入监听状态。
初始化 evconnlistener 结构体,分配内存并将 socket 相关信息保存在 fd 中。
使用 event_assign() 将监听 socket 的 EV_READ 事件与 listener_event 关联,并通过 event_add() 将其注册到 event_base 中进行监听。
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;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;}if (sa) {if (bind(fd, sa, socklen)<0)goto err;}// 实际上是对evconnlistener_new的封装listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd);if (!listener)goto err;return listener;
err:evutil_closesocket(fd);return NULL;
}
2.3.2 evconnlistener_new
evconnlistener_new_bind实际上是对evconnlistener_new的封装,因此,这里展开evconnlistener_new看其实现:
// 分配一个evconnlistener的实例
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// 这是WIN32下IOCP的监听逻辑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);}
#endifif (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;// 此为user侧注册的关注accept成功的回调lev->base.cb = cb;lev->base.user_data = ptr;lev->base.flags = flags;// evconnlistener的引用计数,为0时要freelev->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);}// 缺省使用LT模式,而非ET模式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_new里会注册accept成功事件,在listener_read_cb里通知user侧,表示新连接到来了。
2.3.3 listener_read_cb
当有新连接到来时,事件循环触发 listener_read_cb 回调函数。它会调用 accept() 系统调用来接收新连接。
新连接的 socket 通过 accept() 得到,并传递给用户的连接回调函数 cb,用户可以基于这个连接进一步操作,如创建 bufferevent 等。
static void
listener_read_cb(evutil_socket_t fd, short what, void *p)
{struct evconnlistener *lev = p;int err;evconnlistener_cb cb;evconnlistener_errorcb errorcb;void *user_data;LOCK(lev);// 此处是死循环accept,可接受所有client的connect请求,可兼容LT和ET模式// evconnlistener采用的是LT模式while (1) {struct sockaddr_storage ss;ev_socklen_t socklen = sizeof(ss);evutil_socket_t new_fd = evutil_accept4_(fd, (struct sockaddr*)&ss, &socklen, lev->accept4_flags);if (new_fd < 0)break;if (socklen == 0) {/* This can happen with some older linux kernels in* response to nmap. */evutil_closesocket(new_fd);continue;}if (lev->cb == NULL) {evutil_closesocket(new_fd);UNLOCK(lev);return;}++lev->refcnt;cb = lev->cb;user_data = lev->user_data;UNLOCK(lev);cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen,user_data);LOCK(lev);// 正常情况下,到这里时lev->refcnt == 2 // 若此时开发者已调用了evconnlistener_free,则做清理工作,并不再acceptif (lev->refcnt == 1) {int freed = listener_decref_and_unlock(lev);EVUTIL_ASSERT(freed);evutil_closesocket(new_fd);return;}--lev->refcnt;}err = evutil_socket_geterror(fd);if (EVUTIL_ERR_ACCEPT_RETRIABLE(err)) {UNLOCK(lev);return;}if (lev->errorcb != NULL) {++lev->refcnt;errorcb = lev->errorcb;user_data = lev->user_data;UNLOCK(lev);errorcb(lev, user_data);LOCK(lev);listener_decref_and_unlock(lev);} else {event_sock_warn(fd, "Error from accept() call");UNLOCK(lev);}
}
2.3.4 错误处理
在监听过程中,如果出现错误(如套接字异常关闭等),则会触发 errorcb 回调。
错误处理的机制通过 evconnlistener_set_error_cb 函数来设置。
void
evconnlistener_set_error_cb(struct evconnlistener *lev,evconnlistener_errorcb errorcb)
{LOCK(lev);lev->errorcb = errorcb;UNLOCK(lev);
}
2.3.5 资源释放
当不再需要监听时,调用 evconnlistener_free() 来关闭监听 socket,并释放 evconnlistener 结构体及相关资源。
它会调用 event_del() 从事件循环中移除监听事件,并关闭 socket。
void evconnlistener_free(struct evconnlistener *listener) {event_del(&listener->listener_event);close(listener->fd);free(listener);
}
3 小结
evconnlistener 通过事件循环和 accept 调用,将新连接的处理简化为一个回调机制,使得开发者可以方便地在事件驱动模型中处理大量的连接。
它将监听 socket 的 EV_READ 事件与回调函数关联,当有新连接时自动调用 accept,并触发用户定义的回调来处理新连接。
通过事件机制和回调,evconnlistener 为基于 libevent 的网络编程提供了一个高效且简洁的解决方案。