您的位置:首页 > 理论基础 > 计算机网络

alin的学习之路(Linux网络编程:八)(libevent库)

2020-08-08 21:16 1076 查看

alin的学习之路(Linux网络编程:八)(libevent库)

1. libevent库

1. 优点

  • 开源
  • 精简
  • 跨平台
  • 专注于网络通信 ——可以借助fd来进行 pipe、fifo 等通信

2. 下载安装

  • 下载地址:https://libevent.org/

  • 解压缩:tar zxvf libevent-2.1.8-stable.tar.gz

  • 源码包安装:

    如果有 README、readme 文件, 参考安装。
  • ./configure 检查安装环境,生成 makefile 文件。
  • make 生成 .o 文件 和 可执行文件。
  • sudo make install 将必要的资源, 拷贝至系统指定目录。
  • 测试是否安装成功:

      进入 sample 目录中, 编译 .c 文件, 运行。

    • 编译时,务必添加 指定库名的 选项 -levent

      gcc hello-world.c -o hello -levent
    • 安装成功后, 会在 系统的 /usr/local/lib 目录下,多出 libevent.so 和 libevent.a 库文件。

    • 如果运行 ./hello-world 可执行文件,报出如下错误: error while loading shared libraries: libevent-2.1.so.6: cannot open shared object file: No such file or directory

    • 解决方法

      将库目录添加到 /etc/ld.so.cache 文件中

      sudo vim /etc/ld.so.conf 文件,加入库所在路径即 /usr/local/lib/
      
      执行 sudo ldconfig -v ,该命令会重建/etc/ld.so.cache 文件
    • 编译成功后启动可执行程序,使用 nc 命令指定端口号为 9995,得到输出即为成功

    2. libevent 框架

    1. 创建base,使用 event_base_new()

      #include <event2/event.h>
      struct event_base *event_base_new(void);
      struct event_base *base = event_base_new();
    2. 创建事件对象 event

      // 常规事件 event
      event_new();
      
      // 带缓冲区的事件 bufferevent
      bufferevent_socket_new();
    3. 添加事件到 base上

      int event_add(struct event *ev, const struct timeval *tv);
    4. 循环监听事件满足

      int event_base_dispatch(struct event_base *base);
      event_base_dispatch(base);
    5. 释放 base 和事件对象

      void event_base_free(struct event_base *base);
      event_base_free(base);

    3. 常规事件

    1. 创建事件对象event

    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: 绑定到 event对象上的 文件描述符
    what: fd 对应的监听事件 (read、write、expcet)
    EV_READ  监听 一次 读事件
    EV_WRITE  监听 一次 写事件
    EV_PERSIST  持续触发。 结合 event_base_dispatch 函数使用。 保证持续循环。
    可以使用 “|” 连接。EV_PERSIST|EV_READ  或  EV_PERSIST|EV_WRITE
    cb: 一旦,事件满足监听条, 回调的函数。
    typedef void (*event_callback_fn)(evutil_socket_t fd, short, void *)
    arg: 回调函数的参数。
    
    返回值:成功创建的 事件对象

    2. 添加、销毁事件对象event

    // 添加事件
    int event_add(struct event *ev, const struct timeval *tv);ev: event_new() 的返回值。 ————【注意】不是 event_base !!!
    tv: 通常 NULL
    
    // 销毁事件
    int event_free(struct event *ev);
    成功: 0, 失败: -1
    

    3.编码实现读写 fifo

    read_fifo.c

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <event2/event.h>
    #include <strings.h>
    
    void read_cb(evutil_socket_t fd, short what, void* arg)
    {
    char buf[BUFSIZ];
    bzero(buf,sizeof(buf));
    read(fd, buf, sizeof(buf));
    printf("what:%s, read:%s\n",what & EV_READ ? "read YES" : "read NO", buf);
    sleep(1);
    }
    
    int main()
    {
    unlink("myfifo");
    mkfifo("myfifo", 0664);
    int fd = open("myfifo",O_RDONLY|O_NONBLOCK);
    
    //创建base
    struct event_base* base = event_base_new();
    
    //创建事件
    //struct event *event_new(struct event_base *base,evutil_socket_t fd,short
    //what,event_callback_fn cb;  void *arg);
    struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);
    
    //添加事件
    event_add(ev, NULL);
    
    //循环监听
    event_base_dispatch(base);
    
    //释放事件和base
    event_base_free(base);
    event_free(ev);
    
    return 0;
    }

    write_fifo.c

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <event2/event.h>
    #include <string.h>
    
    void read_cb(evutil_socket_t fd, short what, void* arg)
    {
    char buf[] = "hello libevent";
    write(fd, buf, strlen(buf)+1);
    sleep(1);
    }
    
    int main()
    {
    int fd = open("myfifo",O_WRONLY|O_NONBLOCK);
    
    //创建base
    struct event_base* base = event_base_new();
    
    //创建事件
    //struct event *event_new(struct event_base *base,evutil_socket_t fd,short
    //what,event_callback_fn cb;  void *arg);
    struct event* ev = event_new(base, fd, EV_WRITE|EV_PERSIST, read_cb, NULL);
    
    //添加事件
    event_add(ev, NULL);
    
    //循环监听
    event_base_dispatch(base);
    
    //释放事件和base
    event_base_free(base);
    event_free(ev);
    
    return 0;
    }

    4. 一个小的函数

    event_base_get_method(base); 函数可以返回当前base使用的多路IO转接方式

    4. 事件的未决和非未决态

    未决态:事件有资格被处理, 尚未被处理。
    非未决态:事件没有资格被处理。

    5. 带缓冲的事件bufferevent

    1. bufferevent 特性

    • 读缓冲:有数据 ——> 读回调函数会被调用 ——> 使用 bufferevent_read() (顶替 read() )——> 读数据
    • 写缓冲:使用 bufferevent_write() ( 顶替 write() ) ——> 向 写缓冲,写数据 ——> 写缓冲中,一旦有数据,会自动刷新(写出给对端)——> 写完, 回调函数会被调用。

    2. 创建、释放bufferevent

    • 创建 bufferevent
    struct bufferevent *bufferevent_socket_new(struct event_base *base,
    evutil_socket_t fd, enum bufferevent_options options);
    base: struct event_base  ———— “base底座”
    fd: 要封装到 bufferevent 内的 fd
    options:
    BEV_OPT_CLOSE_ON_FREE。 作用:释放 bufferevent对象时, 同时释放内部封装的资源。
    【注意】:bufferevent_socket_new函数内,没有指定 fd 对应的 回调函数。
    • 释放 bufferevent
    void bufferevent_free(struct bufferevent *bev);
    bev:bufferevent_socket_new 函数的返回值。

    3. 给bufferevent设置回调函数

    void bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb,
    bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb,
    void *cbarg );
    bufev: bufferevent_socket_new() 函数的返回值。
    readcb: 设置 bufferevent 读缓冲,对应的回调。
    如: read_cb(){ 用 bufferevent_read() 去读数据 }
    writecb: 设置 bufferevent 写缓冲,对应的回调。 ———— 通常 NULL
    如: write_cb(){ 给调用者,发送写成功的“通知” }
    eventcb: 设置事件回调。  ———— 通常 NULL
    对应: BEV_EVENT_CONNECTED:———— 用作客户端。
    cbarg:上述回调函数的参数。

    1. readcb对应的回调函数类型

    typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void * ctx);
    // 示例:
    void read_cb(struct bufferevent *bev, void *cbarg)
    {
    .....
    bufferevent_read(); ———— 顶替read()
    }
    // bufferevent_read 函数原型
    size_t bufferevent_read(struct bufferevent *bev,  void *buf,  size_t bufsize);

    2. writecb对应的回调函数类型

    typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void * ctx);
    // 示例:
    void write_cb(struct bufferevent *bev, void *cbarg)
    {
    .....
    数据写完了,这个函数才会被调用!!!
    }
    // bufferevent_write 函数原型
    int bufferevent_write(struct bufferevent *bev, const void *buf, size_t size);

    3. eventcb对应的回调函数类型

    typedef void (*bufferevent_event_cb)(struct bufferevent *bev,  short events, void *cbarg);
    // 示例:
    void event_cb(struct bufferevent *bev, short events, void *cbarg)
    {
    events: BEV_EVENT_CONNECTED:———— 用作客户端。
    .....
    当有状态、特殊状况产生时,该回调函数会被调用。
    }

    4. 启用、禁用bufferevent缓冲区

    • 默认: write 缓冲是 enable
    • read 缓冲 是 disable。 —— 不能直接使用 bufferevent_read(); 读取数。
  • 启用
  • void bufferevent_enable(struct bufferevent *bufev, short events); 启用缓冲区
    bufev: bufferevent_socket_new() 函数的返回值
    events: EV_READ、EV_WRITE、EV_READ|EV_WRITE  对应读和写缓冲区的启用
    • 禁用
    void bufferevent_disable(struct bufferevent *bufev, short events);

    5. 借助bufferevent实现C/S通信

    client端

    原来:socket(); connect(); 现在 socket() + bufferevent_socket_connect()

    int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);
    bev: bufferevent_socket_new() 函数的返回值
    address/addrlen:  等同与 connect() 参2、3

    server端

    原来:socket(); bind(); listen(); accept(); 现在:evconnlistener_new_bind()

    // 这一个函数,相当于 socket(); bind(); listen(); accept(); 的作用。
    #include <event2/listener.h>
    struct evconnlistener *evconnlistener_new_bind (
    struct event_base *base,
    evconnlistener_cb cb,
    void *ptr,
    unsigned flags,
    int backlog,
    const struct sockaddr *sa,
    int socklen);
    
    base:struct event_base  --- “底座”
    cb:回调函数
    一旦,该函数被回调,说明客户端已经成功连接。回调函数内部,应与客户端进行数据通信。
    ptr:回调函数的参数
    flags:LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE
    backlog:listen 函数的2参。 -1 表最大值。
    sa: 服务端自己的地址结构
    socklen: 服务端自己的地址结构的大小。
    返回值:成功创建的监听器。

    监听器 回调函数

    typedef void (*evconnlistener_cb) (
    struct evconnlistener *listener,
    evutil_socket_t sock,
    struct sockaddr *addr,
    int len,
    void *ptr);
    【注】:该回调函数,不由我们调用,是框架自动调用。因此,只需知晓参数含义即可。

    释放监听服务器

    void evconnlistener_free(struct evconnlistener *lev);

    6. 使用libevent库实现C/S模型

    1. 服务器端代码流程:

    1. 创建 base

    2. 绑定地址结构 用于 evconnlistener_new_bind() 函数创建监听器的传参

    3. 创建监听器 evconnlistener_new_bind()

      监听器的回调函数用于进行通信,进行通信要使用 bufferevent 来监听读写和事件,所以 bufferevent的创建要在监听器的回调函数中。以下是创建监听器时的回调函数:

      创建 bufferevent 事件 bufferevent_socket_new()

    4. 设置回调函数 bufferevent_setcb()

    5. 启动读回调 bufferevent_enable()

      读回调:使用 bufferevent_read() 读取客户端发送来的数据并处理,处理后使用 bufferevent_write() 写回
    6. 写回调:bufferevent_write() 函数执行完毕后调用,无卵用
    7. 事件回调:通过判断 events & BEV_EVENT_EOF 可知客户端断开连接,events & BEV_EVENT_ERROR 可知有其他错误发生,这时直接调用 bufferevent_free() 释放 bufferevent
  • event_base_dispatch() 启动循环监听

  • 释放监听器资源,释放base

  • 2. 客户端代码流程:

    1. socket() 函数创建 通信套接字cfd
    2. 设置服务器的地址结构 srv_addr
    3. 创建base
    4. 创建bufferevent事件 bufferevent_socket_new()
    5. 设置回调函数 bufferevent_setcb() ,启动读回调 bufferevent_enable() 读回调:使用 bufferevent_read() 接收服务器发送来的消息,并在屏幕打印
    6. 写回调:打印说明写事件完成,写回调的打印信息应该紧接在 bufferevent_write() 之后
    7. 事件回调:(BEV_EVENT_CONNECTED & events) 为真时表示与服务器连接成功,可以使用事件回调来释放bufferevent的资源
  • 与服务器建立连接,bufferevent_socket_connect()
  • 创建普通事件 event 来监听从屏幕获取字符串的事件,将获取的字符串通过 bufferevent_write() 发送给对端
  • 将 event 添加到 base中 event_add()
  • 启动循环监听 event_base_dispatch()
  • 释放base ,释放 event
  • 3. 代码实现

    服务器端

    #include <stdio.h>
    #include <unistd.h>
    #include <strings.h>
    #include <string.h>
    #include <ctype.h>
    #include <arpa/inet.h>
    #include <event2/bufferevent.h>
    #include <event2/listener.h>
    
    #define PORT 8000
    
    void read_cb(struct bufferevent *bev, void *cbarg)
    {
    char buf[BUFSIZ];
    int n = bufferevent_read(bev, buf, sizeof(buf));
    for(int i=0 ;i<n ;++i)
    {
    buf[i] = toupper(buf[i]);
    }
    write(STDOUT_FILENO, buf, n);
    bufferevent_write(bev, buf, n);
    sleep(1);
    }
    
    void write_cb(struct bufferevent *bev, void* cbarg)
    {
    printf("服务器数据已发送\n");
    }
    
    // 事件
    void event_cb(struct bufferevent *bev, short events, void *arg)
    {
    if (events & BEV_EVENT_EOF)
    {
    printf("connection closed\n");
    }
    else if(events & BEV_EVENT_ERROR)
    {
    printf("some other error\n");
    }
    bufferevent_free(bev);
    printf("buffevent 资源已经被释放...\n");
    }
    
    //用于客户端连接上后的通信
    void listen_cb(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr)
    {
    //char cltip[16];
    //printf("client accept ip:%s, port%d\n",
    //       inet_ntop(AF_INET, (struct sockaddr_in*)addr->sin_addr.s_addr, cltip, sizeof(cltip)),
    //       htons((struct sockaddr_in*)addr->sin_port));
    
    struct event_base* base = (struct event_base*)ptr;
    
    //创建bufferevent事件
    struct bufferevent* bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
    
    //设置回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
    
    //启动读写缓冲区
    bufferevent_enable(bev, EV_READ|EV_WRITE);
    }
    
    int main()
    {
    struct sockaddr_in srv_addr;
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(PORT);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //创建base
    struct event_base* base = event_base_new();
    
    //创建监听器
    struct evconnlistener* listener = evconnlistener_new_bind(base, listen_cb, (void*)base,
    LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
    
    //循环监听
    event_base_dispatch(base);
    
    //释放
    event_base_free(base);
    evconnlistener_free(listener);
    
    return 0;
    }

    客户端

    #include <stdio.h>
    #include <unistd.h>
    #include <strings.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <event2/bufferevent.h>
    #include <event2/listener.h>
    
    #define PORT 8000
    
    void read_cb(struct bufferevent* bev, void *cbarg)
    {
    char buf[BUFSIZ];
    int n = bufferevent_read(bev, buf, sizeof(buf));
    buf[n] = '\0';
    printf("服务器say: %s", buf);
    bufferevent_write(bev, buf, n);   //将数据再写回服务器,做出一直发送的效果
    }
    
    void write_cb(struct bufferevent* bev, void *cbarg)
    {
    printf("客户端写回调,没卵用!\n");
    }
    
    void event_cb(struct bufferevent *bev, short events, void *arg)
    {
    if (events & BEV_EVENT_EOF)
    {
    printf("connection closed\n");
    }
    else if(events & BEV_EVENT_ERROR)
    {
    printf("some other error\n");
    }
    else if(events & BEV_EVENT_CONNECTED)
    {
    printf("已经连接服务器...\\(^o^)/...\n");
    return;
    }
    // 释放资源
    bufferevent_free(bev);
    }
    
    void read_terminal(evutil_socket_t fd, short what, void *arg )
    {
    struct bufferevent* bev = (struct bufferevent*)arg;
    
    char buf[BUFSIZ];
    int n = read(fd, buf, sizeof(buf));
    bufferevent_write(bev, buf, n);
    sleep(1);
    }
    
    int main()
    {
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in srv_addr;
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, "127.0.0.1", &srv_addr.sin_addr.s_addr);
    
    //创建base
    struct event_base* base = event_base_new();
    
    struct bufferevent* bev;
    
    //创建befferevent事件
    bev = bufferevent_socket_new(base, cfd, BEV_OPT_CLOSE_ON_FREE);
    
    //设置回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
    
    //启用读缓冲区
    bufferevent_enable(bev, EV_READ|EV_WRITE);
    
    //连接服务器
    bufferevent_socket_connect(bev, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
    
    struct event* ev = event_new(base, STDOUT_FILENO, EV_READ|EV_PERSIST, read_terminal, bev);
    
    //添加事件
    /vent_add(ev, NULL);
    
    //循环监听
    event_base_dispatch(base);
    
    //释放bufferevent事件
    event_free(ev);
    //释放base
    event_base_free(base);
    
    return 0;
    }

    7. some small point

    有关管道读写注意:当写端关闭,读端还在读的时候,无论阻塞和非阻塞read都会返回0,表示对端关闭

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: