• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2021-12-19 22:55 Aet 隐藏边栏 |   抢沙发  10 
文章评分 2 次,平均分 5.0

概述

  1. Linux服务器程序必须处理三类事件:
    1. I/O事件
    2. 信号
    3. 定时事件
  2. 在处理这三类事件时通常需要考虑三个问题:
    1. 统一事件源
    2. 可移植性
    3. 对并发编程的支持

I/O框架概述

  1. I/O框架库以库函数的形式,封装了较为底层的系统调用,给应用程序提供了一组方便使用的接口

    1. 这些库函数往往比程序员自己实现的同样功能更合理,更高效,且更健壮
  2. 各种I/O框架库的实现原理基本相似,要么以Reactor模式实现,要么以Proactor模式实现,要么同时以这两种实现

  3. 基于ReactorI/O框架库包含:

    1. 句柄
    2. 事件多路分发器
    3. 事件处理器
    4. 具体的事件处理器
    5. Reactor

句柄

  1. I/O框架要处理的对象,即I/O事件、信号和定时事件,统一称为事件源
  2. 一个事件源通常和一个句柄绑定在一起
  3. 句柄的作用是,当内核检测到就绪事件时,他将通过句柄来通知应用程序这一事件
  4. Linux环境下,I/O事件对应的句柄是文件描述符,信号事件对应的句柄就是信号值

事件多路分发器

  1. 事件的到来是随机的、异步的

    1. 我们无法预知程序何时收到一个客户连接请求,又或者收到一个暂停信号,所以程序需要循环地等待并处理事件,这就是事件循环
  2. 在事件循环中,等待事件一般使用I/O复用技术实现

    1. I/O框架库一般将系统支持的各种I/O复用系统调用封装成统一的接口,称为事件多路分发器
  3. 事件多路分发器的demultiplex方法是等待事件的核心函数,内部调用的是selectpollepoll_wait函数

  4. 此外,事件多路分发器还需要实现register_eventremove_event方法,以供调用者往多路分发器中添加事件和从事件多路分发器中删除事件

事件处理器和具体事件处理器

  1. 事件处理器执行事件对应的业务逻辑,通常包含一个或多个的handle_event回调函数,这些回调函数在事件循环中被执行

  2. I/O框架库提供的事件处理器通常是一个接口,用户需要继承它来实现自己的事件处理器,即具体事件处理器

    1. 因此,事件处理器中的回调函数一般被声明为虚函数,以支持用户扩展
  3. 事件处理器一般还提供一个get_handle方法,它返回与该事件处理器关联的句柄

    1. 当事件多路分发器检测到有事件发生时,它是通过句柄来通知应用程序的
    2. 因此,必须将事件处理器和句柄绑定,才能在事件发生时获取到正确的事件处理器

Reactor

  1. ReactorI/O框架库的核心,提供几个主要方法:
    1. handle_events:用于执行事件循环,重复以下过程:
      等待事件
      依次处理所有就绪事件对应的事件处理器
    2. register_handler:调用事件多路分发器的register_event方法来往事件多路分发器中注册一个事件
    3. remove_handler:调用事件多路分发器的remove_event方法来删除事件多路分发器中的一个事件

Libevent源码分析

理解

  1. event_base可以理解为一个管理器
    1. 这个管理器可以处理它从属的事件对象event
  2. event事件对象,里面会有事件消息的类型,如EV_READ,以及对该消息的回调函数
    1. 也就是说,事件对象将事件消息以及回调函数进行了关联
  3. 事件对象创建后可以通过event_add注册到管理器里面,管理器发现是自己从属的事件对象,就对里面的事件消息类型以及回调函数进行注册
  4. 管理器有了,事件对象也有了,然后用event_base_dispatch将管理器的事件循环跑起来
    1. 管理器就开始一直监控注册的事件消息类型有没有发生,有,就调用对应的回调函数
    2. 没有发生,如果一直阻塞等待,那效率就不高,所以就需要用I/O复用模型,来高效地监控多个文件描述符
      确保事件循环在等待事件时不会浪费资源
    3. 至于I/O复用模型,Libevent 把这些常见的I/O 复用机制进行了抽象,提供了统一的接口
    4. 然后,Libevent 的事件基(event base)会在内部使用适当的 I/O 复用机制来监控文件描述符上的事件

特点

  1. 跨平台
  2. 统一事件源
  3. 线程安全
  4. 基于Reactor模式

实例

  1. 调用event_init函数创建了event_base对象
    一个event_base相当于一个Reactor实例。
  2. 创建具体的事件处理器,并设置它们所从属的Reactor实例
    evsignal_newevtimer_new分别用于创建信号事件处理器和定时事件处理器,它们是定义在include/event2/event.h中的宏,它们的统一入口是event_new函数,用于创建通用事件处理器

    1. 其中,base参数指定新创建的事件处理器从属的Reactor
    2. fd参数指定与该事件处理器关联的句柄
      创建I/O事件处理器时,应该给fd参数传递文件描述符值
      创建信号事件处理器时,应该给fd参数传递信号值
      创建定时事件处理器时,应该给fd参数传递-1
    3. event参数指定事件类型(下图)
      EV_PERSIST的作用是:事件被触发后,自动重新对这个event调用event_add函数
    4. cb参数指定目标事件对应的回调函数
    5. arg参数则是Reactor传递给回调函数的参数

  1. event_new成功时返回一个event类型对象,也就是Libevent的事件处理器
  2. libevent用“event"描述事件处理器,而不是事件:
    1. 事件指的是一个句柄上绑定的事件,比如文件描述符上的可读事件
    2. 事件处理器,也就是event结构体类型的对象,除了包含事件必备的两个要素(句柄和事件类型)之外,还有很多其他成员,比如回调函数。
    3. 事件由事件多路分发器管理,事件处理器则由事件队列管理。事件队列包括多种,比如event_base中的注册事件队列、活动事件队列和通用定时器队列,以及evmap中的I/O事件队列、信号事件队列
    4. 事件循环对一个被激活事件(就绪事件)的处理,指的是执行该事件对应的事件处理器中回调函数
  3. event_add,将事件处理器添加到注册事件队列中,并将该事件处理器对应的事件添加到事件多路分发器中
    event_add相当于Reactor中的register_handler方法
  4. event_base_dispatch函数来执行事件循环
  5. 事件循环结束后,使用*_free系列函数来释放系统资源。

源代码组织架构

  1. include/event2
    1. 提供给应用程序使用,比如event.h提供核心函数,http.h提供HTTP协议相关服务,rpc.h提供了远程过程调用支持
    2. 根目录下的头文件,一类是对include/event2目录下的部分头文件的包装,另一类是供libevent内部使用的辅助性头文件
  2. compat/sys
    1. queue.h封装了跨平台的基础数据结构,包括单向链表、双向链表、队列、尾队列和循环队列。
  3. sample
    1. 示例程序
  4. test
    1. 测试代码
  5. WIN32-Code
    1. 提供了Windows平台上的一些专用代码
  6. event.c
    1. 实现了libevent的整体框架,主要是eventevent_base两个结构体相关操作
  7. devpoll.c;kqueue.c;evport.c;select.c;win32select.c;poll.c;epoll.c
    1. 分别封装了/dev/poll;kqueue;event_ports;POSIX select;Windows select;poll;epoll
    2. 这些文件主要内容相似,都是针对eventop结构体所定义的接口函数的具体实现
  8. minheap-internal.h
    1. 实现了一个时间堆,以提供对定时时间的支持
  9. signal.c
    1. 提供了对信号的支持
    2. 其内容也是针对结构体eventop所定义的接口函数的具体实现
  10. evmap.c
    1. 维护句柄(文件描述符或信号)与事件处理器的映射关系
  11. event_tagging.c
    1. 提供了往缓冲区中添加标记数据(比如一个整数),以及从缓冲区中读取标记数据的函数
  12. event_iocp.c
    1. 提供了对Windows IOCP(输入输出完成端口)的支持
  13. buffer*.c
    1. 提供了对网络I/O缓冲的控制,包括:输入输出数据过滤,传输速率限制,使用SSL协议对应用数据进行保护,以及零拷贝文件传输等
  14. evthread*.c
    1. 提供了对多线程的支持
  15. listener.c
    1. 封装了对监听socket的操作,包括监听连接和接受连接
  16. logs.c
    1. libevent的日志系统
  17. evutil.c;evutil_rand.c;strlcpy.c;arc4random.c
    1. 提供了一些基础操作,比如生成随机数、获取socket地址信息,读取文件,设置socket属性等
  18. evdns.c;http.c;evrpc.c
    1. 分别提供了对DNS协议、HTTP协议和RPC协议的支持
  19. epoll_sub.c
  20. event-internal.h;include/event2/event_struct.h;event.c;evmap.c
    1. 整个源码中,这4个文件最重要

event结构体

  1. ev_events
    1. 代表事件类型
  2. ev_next
    1. 所有以及注册的事件处理器(包括I/O事件处理器和信号事件处理器)通过该成员串联成一个尾队列,称之为注册事件队列
      TAILQ_ENTRY是尾队列中的节点类型,定义在compat/sys/queue.h

  1. ev_active_next
    1. 所有被激活的事件处理器通过该成员串联成一个尾队列,称之为活动事件队列。
    2. 活动事件队列不止一个,不同优先级的事件处理器被激活后将插入不同的活动事件队列中。
    3. 在事件循环中,Reactor将按优先级从高到低遍历所有活动事件队列,并依次处理其中的事件处理器。
  2. ev_timeout_pos
    1. 这个是一个联合体。仅用于定时事件处理器。
    2. 老版本的libevent中,定时器都是由时间堆来管理的。但开发者认为有时候使用简单的链表来管理定时器将有更高的效率。
      新版本libevent引入了所谓的“通用定时器”概念。这些定时器不是存储在时间堆中,而是存储在尾队列中,称之为通用定时器队列。
    3. 对于通用定时器而言,ev_timeout_pos联合体的ev_next_with_common_timeout成员指出了该定时器在通用定时器队列中的位置。
    4. 对于其他定时器而言,ev_timeout_pos联合体的min_heap_idx成员指出了该定时器在时间堆中的位置。
    5. 一个定时器是否是通用定时器取决于其超时值大小,具体判断原则由event.cis_common_timeout函数决定。
  3. _ev
    1. 这是一个联合体。
    2. 所有具有相同文件描述符值的I/O事件处理器通过ev.ev_io.ev_io_next成员串联成一个尾队列,称之为信号事件队列
    3. ev.ev_signal.ev_ncalls成员指定信号事件发生时,Reactor需要执行多少次该事件对应的事件处理器中的回调函数
    4. ev.ev_signal.ev_pncalls指针成员要么是NULL,要么指向ev.ev_signal.ev_ncalls
  4. 在程序中,可能针对同一个socket文件描述符上的可读、可写事件创建多个事件处理器(它们拥有不同的回调函数)
    当该文件描述符上有可读或可写事件发生时,所有这些事件处理器都应该被处理
    所以,Libevent使用I/O事件队列将具有相同文件描述符值的事件处理器组织在一起
    这样,当一个文件描述符上有事件发生时,事件多路分发器就能很快地把所有相关的事件处理器添加到活动事件队列中
    信号事件队列的存在也是同样的原因
    可见,I/O事件队列和信号事件队列并不是注册事件队列的细致分类,而是另有用处
  5. ev_fd
    1. 对于I/O事件处理器,它是文件描述符;对于信号事件处理器,它是信号值
  6. ev_base
    1. 该事件处理器从属的event_base实例
  7. ev_res
    1. 它记录当前激活事件的类型
  8. ev_flags
    1. 它是一些事件标志,值在include/event2/event_struct.h
  9. ev_pri
    1. 指定事件处理器优先级,值越小优先级越高
  10. ev_closure
    1. 指定event_base执行事件处理器的回调函数时的行为
  11. ev_timeout
    1. 仅对定时器有效,指定定时器的超时值
  12. ev_callback
    1. 事件处理器的回调函数,由event_base调用
  13. ev_arg
    1. 回调函数的参数

往注册事件队列中添加事件处理器

往事件多路分发器中注册事件

  1. event_queue_insert函数做的仅仅是将一个事件处理器加入event_base的某个事件队列中.
  2. 对于添加的I/O事件处理器和信号事件处理器,我们还需要让事件多路分发器来监听其对应的事件,同时建立文件描述符、信号值与事件处理器之间的映射关系
    通过evmap_io_addevmap_signal_add两个函数来完成。这两函数相当于事件多路分发器中的register_event方法

eventop结构体

event_base结构体

事件循环

  1. libevent实现事件循环的函数是event_base_loop
  2. 该函数首选调用I/O事件多路分发器的事件监听函数,以等待事件。当有事件发生时,就依次处理。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2024-07-03
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享