c++网络库Libevent万字详解

简介: libevent和libev都是c语言实现的异步事件库;通过注册异步事件,库检测事件触发,从而库根据发生事件的先后顺序,调用相应回调函数进行处理;事件包括:网络io事件,定时事件,信号事件;事件循环:等待并分发事件;用于管理事件;libevent 和 libev 主要封装了异步事件库与操作系统的交互;让用户不用关注平台的差异,只需着手事件的具体处理;创建事件处理框架event_base event_base_new()创建新事件event event_new()

服务端事件组成

  • 网络iO事件

    • Linux:epoll,poll,select
    • windows:iocp
    • mac:kqueue
  • 定时事件

    • 红黑树
    • 最小堆:二叉树,四叉树
    • 跳表
    • 时间轮
  • 信号事件

概述

libevent和libev都是c语言实现的异步事件库;通过注册异步事件,库检测事件触发,从而库根据
发生事件的先后顺序,调用相应回调函数进行处理;
事件包括:网络io事件,定时事件,信号事件;
事件循环:等待并分发事件;用于管理事件;
libevent 和 libev 主要封装了异步事件库与操作系统的交互;让用户不用关注平台的差异,只需着
手事件的具体处理;

主要接口

evuntil socket函数封装

  • evutil_make_socket_nonblocking(fd) 设置非阻塞
  • evutil_make_listen_socket_reuseable(fd) 设置端口复用
  • evutil_closesocket(fd) 关闭socket

1.event_base函数api

初始化libevent,相当于epoll的epoll_create()函数

// 头文件
  #include <event2/event.h>
  // 操作函数
  struct event_base * event_base_new(void;          //创建事件处理框架
  void event_base_free(struct event_base * base);    //释放事件处理框架

2.事件循环

event_base不停的检测委托的检测是实际是不是发生了, 如果发生了, event_base会调用对应的回调函数, 这个回调函数的用户委托检测事件的时候给的.

2.1设置事件循环

// 头文件
#include <event2/event.h>
int event_base_dispatch(struct event_base* base);     // 一般使用这个函数
/*
参数:
    - base: 通过 event_base_new(void)得到的
    */

2.2终止事件循环

// 头文件
  #include <event2/event.h>

  struct timeval {
   
      long    tv_sec;                    
      long    tv_usec;    // 微秒        
  };

  // 在 tv 时长之后退出循环, 如果这个参数为空NULL, 直接退出事件循环
  // 事件循环: 检测对应的事件是否被触发了
  // 如果事件处理函数正在被执行, 执行完毕之后才终止
  int event_base_loopexit(struct event_base * base, const struct timeval * tv);

// 马上终止
  int event_base_loopbreak(struct event_base * base);

3.事件

3.1事件的基本操作

事件的创建:event_new

创建事件,初始化event和相应的回调函数

  #include <event2/event.h>

//要检测事件   what:
  #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_socket_t,shortvoid *);
/*
      参数:
          - 第一个参数: event_new的第二个参数
          - 第二个参数: 实际触发的事件
          - 第三个参数: event_new的最后一个参数
 */
// 创建事件
  struct event* event_new(struct event_base * base,evutil_socket_t fd,
                            short what,event_callback_fn cb,void * arg);
/*
      参数:
          - base: event_base_new得到的
          - fd: 文件描述符, 检测这个fd对应的事件
          - what: 监测fd的什么事件 
          - cb: 回调函数, 当前检测的事件被触发, 这个函数被调用
          - arg: 给回调函数传参
          */

事件的释放

#include <event2/event.h>
// 释放事件资源
  void event_free(struct event * event);

事件的添加、删除

事件被new出之后, 不能直接被event_base进行检测event_add之后event_base就可以对事件进行检测

注册事件,包括时间事件;相当于 epoll_ctl;

#include <event2/event.h>
int  event_add(struct event * ev,const  struct timeval * tv);
/*
      参数: tv-> 超时时间, 
          如果这个值> 0, 比如 == 3
          检测fd的读事件, 在三秒之内没有触发该事件 -> 超时-> 超时之后, 事件对应的回调函数会被强制调用
          如果该参数为NULL, 不会做超时检测
  */
// 删除要检测的事件
  int  event_del(struct event * ev);

3.2 事件的优先级设置

// 头文件
  #include <event2/event.h>

  // EVENT_MAX_PRIORITIES == 256     最大的初始化事件优先级

  int event_base_priority_init(struct event_base * base,int n_priorities);
/*
      参数:
          - n_priorities: 等级的个数, 假设 == 6
             也就是说有6个等级: 0,1,2,3,4,5, 0优先级最高
  */
  // 获取当前可用的等的个数
  int event_base_get_npriorities(struct event_base * base);
  // 给事件设置等级
  int event_priority_set(struct event *event, int priority);
/*
      参数:
          - event: 创建的事件
          - priority: 要设置的等级
          */

4.带缓冲区的事件

提供 bufferevent,进一步提供管理读写事件(包括连接断开事件),以及读写数据缓冲;

  • bufferevent_socket_new
  • bufferevent_socket_connect
  • bufferevent_free
  • bufferevent_setcb
  • bufferevent_enable
  • bufferevent_disable
  • bufferevent_get_input
  • bufferevent_get_output
  • bufferevent_write
  • bufferevent_write_buffer
  • bufferevent_read
  • bufferevent_read_buffer

    bufferevent封装了底层的read/send函数,以及读写缓冲区,并提供读/写的回调函数

4.1创建/释放基于套接字的bufferevent

struct bufferevent *bufferevent_socket_new(
      struct event_base *base,
      evutil_socket_t fd,
      enum bufferevent_options options
  ); 
/*
      参数:
          - base: 处理事件的
          - fd: 通信的文件描述符
          - options: BEV_OPT_CLOSE_ON_FREE -> 自动释放底层资源
      返回值: 得到带缓冲区的事件变量
  */
// 释放资源
  void bufferevent_free(struct bufferevent *bev);

4.2在bufferevent上启动连接服务器函数 bufferevent_socket_connect

  • 如果还没有为bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的
    • 例子:bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
  • 如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
  • 连接完成之前可以向输出缓冲区添加数据。
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); 
/*
      参数:
          - bev: 带缓冲区的事件, 里边封装 fd
          - address: 要连接的服务器的IP和端口
          - addrlen: address结构体的内存大小
          */

4.3bufferevent读写缓冲区回调操作 bufferevent_setcb

//读、写事件触发之后的回调函数格式
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
/*
      参数:
          - bev: 从bufferevent_setcb函数中的第一个参数传入的
          - ctx: 从bufferevent_setcb函数中的最后第一个参数传入的
 */
//特殊事件的回调函数格式         
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
/*
      参数:
          - bev: 从bufferevent_setcb函数中的第一个参数传入的
          - events: 可以检测到的事件
              EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
              BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
              BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用 EVUTIL_SOCKET_ERROR()。
              BEV_EVENT_TIMEOUT:发生超时。
              BEV_EVENT_EOF:遇到文件结束指示。
              BEV_EVENT_CONNECTED:请求的连接过程已经完成 
 */
void bufferevent_setcb(struct bufferevent *bufev, 
                       bufferevent_data_cb readcb,         
                       bufferevent_data_cb writecb, 
                       bufferevent_event_cb eventcb, void *cbarg
);
/*
      参数:
          - bufev: 带缓冲区的事件
          - readcb: 读事件触发之后的回调函数
          - writecb: 写事件触发之后的回调函数
          - eventcb: 特殊事件的回调函数
          - cbarg: 给回调函数传参
          */

4.4 禁用、启用缓冲区

可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。
没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。

写缓冲区默认是有效的,读缓冲区默认无效

// 设置某个事件有效
  void bufferevent_enable(struct bufferevent *bufev, short events); 

// 设置某个事件无效
  void bufferevent_disable(struct bufferevent *bufev, short events);

// 获取缓冲区对应的有效事件
  short bufferevent_get_enabled(struct bufferevent *bufev);

4.5操作bufferevent中的数据 bufferevent_write bufferevent_read

// 向bufferevent的输出缓冲区添加数据
  int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);

// 从bufferevent的输入缓冲区移除数据
  size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

5.链接监听器

提供了监听和接受 tcp 连接的方法

  • evconnlistener_new
  • evconnlistener_new_bind
  • evconnlistener_free

5.1创建和释放evconnlistener

#include <event2/listener.h> 

//回调函数格式
  typedef void (*evconnlistener_cb)(
              struct evconnlistener *listener,   
              evutil_socket_t sock,   
              struct sockaddr *addr, 
              int len, 
              void *ptr
  ); 
/*
      参数:
          - listener: evconnlistener_new_bind 返回的地址
          - sock: 用于通信的fd
          - addr: 客户端的地址信息
          - ptr: 外部传进来的参数, evconnlistener_new_bind的第三个参数
          */

// 创建监听的套接字, 绑定, 设置监听, 等待并接受连接请求
  struct evconnlistener *evconnlistener_new_bind(
              struct event_base *base,    
              evconnlistener_cb cb,            // 接受新连接之后的回调函数
              void *ptr,                      // 回调函数参数
              unsigned flags, 
              int backlog,                   // listen()中的第二参数,最多的监听数量,小于128的整数
              const struct sockaddr *sa,     // 本地的IP和端口
              int socklen                   // struct sockaddr结构体大小
  );
/*
      参数:
          - flags:
              LEV_OPT_CLOSE_ON_FREE: 自动关闭底层套接字
              LEV_OPT_REUSEABLE: 设置端口复用
              */
// 释放
  void evconnlistener_free(struct evconnlistener *lev);

5.2启用和禁用 evconnlistener

  #include <event2/listener.h> 

  int evconnlistener_disable(struct evconnlistener *lev);
  int evconnlistener_enable(struct evconnlistener *lev);

5.3调整 evconnlistener 的回调函数

#include <event2/listener.h>

void evconnlistener_set_cb(struct evconnlistener *lev, evconnlistener_cb cb, void *arg);

6.示例

服务端server示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>

// read缓冲区的回调
void read_cb(struct bufferevent* bev, void* arg)
{
   
    // 读缓冲区的数据
    char buf[128];
    int len = bufferevent_read(bev, buf, sizeof(buf));
    printf("read data: len = %d, str = %s\n", len, buf);

    // 回复数据
    bufferevent_write(bev, buf, len);
    printf("数据发送完毕...\n");
}

// 写缓冲区的回调
// 调用的时机: 写缓冲区中的数据被发送出去之后, 该函数被调用
void write_cb(struct bufferevent* bev, void* arg)
{
   

    printf("arg value: %s\n", (char*)arg);
    printf("数据已经发送完毕...xxxxxxxxxxxx\n");
}

// 事件回调
void events_cb(struct bufferevent* bev, short event, void* arg)
{
   
    if(event & BEV_EVENT_ERROR)
    {
   
        printf("some error happened ...\n");
    }
    else if(event & BEV_EVENT_EOF)
    {
   
        printf("server disconnect ...\n");
    }
    // 终止连接
    bufferevent_free(bev);
}

// 接收连接请求之后的回调
void listener_cb(struct evconnlistener *listener,   
                evutil_socket_t sock,   
                struct sockaddr *addr, 
                int len, 
                void *ptr)
{
   
    // 通信
    // 使用带缓冲区的事件对sock进行包装
    struct event_base* base = (struct event_base*)ptr;
    struct bufferevent* bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
    // 设置回调
    bufferevent_setcb(bev, read_cb, write_cb, events_cb, NULL);
    bufferevent_enable(bev, EV_READ);
}

int main()
{
   
    struct event_base * base = event_base_new();
    // 1. 创建监听的套接字, 绑定, 设置监听, 等待并接受连接请求
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9898);    // 服务器监听的端口
    addr.sin_addr.s_addr = INADDR_ANY;
    struct evconnlistener* listener = evconnlistener_new_bind(base, listener_cb, base, 
                                                               LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
                                                               100, (struct sockaddr*)&addr, sizeof(addr)); 

    event_base_dispatch(base);
    evconnlistener_free(listener);
    event_base_free(base);


    return 0;
}

客户端client示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>

// read缓冲区的回调
void read_cb(struct bufferevent* bev, void* arg)
{
   
    printf("arg value: %s\n", (char*)arg);
    // 读缓冲区的数据
    char buf[128];
    int len = bufferevent_read(bev, buf, sizeof(buf));
    printf("read data: len = %d, str = %s\n", len, buf);

    // 回复数据
    bufferevent_write(bev, buf, len);
    printf("数据发送完毕...\n");
}

// 写缓冲区的回调
// 调用的时机: 写缓冲区中的数据被发送出去之后, 该函数被调用
void write_cb(struct bufferevent* bev, void* arg)
{
   

    printf("arg value: %s\n", (char*)arg);
    printf("数据已经发送完毕...xxxxxxxxxxxx\n");
}

// 事件回调
void events_cb(struct bufferevent* bev, short event, void* arg)
{
   
    if(event & BEV_EVENT_ERROR)
    {
   
        printf("some error happened ...\n");
    }
    else if(event & BEV_EVENT_EOF)
    {
   
        printf("server disconnect ...\n");
    }
    // 终止连接
    bufferevent_free(bev);
}

void send_msg(evutil_socket_t fd, short ev, void * arg)
{
   
    // 将写入到终端的数据读出
    char buf[128];
    int len = read(fd, buf, sizeof(buf));
    // 发送给服务器
    struct bufferevent* bev = (struct bufferevent*)arg;
    bufferevent_write(bev, buf, len);
}

int main()
{
   
    struct event_base * base = event_base_new();
    // 1. 创建通信的套接字
    struct bufferevent* bufev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);

    // 2. 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9898);    // 服务器监听的端口
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
    // 这个函数调用成功, == 服务器已经成功连接
    bufferevent_socket_connect(bufev, (struct sockaddr*)&addr, sizeof(addr));

    // 3. 通信
    // 给bufferevent的缓冲区设置回调
    bufferevent_setcb(bufev, read_cb, write_cb, events_cb, (void*)"hello, world");
    bufferevent_enable(bufev, EV_READ);

    // 创建一个普通的输入事件
    struct event* myev = event_new(base, STDIN_FILENO, EV_READ|EV_PERSIST, send_msg, bufev);
    event_add(myev, NULL);


    event_base_dispatch(base);
    event_free(myev);
    event_base_free(base);


    return 0;
}

7.总结

处理不带缓冲区的事件:

  1. 创建事件处理框架event_base event_base_new()
  2. 创建新事件event event_new()
  3. 将事件添加到事件处理框架event_base上 event_add()
  4. 启动事件循环检测 event_base_dispatch()
  5. 循环结束之后释放资源 event_base_free() 、event_free()

处理带缓冲区的事件:

创建事件处理框架event_base event_base_new()

2、服务器端:

  1. 创建连接监听器(在回调函数得到fd) evconnlistener_new_bind()
  2. 将通信fd包装 bufferevent_socket_new()
  3. 使用bufferevent通信:给bufferevent读写缓冲区设置回调函数 bufferevent_setcb()
  4. 设置读缓冲区可用 bufferevent_enable()
  5. 对缓冲区数据操作 bufferevent_write()、bufferevent_rea()

3、客户端:

  1. 创建通信用的fd并且使用bufferevent包装 bufferevent_socket_new()
  2. 连接服务器 bufferevent_socket_connect()
  3. 使用bufferevent通信:给bufferevent读写缓冲区设置回调函数 bufferevent_setcb()
  4. 设置读缓冲区可用 bufferevent_enable()
  5. 对缓冲区数据操作 bufferevent_write()、bufferevent_read()
目录
相关文章
|
1月前
|
NoSQL 网络协议 Linux
Redis的实现一:c、c++的网络通信编程技术,先实现server和client的通信
本文介绍了使用C/C++进行网络通信编程的基础知识,包括创建socket、设置套接字选项、绑定地址、监听连接以及循环接受和处理客户端请求的基本步骤。
45 6
|
29天前
|
算法 C++ 容器
C++标准库(速查)总结
C++标准库(速查)总结
58 6
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
91 10
|
29天前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
67 5
|
29天前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
51 1
|
2月前
|
编译器 API C语言
超级好用的C++实用库之跨平台实用方法
超级好用的C++实用库之跨平台实用方法
39 6
|
2月前
|
安全 C++
超级好用的C++实用库之环形内存池
超级好用的C++实用库之环形内存池
45 5
|
2月前
|
缓存 网络协议 Linux
超级好用的C++实用库之套接字
超级好用的C++实用库之套接字
32 1
|
2月前
|
存储 算法 安全
超级好用的C++实用库之sha256算法
超级好用的C++实用库之sha256算法
88 1
|
1月前
|
存储 监控 NoSQL
Redis的实现二: c、c++的网络通信编程技术,让服务器处理多个client
本文讨论了在C/C++中实现服务器处理多个客户端的技术,重点介绍了事件循环和非阻塞IO的概念,以及如何在Linux上使用epoll来高效地监控和管理多个文件描述符。
25 0