zoukankan      html  css  js  c++  java
  • Libevent库介绍

    1.libevent介绍

    简介

    基本的socket变成是阻塞/同步的,每个操作除非已经完成,出错,或者超时才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没有办法支撑大量的请求。posix定义了可以使用异步的select系统调用,但是因为它采用了轮询的方式来判断某个fd是否变成active,效率不高。于是各系统就分别提出了基于异步的系统调用,例如Linux的epoll,由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。

    原理简介

    libevent默认情况下是单线程的,可以配置成多线程,每个线程有且只有一个event_base,对应一个struct event_base结构体以及附于其上的事件管理器,用来调度托管给它的一系列event,可以和操作系统的进程管理类比。当一个事件发生后,event_base会在合适的时间,不一定是立即去调用绑定在这个事件上的函数,直到这个函数执行完,再去调度其他的事件。

    1 //创建一个event_base
    2 struct event_base *base = event_base_new();
    3 assert(base != NULL);
    event_base内部有一个循环,循环阻塞在epoll等系统调用上,直到有一个/一些时间发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上,每个事件对应一个struct event,可以是监听一个fd或者信号量之类的,struct event使用event_new来创建和绑定,使用event_add来将event绑定到event_base中。
    1 // 创建并绑定一个event
    2 struct event* listen_event;
    3 
    4 //参数:event_base,监听的对象,需要监听的事件,事件发生后的回调函数,传给回调函数的参数
    5 listen_event = event_new(base, listener, EV_READ | EV_PERSIST, callback_func, (void*)base);
    6 //参数:event,超时时间,NULL表示无超时设置
    7 event_add(listen_event, NULL);

    注:libevent支持的事件及属性包括(使用bitfield实现)

    1. EV_TIMEOUT:超时;
    2. EV_READ:只要网络缓冲中还有数据,回调函数就会被触发;
    3. EV_WRITE:只要塞给网络缓冲的数据被写完,回调函数就会被触发;
    4. EV_SIGNAL:POSIX信号量;
    5. EV_PERSIST:不指定这个属性,回调函数被触发后事件会被删除;
    6. EV_ET:Edge-Trigger边缘触发(这个还不懂是什么意思)

    然后启动event_base的循环,开始处理事件。循环地启动使用event_base_dispatch,循环将一直持续,找到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。

    //启动循环,开始处理事件
    event_base_dispatch(base);

    接下来再来关注事件发生时的回调函数callback_func,callback_func的原型如下所示

    typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)

    传给callback_func的是一个监听的fd,监听的事件类型,以及event_new中最后一个参数。在上述程序中,是将event_base传给了callback_func,实际中更常用的是构造一个结构体,把需要传给回调函数的参数都放进来,然后传给event_new,event_new再传给回调函数。

    所以总结一下,对于一个服务器而言,流程大致如下:

    1. 获取待监听的内容的fd;
    2. 创建一个event_base;
    3. 创建一个event,指定待监听的fd,待监听事件的类型,以及事件放生时的回调函数及传给回调函数的参数;
    4. 将event添加到event_base的事件管理器中;
    5. 开启event_base的事件处理循环;
    6. (异步)当事件发生的时候,调用前面设置的回调函数。

    2.libevent 使用流程

    使用流程

    1. 创建一个事件处理框架
    2. 创建一个事件
    3. 事件添加到处理框架
    4. 开始事件循环
    5. 释放资源

    事件处理框架 - event_base

    1. 使用libevent函数之前需要分配一个或者多个event_base结构体. 每个event_base结构体有一个事件集合,可以检测以确定哪个事件是激活的.

      • 相当于epoll红黑树的树根
      • 底座
      • 抽象层,完成对event_base的封装
      • 每个event_base都有一种用于检测那种事件已经就绪的"方法",或者说后端
    2. 具体操作

      • 创建event_base
        • struct event_base* event_base_new(void);
        • 失败返回NULL
      • 释放event_base
        • event_base_free(struct event_base* base);
      • 循环监听base对应的事件, 等待条件满足
        • event_base_dispatch(struct event_base* base);
    3. 查看event_base封装的后端(当前系统中支持那些函数)

      • const char **event_get_supported_methods();
      • 返回一个字符串数组中
      • const char * event_base_get_method(const struct event_base *base);
      • 返回当前使用的IO转接
    4. event_base和fork

      • 子进程创建成功后,父进程可以继续使用event_base
      • 子进程中需要继续使用event_base需要重新进行初始化
        • int event_reinit(struct event_base * base)

    事件 - event

    1. 创建新事件
    
    #define  EV_TIMEOUT         0x01    // 废弃
    #define  EV_READ            0x02
    #define  EV_WRITE           0x04
    #define  EV_SIGNAL          0x08
    #define  EV_PERSIST         0x10    // 持续触发
    #define  EV_ET              0x20    // 边沿模式
    
    typedef void(*event_callback_fn)(evutil_sockt_t,short,void *);
    
    struct event *event_new(
        struct event_base *base,
        evutil_socket_t fd,     // 文件描述符-int
        shord what,
        event_callback_fn cb,   // 事件处理动作
        void *arg
    );
    
    1. 释放事件

      • void event_free(struct event *event);
    2. 设置未决事件(有资格但是没有被处理的事件)

      • 构造事件之后,在将其添加到 event_base 之前实际上是不能对其做任何操作的。使用event_add()将事件添加到event_base, 非未决事件 -> 未决事件.
      • 函数
        int event_add(
            struct event *ev, 
            const struct timeval *tv
        ); 
        
      • tv:
        • NULL: 事件被触发, 对应的回调被调用
        • tv = {0, 100}, 如果设置的时间,
          • 在改时间段内检测的事件没被触发, 时间到达之后, 回调函数还是会被调用
      • 函数调用成功返回0, 失败返回-1
    3. 设置非未决(还没有资格被处理的事件)

      • int event_del(struct event *ev);
      • 对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。如果事件不是未决的或者激活的,调用将没有效果。成功时函数返回 0,失败时返回-1。

    事件循环

    1. 开始循环

      • 一旦有了一个已经注册了某些事件的 event_base, 就需要让 libevent 等待事件并且通知事件的发生。
      #define EVLOOP_ONCE                        0x01
      	事件只会被触发一次
      	事件没有被触发, 阻塞等
      #define EVLOOP_NONBLOCK                    0x02
      	非阻塞 等方式去做事件检测
      	不关心事件是否被触发了
      #define EVLOOP_NO_EXIT_ON_EMPTY  0x04
      	没有事件的时候, 也不退出轮询检测
      
      • int event_base_loop(struct event_base *base, int flags);
        • 正常退出返回0, 失败返回-1
      • event_base_dispatch(struct event_base* base)
        • 等同于没有设置标志的 event_base_loop()
        • 将一直运行,直到没有已经注册的事件了,或者调用 了event_base_loopbreak()或者 event_base_loopexit()为止。
    2. 停止循环

      • 返回值:成功0,失败-1
      struct timeval{
          long tv_sec;
          long tv_usec;
      };
      
      • 如果 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后立即退出
      int event_base_loopexit(
          struct event_base *base,
          const struct timeval *tv
      );
      
      • 让event_base 立即退出循环
      int event_base_loopbreak(struct event_base *base);

    3.实例演示

      1 /*
      2   This exmple program provides a trivial server program that listens for TCP
      3   connections on port 9995.  When they arrive, it writes a short message to
      4   each client connection, and closes each connection once it is flushed.
      5 
      6   Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
      7 */
      8 
      9 
     10 #include <string.h>
     11 #include <errno.h>
     12 #include <stdio.h>
     13 #include <signal.h>
     14 #ifndef WIN32
     15 #include <netinet/in.h>
     16 # ifdef _XOPEN_SOURCE_EXTENDED
     17 #  include <arpa/inet.h>
     18 # endif
     19 #include <sys/socket.h>
     20 #endif
     21 
     22 #include <event2/bufferevent.h>
     23 #include <event2/buffer.h>
     24 #include <event2/listener.h>
     25 #include <event2/util.h>
     26 #include <event2/event.h>
     27 
     28 static const char MESSAGE[] = "Hello, World!
    ";
     29 
     30 static const int PORT = 9995;
     31 
     32 static void listener_cb(struct evconnlistener *, evutil_socket_t,
     33     struct sockaddr *, int socklen, void *);
     34 static void conn_writecb(struct bufferevent *, void *);
     35 static void conn_eventcb(struct bufferevent *, short, void *);
     36 static void signal_cb(evutil_socket_t, short, void *);
     37 
     38 int
     39 main(int argc, char **argv)
     40 {
     41     struct event_base *base;
     42     struct evconnlistener *listener;
     43     struct event *signal_event;
     44 
     45     struct sockaddr_in sin;
     46 #ifdef WIN32
     47     WSADATA wsa_data;
     48     WSAStartup(0x0201, &wsa_data);
     49 #endif
     50 
     51     base = event_base_new();  //创建base
     52     if (!base) {
     53         fprintf(stderr, "Could not initialize libevent!
    ");
     54         return 1;
     55     }
     56 
     57     memset(&sin, 0, sizeof(sin));
     58     sin.sin_family = AF_INET;
     59     sin.sin_port = htons(PORT);
     60 
     61     listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
     62         LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
     63         (struct sockaddr*)&sin,
     64         sizeof(sin));  //添加事件
     65 
     66     if (!listener) {
     67         fprintf(stderr, "Could not create a listener!
    ");
     68         return 1;
     69     }
     70 
     71     signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base); //添加信号事件
     72 
     73     if (!signal_event || event_add(signal_event, NULL)<0) {
     74         fprintf(stderr, "Could not create/add a signal event!
    ");
     75         return 1;
     76     }
     77 
     78     event_base_dispatch(base);
     79 
     80     evconnlistener_free(listener);
     81     event_free(signal_event);
     82     event_base_free(base);
     83 
     84     printf("done
    ");
     85     return 0;
     86 }
     87 
     88 static void
     89 listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
     90     struct sockaddr *sa, int socklen, void *user_data)
     91 {
     92     struct event_base *base = user_data;
     93     struct bufferevent *bev;
     94 
     95     bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
     96     if (!bev) {
     97         fprintf(stderr, "Error constructing bufferevent!");
     98         event_base_loopbreak(base);
     99         return;
    100     }
    101     bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
    102     bufferevent_enable(bev, EV_WRITE);
    103     bufferevent_disable(bev, EV_READ);
    104 
    105     bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
    106 }
    107 
    108 static void
    109 conn_writecb(struct bufferevent *bev, void *user_data)
    110 {
    111     struct evbuffer *output = bufferevent_get_output(bev);
    112     if (evbuffer_get_length(output) == 0) {
    113         printf("flushed answer
    ");
    114         bufferevent_free(bev);
    115     }
    116 }
    117 
    118 static void
    119 conn_eventcb(struct bufferevent *bev, short events, void *user_data)
    120 {
    121     if (events & BEV_EVENT_EOF) {
    122         printf("Connection closed.
    ");
    123     } else if (events & BEV_EVENT_ERROR) {
    124         printf("Got an error on the connection: %s
    ",
    125             strerror(errno));/*XXX win32*/
    126     }
    127     /* None of the other events can happen here, since we haven't enabled
    128      * timeouts */
    129     bufferevent_free(bev);
    130 }
    131 
    132 static void
    133 signal_cb(evutil_socket_t sig, short events, void *user_data)
    134 {
    135     struct event_base *base = user_data;
    136     struct timeval delay = { 2, 0 };
    137 
    138     printf("Caught an interrupt signal; exiting cleanly in two seconds.
    ");
    139 
    140     event_base_loopexit(base, &delay);
    141 }

    参考链接:https://www.jianshu.com/p/8ea60a8d3abb

         https://www.cnblogs.com/joker-wz/p/10735410.html

  • 相关阅读:
    fedora上部署ASP.NET——(卡带式电脑跑.NET WEB服务器)
    SQL Server 请求失败或服务未及时响应。有关详细信息,请参见事件日志或其它适合的错误日志
    8086CPU的出栈(pop)和入栈(push) 都是以字为单位进行的
    FTP 服务搭建后不能访问问题解决
    指定的 DSN 中,驱动程序和应用程序之间的体系结构不匹配
    Linux 安装MongoDB 并设置防火墙,使用远程客户端访问
    svn Please execute the 'Cleanup' command. 问题解决
    .net 操作MongoDB 基础
    oracle 使用绑定变量极大的提升性能
    尝试加载 Oracle 客户端库时引发 BadImageFormatException。如果在安装 32 位 Oracle 客户端组件的情况下以 64 位模式运行,将出现此问题。
  • 原文地址:https://www.cnblogs.com/single-dont/p/12866970.html
Copyright © 2011-2022 走看看