当前位置: 首页 > news >正文

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 的实现,并将新连接的处理交由用户定义回调函数。它的实现原理基于 socketaccept 系统调用,并结合 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 的网络编程提供了一个高效且简洁的解决方案。


http://www.mrgr.cn/news/58248.html

相关文章:

  • 擎创科技声明
  • 【Java基础】反射的解析与应用场景说明
  • Qt(简介)
  • 基于django的网络问政管理平台
  • JavaSE——IO流7:其他流
  • leetcode动态规划(八)-不同的二叉搜索树
  • 不是网吧去不起,而是云电脑更具性价比
  • 筋膜枪哪个品牌最好性价比最高?倍益康M2 Pro Max,航天科技助力全家按摩体验
  • STM32--I2C通信
  • 轻松拿下offer,一次真实的面试回答记录
  • 最强开源大模型面世:阿里发布Qwen2
  • LINUX设备可以上网,但是外部设备连接linux设备之后,外部设备无法上网
  • 新版本发布丨向企业级实时计算平台迈进!支持存算分离、FICC 函数库大更新!
  • DDD系列 - 番外篇1 记一些常用的架构设计原则
  • ReentrantReadWriteLock底层实现原理?
  • vue3中ref和reactive的用法,区别和优缺点,以及使用场景
  • FMEA 系统在医疗设备行业的重要性与创新_SunFMEA
  • 漏洞挖掘 | 记一次逻辑漏洞修改任意用户密码
  • 【主机漏洞扫描常见修复方案】:Tomcat安全(机房对外Web服务扫描)
  • CSS简介
  • 气膜建筑:突破传统建筑的优势—轻空间
  • 大学新生如何开启高效学习编程之路
  • 书客、孩视宝、霍尼韦尔护眼大路灯哪款更好?对比测评谁是top1!
  • office的图标白板后的修改方法
  • Qt 二进制文件的读写
  • 2024中国AI Agent市场研究报告|附43页PDF文件下载