• Libevent库的学习


    目录

    Libevent 概述

    Libevent 使用模型

    使用Libevent的基本流程:

    libevent 的核心,event 事件

    1. 创建一个事件event

    2. 释放event_free

    3. 注册event

    4. 信号事件 

    5. 销毁event_base 

    Libevent 结构图

    使用libevent库去实现tcp服务器


    Libevent 概述

    Libevent 是开源社区的一款高性能的 I/O 框架库,使用 Libevent 的著名案例有:高性能 的分布式内存对象缓存软件 memcached,Google 浏览器 Chromium 的 Linux 版本。作为一个 I/O 框架库,

    Libevent 具有如下特点:

    ◼ 跨平台支持。 Libevent 支持 Linux、Unix 和 Windows。

    ◼ 统一事件源。Libevent 对 I/O 事件、信号和定时事件提供统一的处理。

    ◼ 线程安全。Libevent 使用 libevent_pthreads 库来提供线程安全支持。

    基于 Reactor 模式的实现。

    Libevent 使用模型

    使用Libevent的基本流程:

    根据一个简单的场景,了解其基本流程

    1)首先初始化 libevent 库,并保存返回的指针

    struct event_base * base = event_init();这个init底层其实调用的就是下面这个base_new

    等于struct event_base *base=event_base_new();常用

    1. // 创建event_base
    2. struct event_base* event_base_new(void)

    当我们创建一个event_base的时候,libevent会自动为我们选择最快的IO多路复用模型,Linux下一般会用epoll模型

    实际上这一步相当于初始化一个 Reactor 实例;在初始化 libevent 后,就可以注册事件了。

    2)初始化事件 event,设置回调函数和关注的事件

    evtimer_set(&ev, timer_cb, NULL);

    事实上这等价于调用 event_set(&ev, -1, 0, timer_cb, NULL);

    event_set 的函数原型是: 

    void event_set(struct event *ev, int fd, short event, void (*cb)(int, short, void *), void *arg) ev

    ev:执行要初始化的 event 对象;

    fd:该 event 绑定的“句柄”,对于信号事件,它就是关注的信号;

    event:在该 fd 上关注的事件类型,它可以是 EV_READ, EV_WRITE, EV_SIGNAL;

    cb:这是一个函数指针,当 fd 上的事件 event 发生时,调用该函数执行处理,它有三个参数, 调用时由 event_base 负责传入,按顺序,实际上就是 event_set 时的 fd, event 和 arg;

    arg:传递给 cb 函数指针的参数; 由于定时事件不需要 fd,并且定时事件是根据添加时(event_add)的超时值设定的,因此 这里 event 也不需要设置。

    这一步相当于初始化一个 event handler,在 libevent 中事件类型保存在 event 结构体中。 注意:libevent 并不会管理 event 事件集合,这需要应用程序自行管理;

    3)设置 event 从属的 event_base

    event_base_set(base, &ev);

    这一步相当于指明 event 要注册到哪个 event_base 实例上;

    上面的2 ,3其实都可以用下面的event_new,evtimer_new啊这些去初始化事件,总之set现在很少用了,set跟下面主要介绍的event_new一个道理,干的都是一个事情,参数都是一样的

    4)是正式的添加事件的时候了

    event_add(&ev, timeout);

    基本信息都已设置完成,只要简单的调用 event_add()函数即可完成,其中 timeout 是定时值;

    5)程序进入无限循环,等待就绪事件并执行事件处理

    我们上面说到 event_base是一组event的集合,我们也可以将event事件注册到这个集合中。当需要事件监听的时候,我们就需要对这个event_base进行循环。

    event_base_dispatch(base);//linux下一般就调用的io方法是epoll

    返回值:0 表示成功退出  -1 表示存在错误信息。

    6)上面event_new或者set了事件,下面就要释放

    1. // 创建event_free
    2.     void event_free(struct event *event)

    7)销毁event_base 

    1. // 释放event_base_free
    2. event_base_free(struct event_base* base);

     示例代码:

    1. struct event ev;
    2. struct timeval tv;
    3. /*
    4. struct timeval {
    5. long tv_sec; /* seconds */
    6. long tv_usec; /* and microseconds */
    7. };
    8. tv_sec 代表多少秒
    9. tv_usec 代表多少微秒 1000000 微秒 = 1
    10. */
    11. void time_cb(int fd, short event, void *argc)
    12. {
    13. printf("timer wakeup\n");
    14. event_add(&ev, &tv); // reschedule timer
    15. }
    16. int main()
    17. {
    18. struct event_base *base = event_init();
    19. tv.tv_sec = 10; // 10s period
    20. tv.tv_usec = 0;
    21. evtimer_set(&ev, time_cb, NULL);
    22. event_add(&ev, &tv);
    23. event_base_dispatch(base);
    24. }

    libevent 的核心,event 事件

    event_base是事件的集合,负责事件的循环,以及集合的销毁。而event就是event_base中的基本单元:事件。

    我们举一个简单的例子来理解事件。例如我们的socket来进行网络开发的时候,都会使用accept这个方法来阻塞监听是否有客户端socket连接上来,如果客户端连接上来,则会创建一个线程用于服务端与客户端进行数据的交互操作,而服务端会继续阻塞等待下一个客户端socket连接上来。客户端连接到服务端实际就是一种事件。

    1. 创建一个事件event

    struct event *event_new(struct event_base *base, evutil_socket_t fd,short events, event_callback_fn cb,void *arg);

    参数:

    • base:即event_base
    • fd:文件描述符。
    • events:event关心的各种条件(事件类型)。
    • cb:回调函数。
    • arg:用户自定义的数据,可以传递到回调函数中去。

    libevent是基于事件的,也就是说只有在事件到来的这种条件下才会触发当前的事件。例如:

    • fd文件描述符已准备好可写或者可读
    • fd马上就准备好可写和可读。
    • 超时的情况 timeout
    • 信号中断
    • 用户触发的事件
       

    Libevent 支持的事件类型

     可以看出事件类型可以使用“|”运算符进行组合,需要说明的是,信号和I/O事件不能同时设置;

    2. 释放event_free

    真正的释放event的内存。 

    void event_free(struct event *event);

    event_del 清理event的内存。这个方法并不是真正意义上的释放内存。

    当函数会将事件转为 非pending和非activing的状态。

    int event_del(struct event *event);

    3. 注册event

    该方法将用于向event_base注册事件。

    参数:ev 为事件指针;tv 为时间指针。当tv = NULL的时候则无超时时间。

    函数返回:0表示成功 -1 表示失败。

    int event_add(struct event *ev, const struct timeval *tv);

    tv时间结构例子: 

    1. struct timeval five_seconds = {5, 0};
    2. event_add(ev1, &five_seconds);

    或者像上面我最早示例的那种把结构体定义出来分别给他的成员赋值

    1. struct timeval tv;
    2. /*
    3. struct timeval {
    4. long tv_sec; /* seconds */
    5. long tv_usec; /* and microseconds */
    6. };
    7. tv_sec 代表多少秒
    8. tv_usec 代表多少微秒 1000000 微秒 = 1
    9. */
    10. tv.tv_sec = 5; // 5s period
    11. tv.tv_usec = 0;
    12. event_add(ev1, &tv);

    4. 信号事件 

    信号事件也可以对信号进行事件的处理。用法和event_new类似。只不过处理的是信号而已。

    1. // base --- event_base
    2. // signum --- 信号,例如 SIGHUP
    3. // callback --- 信号出现时调用的回调函数
    4. // arg --- 用户自定义数据
    5. evsignal_new(base, signum, cb, arg)
    6. //将信号 event 注册到 event_base
    7. evsignal_add(ev, tv)
    8. // 清理信号 event
    9. evsignal_del(ev)

    注意一些细节:

    每一个事件event都需要通过event_new初始化生成。event_new生成的事件是在堆上分配的内存。
    当一个事件通过event_add被注册到event_base上的时候,这个事件处于pending(等待状态),当只有有事件进来的时候,event才会被激活active状态,相关的回调函数就会被调用。
    persistent 如果event_new中的what参数选择了EV_PERSIST,则是持久的类型持久的类型调用玩回调函数后,会继续转为pending状态,就会继续等待事件进来。大部分情况下会选择持久类型的事件。
    而非持久的类型的事件,调用玩一次之后,就会变成初始化的状态。这个时候需要调用event_add 继续将事件注册到event_base上之后才能使用。
     

    示例:设置定时器,从键盘获得SIGINT信号,打印其对应数字

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void sig_fun(int fd,short ev,void* arg)
    10. {
    11. printf("sig=%d\n",fd);
    12. }
    13. void timeout(int fd,short ev,void *arg)
    14. {
    15. printf("time out\n");
    16. }
    17. int main()
    18. {
    19. struct event_base *base=event_base_new();//创建实例
    20. assert(base!=NULL);
    21. //创建事件
    22. // struct event*sig_ev=evsignal_new(base,SIGINT,sig_fun,NULL);//定义信号事件
    23. struct event * sig_ev = event_new(base,SIGINT,EV_SIGNAL|EV_PERSIST,sig_fun,NULL);
    24. assert(sig_ev!=NULL);
    25. event_add(sig_ev,NULL);//添加事件到实例中
    26. //struct event* ev_time=evtimer_new(base,timeout,NULL);//定义定时器事件
    27. struct event* ev_time = event_new(base,-1,EV_TIMEOUT|EV_PERSIST,timeout,NULL);
    28. struct timeval tv={5,0};
    29. event_add(ev_time,&tv);//添加事件到实例中
    30. //调用事件循环方法
    31. event_base_dispatch(base);//阻塞,底层调io方法
    32. event_free(ev_time);
    33. event_free(sig_ev);
    34. event_base_free(base);
    35. return 0;
    36. }

    Libevent 结构图

    使用libevent库去实现tcp服务器

    使用 libevent 库实现的 TCP 服务器代时,将监听 socket 和连接 socket 分别生成一个 Libevent 事件(指定其对应的回调函数),并将其添加到 Libevent 的一个 Base 中,执行事件 循环,检测事件发生。(客户端的代码与 select 部分客户端代码相同),代码示例如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. int socket_init();
    12. void recv_cb(int fd,short ev,void *arg)
    13. {
    14. struct event**c_ev=(struct event**)arg;
    15. if(ev&EV_READ)
    16. {
    17. char buff[128]={0};
    18. int n=recv(fd,buff,127,0);
    19. if(n<=0)
    20. {
    21. //移除事件 event_del
    22. event_free(*c_ev);
    23. free(c_ev);
    24. //关闭连接
    25. close(fd);
    26. printf("close one clinet\n");
    27. //释放事件占的空间 event_free->event_del
    28. return;
    29. }
    30. printf("recv(%d)=%s\n",fd,buff);
    31. send(fd,"ok",2,0);
    32. }
    33. }
    34. void accept_cb(int fd,short ev,void *arg)
    35. {
    36. struct event_base* base=(struct event_base*)arg;
    37. if(ev&EV_READ)
    38. {
    39. struct sockaddr_in caddr;
    40. int len=sizeof(caddr);
    41. int c=accept(fd,(struct sockaddr*)&caddr,&len);
    42. if(c<0)
    43. {
    44. return ;
    45. }
    46. printf("accept c=%d\n客户端ip:(%s)已连接;客户端端口port:(%d)已连接\n",c,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
    47. struct event**c_ev=(struct event**)malloc(sizeof(struct event*));
    48. if(c_ev==NULL)
    49. {
    50. return ;
    51. }
    52. *c_ev=event_new(base,c,EV_READ|EV_PERSIST,recv_cb,(void*)c_ev);
    53. if(*c_ev==NULL)
    54. {
    55. return ;
    56. }
    57. event_add(*c_ev,NULL);
    58. }
    59. }
    60. int main()
    61. {
    62. int sockfd=socket_init();
    63. assert(sockfd!=-1);
    64. struct event_base* base=event_base_new();
    65. assert(base!=NULL);
    66. struct event* sock_ev=event_new(base,sockfd,EV_READ|EV_PERSIST,accept_cb,base);
    67. assert(sock_ev!=NULL);
    68. event_add(sock_ev,NULL);
    69. event_base_dispatch(base);//事件循环 阻塞
    70. event_free(sock_ev);
    71. event_base_free(base);
    72. }
    73. int socket_init()
    74. {
    75. int sockfd=socket(AF_INET,SOCK_STREAM,0);
    76. if(sockfd==-1)
    77. {
    78. return sockfd;
    79. }
    80. struct sockaddr_in saddr;
    81. memset(&saddr,0,sizeof(saddr));
    82. saddr.sin_family=AF_INET;
    83. saddr.sin_port=htons(6000);
    84. saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    85. int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    86. if(res==-1){
    87. return -1;
    88. }
    89. res=listen(sockfd,5);
    90. if(res==-1){
    91. return -1;
    92. }
    93. return sockfd;
    94. }

  • 相关阅读:
    【数据结构C/C++】多维数组的原理、访问方式以及作用
    Node学习(二)01-node核心模块之path模块——extname()-获取文件后缀 & join()-智能拼接路径
    聊聊客户档案模型的设计与管理
    C++基础篇之缺省参数和函数重载
    一款轻量级事件驱动型应用程序框架
    Spring MVC(中)
    web大作业 静态网页 HTML+CSS+JavaScript橙色的时尚服装购物商城
    做好接口测试
    记录本地部署Stable-diffusion所依赖的repositories和一些插件
    ChatGPT:字符串操作问题——提取包含括号的字符串中的题干内容
  • 原文地址:https://blog.csdn.net/weixin_51609435/article/details/127532516