资讯专栏INFORMATION COLUMN

基于 epoll 设计类似 libevent 的异步 I/O 库 - 接口

Taste / 1018人阅读

摘要:支持三种事件文件事件或者称事件文件描述符事件。各参数说明如下当创建文件事件时,参数就是对应的文件描述符当创建信号事件时,则是信号码。接收事件的回调函数。获取到响应后,将自身事件清除掉。

这篇文章可以算是我在 GitHub 上一个工程的设计概要了。简要说明了该工程的设计思路以及技术要点。

本文章纯原创,没有参考资料。不过有设计过程中记录下的相关文章:

使用 pipe 在程序正文中捕获和处理信号

本文地址:https://segmentfault.com/a/1190000010098194

工程简介 基本原理

总所周知,在 Linux 中实现异步 I/O,适用的系统 API 就是 epoll。这里主要包括 epoll_ctl()epoll_wait() 两个函数。前者配置各个文件描述符在 epoll 里的机制,而后者则是关键的阻塞调用。

这个工程,就是基于 epoll,实现类似于 libevent 的异步 I/O 库。

函数的具体用法可以参照 man 页,或者工程的 AMCEpoll.c 源文件的 _dispatch_main_loop() 函数。


设计缘由

早期打算通用的写一个进程间通信库。通信库想要使基于异步 I/O 来做,又不想只是使用裸的 epoll。想起自己对异步 I/O 的原理也算是大致了解了,就自己研究着写一个。目前已经大致完成的 AMCEpoll 并没有经过严格的测试验证,更多地像是一个学习工程,或者说是课后作业一样。就像 Andrew S. Tanenbaum 教授的 Minix 类似哈。

不过这个库是瞄准了实用来设计的。如果要面向可靠的话,建议用开源的 libevent 来构建;如果觉得 libevent 太大了,则推荐 libev;如果觉得 libev 的开发者太少,不靠谱的话,那么可以用 libuv。如果需要有自己知识产权的产品的话,那么,可以自己设计一个——这也就是我开发这个库的初衷。

API

公共的 API 都在 AMCEpoll.h 文件中。各函数的说明如下:

接口说明

struct AMCEpoll

相当于 libevent 的 “event base”。这是整个 AMCEpoll 对象,每一个对象可执行一个事件循环(event loop)。

struct AMCEpollEvent

相当于 libevent 的 “event”。与 libevent 不同的是,每一个 event 可以从某个 base 中分离,再加入到另一个 base 里面去。不过实际上应该没有这样的需求,只是说我的程序架构允许这么做。

AMCEpoll 支持三种事件:

文件事件:或者称 fd 事件、文件描述符事件。基于 Linux 的 file descriptor 的事件。同时支持事件超时

信号事件:也就是 signal 事件。可以捕获信号,然后在事件循环中处理信号,而不用担心信号函数的上下文。信号时间也支持超时机制

超时事件:纯粹的超时事件,支持一次性的定时,也支持循环定时。

AMCEpoll_New()

创建、初始化并返回一个 AMCEpoll 对象。其中参数 pollSize 指的是 epoll_wait() 函数中的 maxevents 参数。

AMCEpoll_Free()

销毁一个 AMCEpoll 对象。如果对象中还有事件存在,并且事件关注了 EP_EVENT_FREE 事件,则会收到对应的回调。

不能在 event loop 中调用。

AMCEpoll_NewEvent()

创建并返回一个事件(AMCEpollEvent)对象。各参数说明如下:

fd:当创建文件事件时,fd 参数就是对应的文件描述符;当创建信号事件时,fd 则是信号码。如果是纯超时时间,fd 必须设置为 -1。需要注意的是,所有的文件事件在 event loop 启动之前,必须确保 fd 是非阻塞的。

events:EP_EVENT_XXX 掩码组成的时间列表集。具体各个掩码的作用,后文再说。纯粹的超时事件还没集成上去(这有什么难的呢,毕竟 fd 事件和 signal 事件的 timeout 都已经完成了。只是我最近没时间……)。

timeout:超时时间,单位是毫秒。当然,实际上超时调用的时候是大于这个数的,毕竟函数切换需要时间。在压力大的时候,建议超时不要小于 1 秒,最好大于 10 秒。

callback:接收事件的回调函数。

userData:事件回调函数的自定义参数。

AMCEpoll_FreeEvent()

销毁一个事件。如果事件关注了 EP_EVENT_FREE,那么会在此时收到回调。程序员可以在这个地方执行一些库以外的清理工作,比如 close(fd) 之类的。

如果该事件已经加入到某个 AMCEpoll 对象中,那么这个函数会出错。如果要避免这个问题,建议使用 AMCEpoll_DelAndFreeEvent() 函数。

AMCEpoll_AddEvent()

将某个 AMCEpollEvent 对象加入到 AMCEpoll 中,这样才能在事件循环中关注这个对象上的事件。

AMCEpoll_DelEvent()

将已经加入到 AMCEpoll 的时间分离开来,成为 “孤魂野鬼” 状态。

AMCEpoll_DelAndFreeEvent()

Del 和 Free 两个函数的结合。

AMCEpoll_SetEventTimeout()AMCEpoll_GetEventTimeout()

重新设置或者获取一个事件的超时时间。这只有在创建时,添加了 EP_EVENT_TIMEOUT 事件,才会有效。

AMCEpoll_Dispatch()

在当前线程启动事件循环。当没有事件注册在案的时候,event loop 会退出,也就是这个函数会返回。

AMCEpoll_LoopExit()

退出事件循环。如果这个函数在 event loop 外调用,那么 AMCEpoll 会在走完下一次 event chain 之后退出;如果在 event loop 中间调用,则 AMCEpoll 会走完当前 event chain 之后退出。Event chain 的原理,在后文说明。


事件类型

事件类型就是 event_t 数据类型,声明如下:

enum {
    EP_EVENT_READ    = (1 << 0),
    EP_EVENT_WRITE   = (1 << 1),
    EP_EVENT_ERROR   = (1 << 2),
    EP_EVENT_FREE    = (1 << 3),
    EP_EVENT_TIMEOUT = (1 << 4),
    EP_EVENT_SIGNAL  = (1 << 5),

    EP_MODE_PERSIST  = (1 << 8),    /* only used when adding events */
    EP_MODE_EDGE     = (1 << 9),    /* only used when adding events */
};

当创建事件的时候,上述所有的类型都可以使用;而当回调的时候,回调函数只可能接收到 EV_EVENT_XXX 类型的值。三种 AMCEpollEvent 所支持的具体类型如下:

———— _READ _WRITE _ERROR _FREE _TIMEOUT _SIGNAL _PERSIST _EDGE
文件事件 O O O O O . O O
信号事件 . . O O O O O O
超时事件 . . . O O . . .

具体的参数说明如下:

EP_EVENT_READ:读事件。如果出现这个事件类型,则视为文件事件。

EP_EVENT_WRITE:读事件。如果出现这个事件类型,则视为文件事件。

EP_EVENT_ERROR:一般是系统调用出错。保留。

EP_EVENT_FREE:“销毁” 事件。当调用 AMCEpoll_FreeEvent() 的时候触发。

EP_EVENT_TIMEOUT:超时事件

EP_EVENT_SIGNAL:信号事件。如果出现这个事件类型,则视为信号事件。不得与文件事件同时存在。

EP_MODE_PERSIST:持续事件。当一次回调(除了 free)完成后,程序会自动将事件及其超时配置加入到 event loop 中。当然,如果程序手动 add 的话,也是可以的,只是没必要。

EP_MODE_EDGE:边缘触发模式。如果使用这个模式的话,只在出现事件才会触发回调。

典型调用过程

基本的使用流程和 libevent 和 libev 非常类似,可以参照我的这篇文章:《使用 libev 构建 TCP 响应服务器的简单流程》

比如一个 UDP 事件基本流程:

创建 AMCEpoll 对象

创建 DGRAM fd,必要的话 bind

基于 fd 创建一个 AMCEpollEvent 对象

将 AMCEpollEvent 对象 add 到 AMCEpoll 对象中

写一个 callback,遇到 read 事件的时候,调用 recvfrom()

dispatch

其他事件的用法,则可以参照工程内的 test_server.c 文件。这个文件创建了三种事件:

一个 DNS 请求和解析,是一个 UDP read 事件。获取到 DNS 响应后,将自身事件清除掉。

一个 HTTP server(其实 HTTP 响应不规范,请别在意),并且 echo 回数据。是一个边沿触发的 TCP read 事件。

两个信号事件,分别监听 SIGINTSIGQUIT。监听到前者时,退出 AMCEpoll 事件循环。监听到后者时,打印调试信息。

下一篇

下一篇会说明一下工程的实现篇。文章尚未完成,敬请期待。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/10042.html

相关文章

  • libevent 中使用 MariaDB(MySQL)

    摘要:实线表示该状态的流转需要经过异步等待调用后才能获取相应的状态码或返回值进行检查后才可以进行的状态流转,虚线表示在该状态下即已有足够的变量可进行状态流转。而函数的返回值,则换成一个类型的变量,用于适配异步。 在之前我翻译的官方文档中提到了 MariaDB 提供了对异步 I/O 的支持。那篇文章是一个比较简要的介绍。不过实际适配中,官方也提供了一个完整适配 libevent 的示例代码。本...

    djfml 评论0 收藏0
  • 谈谈Python协程技术演进

    摘要:事件循环是异步编程的底层基石。对事件集合进行轮询,调用回调函数等一轮事件循环结束,循环往复。协程直接利用代码的执行位置来表示状态,而回调则是维护了一堆数据结构来处理状态。时代的协程技术主要是,另一个比较小众。 Coding Crush Python开发工程师 主要负责岂安科技业务风险情报系统redq。 引言 1.1. 存储器山 存储器山是 Randal Bryant 在《深入...

    zhiwei 评论0 收藏0
  • 基于汇编 C/C++ 协程 - 背景知识

    摘要:近几年来,协程在服务器中的解决方案开始涌现。本文主要阐述以汇编实现上下文切换的协程方案,并且说明其在异步开发模式中的应用。协程原理协程的实现,涉及两个内容协程调度上下文切换协程的调度协程调度的原理,往大了说,其实和线程进程的调度原理无异。 近几年来,协程在 C/C++ 服务器中的解决方案开始涌现。本文主要阐述以汇编实现上下文切换的协程方案,并且说明其在异步开发模式中的应用。 本文地址:...

    VPointer 评论0 收藏0
  • 使用 pipe 在程序正文中捕获和处理信号

    摘要:本文地址环境高级编程源码深度剖析使用和在程序正文中捕获和处理信号基本原理在设置捕获信号之前,首先创建一个通信通道。在中断处理函数中,将捕获到的信号数往这个通道内写入。大量的数据处理放在程序正文中,获取信号的方法很简单,只是。 我的上一篇文章研究了一下如何在程序的正文(而不是信号处理函数)中捕获和处理信号。当时用的方案是 sigprocmask()。但那个方法理论上是可能漏掉一些信号的。...

    NotFound 评论0 收藏0
  • 物联网高并发编程之C10K问题原理和解决方案

    摘要:问题任一文件句柄的不成功会阻塞住整个应用。主要解决的前两个问题通过一个数组向内核传递需要关注的事件消除文件句柄上限,同时使用不同字段分别标注关注事件和发生事件,来避免重复初始化。问题逐个排查所有文件句柄状态效率不高。 C10K问题思维导图 showImg(https://segmentfault.com/img/bVbkrKe?w=1818&h=1276); C10K问题出现前期 大家...

    dayday_up 评论0 收藏0

发表评论

0条评论

Taste

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<