libevent库介绍--事件和数据缓冲

首先在学习libevent库的使用前,咱们还要从基本的了解开始,已经熟悉了epoll以及reactor,而后从event_base学习,依次学习事件event、数据缓冲Bufferevent和数据封装evBuffer等,再结合具体的几个实例来了解libevent库的一些基本使用,有助于咱们理解它的一些内部实现(因为以前我已经写过一篇epoll反应堆模型的,因此这里就再也不介绍,直接从event_base开始介绍)。react

 

libevent特色:后端

  • 事件驱动,高性能;
  • 轻量级,专一于网络;
  • 跨平台,支持Windows、Linux、Mac OS等;
  • 支持多种    I/O多路复用技术,    epoll、poll、dev/poll、select    和kqueue    等;
  • 支持    I/O,定时器和信号等事件;

libevent组件:安全

  • evutil:用于抽象不一样平台网络实现差别的通用功能。
  • event和event_base:libevent的核心,为各类平台特定的、基于事件的非阻塞 IO后端提供抽象API,让程序能够知道套接字什么时候已经准备好,能够读或者写,而且处理基本的超时功能,检测OS信号。
  • bufferevent:为libevent基于事件的核心提供使用更方便的封装。除了通知程序套接字已经准备好读写以外,还让程序能够请求缓冲的读写操做,能够知道什么时候IO已经真正发生。( bufferevent接口有多个后端, 能够采用系统可以提供的更快的非阻塞 IO方式,如Windows中的IOCP。)
  • evbuffer:在bufferevent层之下实现了缓冲功能,而且提供了方便有效的访问函数

event_base

  使用 libevent 函数以前须要分配一个或者多个 event_base 结构体。每一个event_base 结构 体持有一个事件集合,能够检测以肯定哪一个事件是激活的。若是设置 event_base 使用锁,则能够安全地在多个线程中访问它 。然而,其事件循环只能 运行在一个线程中。若是须要用多个线程检测 IO,则须要为每一个线程使用一个 event_base。
  event_base_new()函数分配而且返回一个新的具备默认设置的 event_base。函数会检测环境变量,返回一个到 event_base 的指针。若是发生错误,则返回 NULL。选择各类方法时,函数会选择 OS 支持的最快方法。
       struct event_base *event_base_new(void);服务器

  使用完 event_base 以后,使用 event_base_free()进行释放。
     void event_base_free(struct event_base *base);
       注意:这个函数不会释放当前与 event_base 关联的任何事件,或者关闭他们的套接字 ,或 者释听任何指针网络

   一旦有了一个已经注册了某些事件的 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);socket

   默认状况下,event_base_loop()函数运行 event_base 直到其中没有已经注册的事件为止。执行循环的时候 ,函数重复地检查是否有任何已经注册的事件被触发 (好比说,读事件 的文件描述符已经就绪,能够读取了;或者超时事件的超时时间即将到达)。若是有事件被触发,函数标记被触发的事件为 “激活的”,而且执行这些事件。
        在 flags 参数中设置一个或者多个标志就能够改变 event_base_loop()的行为。若是设置了 EVLOOP_ONCE ,循环将等待某些事件成为激活的 ,执行激活的事件直到没有更多的事件能够执行,然会返回。若是设置了 EVLOOP_NONBLOCK,循环不会等待事件被触发: 循环将仅仅检测是否有事件已经就绪,能够当即触发,若是有,则执行事件的回调。
        完成工做后,若是正常退出, event_base_loop()返回0;若是由于后端中的某些未处理 错误而退出,则返回 -1。 函数

   int event_base_dispatch(struct event_base *base);
      event_base_dispatch ()等同于没有设置标志的 event_base_loop ( )。因此event_base_dispatch ()将一直运行,直到没有已经注册的事件了,或者调用      event_base_loopbreak()或者 event_base_loopexit()为止。oop

     若是想在移除全部已注册的事件以前中止活动的事件循环,能够调用两个稍有不一样的函数 。
      int event_base_loopexit(struct event_base *base,const struct timeval *tv);
      int event_base_loopbreak(struct event_base *base);
      event_base_loopexit()让 event_base 在给定时间以后中止循环。若是 tv 参数为NULL, event_base 会当即中止循环,没有延时。
      若是 event_base 当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完全部激活事件的回调之才退出。
      event_base_loopbreak ()让 event_base 当即退出循环。它与event_base_loopexit (base,NULL)的不一样在于,若是 event_base 当前正在执行激活事件的回调 ,它将在执行完当前正在处理的事件后当即退出。性能

综上所述,event_base处理过程主要以下:学习

  1. 调用event_base_new()建立一个event_base
  2. 注册了某些事件的 event_base
  3. 调用event_base_loop()或者event_base_dispatch()函数,循环等待事件而且通知事件的发生
  4. 调用event_base_loopexit()或者event_base_loopbreak()移除全部已注册的事件以前中止活动的事件循环
  5. 使用完 event_base 以后,使用event_base_free()进行释放

 

事件event

  libevent 的基本操做单元是事件。每一个事件表明一组条件的集合,这些条件包括:

  • 文件描述符已经就绪,能够读取或者写入
  • 文件描述符变为就绪状态,能够读取或者写入(仅对于边沿触发 IO)
  • 超时事件
  • 发生某信号
  • 用户触发事件

  全部事件具备类似的生命周期。调用 libevent 函数设置事件而且关联到event_base 以后, 事件进入“已初始化(initialized)”状态。此时能够将事件添加到event_base 中,这使之进入“未决(pending)”状态。在未决状态下,若是触发事件的条件发生(好比说,文件描述 符的状态改变,或者超时时间到达 ),则事件进入“激活(active)”状态,(用户提供的)事件回调函数将被执行。若是配置为“持久的(persistent)”,事件将保持为未决状态。不然, 执行完回调后,事件再也不是未决的。删除操做可让未决事件成为非未决(已初始化)的 ; 添加操做可让非未决事件再次成为未决的。

建立事件

    使用 event_new()接口建立事件。
    #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, short, void *); 
    struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event_callback_fn cb,void *arg);
    void event_free(struct event *event);

    event_new()试图分配和构造一个用于 base 的新的事件。 
      

what 参数是上述标志的集合。若是 fd 非负,则它是将被观察其读写事件的文件。事件被激活时, libevent 将调用 cb 函数,传递这些参数:文件描述符 fd,表示全部被触发事件的位字段 ,以及构造事件时的 arg参数。发生内部错误,或者传入无效参数时, event_new()将返回 NULL。全部新建立的事件都处于已初始化和非未决状态 ,调用 event_add()可使其成为未决的。

释放事件

    要释放事件,调用 event_free()。对未决或者激活状态的事件调用 event_free()是安全的:在释放事件以前,函数将会使事件成为非激活和非未决的。

事件标志

    EV_TIMEOUT:这个标志表示某超时时间流逝后事件成为激活的。构造事件的时候,EV_TIMEOUT标志是 被忽略的:能够在添加事件的时候设置超时 ,也能够不设置。超时发生时,回调函数的 what 参数将带有这个标志。
    EV_READ:表示指定的文件描述符已经就绪,能够读取的时候,事件将成为激活的。
    EV_WRITE:表示指定的文件描述符已经就绪,能够写入的时候,事件将成为激活的。
    EV_SIGNAL:用于实现信号检测,请看下面的 “构造信号事件”节。
    EV_PERSIST:表示事件是“持久的”
    EV_ET:表示若是底层的 event_base 后端支持边沿触发事件,则事件应该是边沿触发的。

事件的未决和非未决 

    设置未决事件:在非未决的事件上调用 event_add()将使其在配置的 event_base 中成为未决的。
    int event_add(struct event *ev, const struct timeval *tv);
    若是 tv 为 NULL,添加的事件不会超时。不然, tv 以秒和微秒指定超时值。
    若是对已经未决的事件调用 event_add(),事件将保持未决状态,并在指定的超时时间被从新调度。

    设置非未决事件:对已经初始化的事件调用 event_del()将使其成为非未决和非激活的。若是事件不是未决的或者激活的,调用将没有效果。
    int event_del(struct event *ev);
      若是在事件激活后,其回调被执行前删除事件,回调将不会执行。

一次触发事件 

    若是不须要屡次添加一个事件,或者要在添加后当即删除事件,而事件又不须要是持久的 , 则可使用 event_base_once()
    int event_base_once(struct event_base *, evutil_socket_t, short,void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
      除了不支持 EV_SIGNAL 或者 EV_PERSIST 以外,这个函数的接口与 event_new()相同。 安排的事件将以默认的优先级加入到 event_base并执行。回调被执行后,libevent内部将 会释放 event 结构。

事件总结:1.建立事件

     2.事件的未决和非未决   设置未决事件调用event_add()    设置非未决事件调用event_del()

     3.触发事件 一次触发事件调用event_base_once()

event代码示例

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <event2/event.h>

//typedef void (*event_callback_fn)(evutil_socket_t, short, void *)
void callback_func(evutil_socket_t fd, short event, void *arg)
{
    char buf[256] = {0};
    int len = 0;

    printf("fd = %d, event = %d", fd, event);

    len = read(fd, buf, sizeof(buf));

    if (len == -1) {
        perror("read");
        return;
    } else if (len == 0) {
        perror("remote close fd");
        return;
    } else {
        buf[len] = '\0';
        printf("read buf=[%s]\n", buf);
    }
    return; 
}

int main(int argc, char *argv[])
{
    int fd;
    struct event_base * base = NULL;
    struct event *evfifo = NULL;
    const char *fifo = "event.fifo";

    unlink(fifo);

    if (mkfifo(fifo, 0644) == -1) {
        perror("mkfifo");
        exit(1);
    }

    fd = open(fifo, O_RDONLY);
    if (fd == -1) {
        perror("open socket error");
        exit(1);
    }

    //建立一个event_base 
    base = event_base_new(); 

    //将fd 绑定到一个事件中  同时绑定一个读的回调函数
    //此时evfifo事件是一个初始化状态的事件
    evfifo = event_new(base, fd, EV_READ|EV_PERSIST, callback_func, NULL); 

    //将此事件从 初始化--->未决
    event_add(evfifo, NULL);

    //循环等待事件监控
    event_base_dispatch(base);

    event_free(evfifo);
    event_base_free(base);

    return 0;
}    

client.c 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd = 0;

    const char *str = "hello event";

    fd = open("event.fifo", O_RDWR);
    if (fd == -1) {
        perror("open event.fifo error");
        exit(1);
    }

    while (1) {
       write(fd, str, strlen(str));
       sleep(1);
    }

    close(fd);
    return 0;
}

 

    执行以下命令进行编译(前提已经安装libevent库)

    gcc server.c -l event -o server

    gcc client.c -l event -o client

    编译好后启动server和client,结果以下:

    

 

数据缓冲bufferevent  

不少时候,除了响应事件以外,应用还但愿作必定的数据缓冲。好比说,写入数据的时候 ,一般的运行模式是:    

      1.决定要向链接写入一些数据,把数据放入到缓冲区中
      2.等待链接能够写入
      3.写入尽可能多的数据
      4.记住写入了多少数据,若是还有更多数据要写入,等待链接再次能够写入
  bufferevent 由一个底层的传输端口(如套接字 ),一个读取缓冲区和一个写入缓冲区组成。与一般的事件在底层传输端口已经就绪,能够读取或者写入的时候执行回调不一样的是,bufferevent 在读取或者写入了足够量的数据以后调用用户提供的回调。
  bufferevent和evbuffer
    每一个 bufferevent 都有一个输入缓冲区和一个输出缓冲区 ,它们的类型都是“structevbuffer”。 有数据要写入到 bufferevent 时,添加数据到输出缓冲区 ;bufferevent 中有数据供读取的时候,从输入缓冲区抽取(drain)数据。 evbuffer 接口支持不少种操做。

  水位

    每一个 bufferevent 有两个数据相关的回调:一个读取回调和一个写入回调。默认状况下,从底层传输端口读取了任意量的数据以后会调用读取回调 ;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。经过调整 bufferevent 的读取和写入 “水位 (watermarks )”能够覆盖这些函数的默认行为。
    每一个 bufferevent 有四个水位:
        读取低水位 : 读取操做使得输入缓冲区的数据量在此级别或者更高时 ,读取回调将被调用。默认值为 0,因此每一个读取操做都会致使读取回调被调用。
        读取高水位 : 输入缓冲区中的数据量达到此级别后, bufferevent 将中止读取,直到输入缓冲区中足够量的数据被抽取 ,使得数据量低于此级别 。默认值是无限 ,因此永远不会由于输入缓冲区的大小而中止读取。
        写入低水位 : 写入操做使得输出缓冲区的数据量达到或者低于此级别时 ,写入回调将被调用。默认值是 0,因此只有输出缓冲区空的时候才会调用写入回调。
        写入高水位 : bufferevent 没有直接使用这个水位。它在 bufferevent 用做另一 个 bufferevent 的底层传输端口时有特殊意义。

  回调

     默认状况下,bufferevent 的回调在相应的条件发生时当即被执行 。在依赖关系复杂的状况下 ,这种当即调用会制造麻烦 。好比说,假如某个回调在 evbuffer A 空的时候向其中移入数据 ,而另外一个回调在
evbuffer A 满的时候从中取出数据。这些调用都是在栈上发生的,在依赖关系足够复杂的时候,有栈溢出的风险。
        要解决此问题,能够请求 bufferevent(或者 evbuffer)延迟其回调。条件知足时,延迟回调不会当即调用,而是在 event_loop()调用中被排队,而后在一般的事件回调以后执行.

  bufferevent 选项标志

      BEV_OPT_CLOSE_ON_FREE : 释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等
           BEV_OPT_THREADSAFE : 自动为 bufferevent 分配锁,这样就能够安全地在多个线程中使用 bufferevent。
           BEV_OPT_DEFER_CALLBACKS : 设置这个标志时, bufferevent 延迟全部回调.
           BEV_OPT_UNLOCK_CALLBACKS : 默认状况下,若是设置 bufferevent 为线程安全 的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent 在执行回调的时候不进行锁定。

  建立基于套接字的bufferevent

     可使用 bufferevent_socket_new()建立基于套接字的 bufferevent。
      struct bufferevent *bufferevent_socket_new(struct event_base *base,evutil_socket_t fd,enum bufferevent_options options);
        base 是 event_base,options 是表示 bufferevent 选项(BEV_OPT_CLOSE_ON_FREE 等) 的位掩码, fd是一个可选的表示套接字的文件描述符。若是想之后设置文件描述符,能够设置fd为-1。成功时函数返回一个 bufferevent,失败则返回 NULL。

     在bufferevent上启动连接
      int bufferevent_socket_connect(struct bufferevent *bev,struct sockaddr *address, int addrlen);
        address 和 addrlen 参数跟标准调用 connect()的参数相同。若是尚未为bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,而且设置为非阻塞的。
        若是已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知libevent 套接字还未链接,直到链接成功以前不该该对其进行读取或者写入操做。链接完成以前能够向输出缓冲区添加数据。若是链接成功启动,函数返回 0;若是发生错误则返回 -1。

  释放bufferevent操做
       void bufferevent_free(struct bufferevent *bev);

     这个函数释放 bufferevent。bufferevent 内部具备引用计数,因此,若是释放 时还有未决的延迟回调,则在回调完成以前 bufferevent 不会被删除。
       若是设置了 BEV_OPT_CLOSE_ON_FREE 标志,而且 bufferevent 有一个套接字或者底层 bufferevent 做为其传输端口,则释放 bufferevent 将关闭这个传输端口。

  操做回调和启用/禁用

     typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
          typedef void (*bufferevent_event_cb)(struct bufferevent *bev,short events, void *ctx);
          void bufferevent_setcb(struct bufferevent *bufev,bufferevent_data_cb readcb, bufferevent_data_cb writecb,bufferevent_event_cb eventcb, void *cbarg);
          void bufferevent_getcb(struct bufferevent *bufev,bufferevent_data_cb *readcb_ptr,bufferevent_data_cb *writecb_ptr,bufferevent_event_cb *eventcb_ptr,void **cbarg_ptr);

     bufferevent_setcb()函数修改 bufferevent 的一个或者多个回调 。readcb、writecb和eventcb函数将分别在已经读取足够的数据 、已经写入足够的数据 ,或者发生错误时被调用 。每一个回调函数的第一个参数都是发生了事件的bufferevent ,最后一个参数都是调用bufferevent_setcb()时用户提供的 cbarg 参数:能够经过它向回调传递数据。事件回调 的 events 参数是一个表示事件标志的位掩码:请看前面的 “回调和水位”节。要禁用回调,传递 NULL 而不是回调函数 。注意:bufferevent 的全部回调函数共享单个 cbarg, 因此修改它将影响全部回调函数。
        接口
          void bufferevent_enable(struct bufferevent *bufev, short events);
          void bufferevent_disable(struct bufferevent *bufev, short events);
          short bufferevent_get_enabled(struct bufferevent *bufev);
        
          能够启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。没有必要在输出缓冲区空时禁用写入事件: bufferevent 将自动中止写入,而后在有数据等 待写入时从新开始。相似地,没有必要在输入缓冲区高于高水位时禁用读取事件 :bufferevent 将自动中止读取, 而后在有空间用于读取时从新开始读取。默认状况下,新建立的 bufferevent 的写入是启用的,可是读取没有启用。 能够调用bufferevent_get_enabled()肯定 bufferevent 上当前启用的事件。

  操做bufferevent中的数据
          经过bufferevent获得evbuffer
          struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
          struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
          这两个函数提供了很是强大的基础 :它们分别返回输入和输出缓冲区 。
          若是写入操做由于数据量太少而中止(或者读取操做由于太多数据而中止 ),则向输出缓冲 区添加数据(或者从输入缓冲区移除数据)将自动重启操做。
   向bufferevent的输出缓冲区添加数据
        int bufferevent_write(struct bufferevent *bufev,const void *data, size_t size);
        int bufferevent_write_buffer(struct bufferevent *bufev,struct evbuffer *buf);

     这些函数向 bufferevent 的输出缓冲区添加数据。 bufferevent_write()将内存中从data 处开 始的 size 字节数据添加到输出缓冲区的末尾 。
          bufferevent_write_buffer()移除 buf 的全部内 容,将其放置到输出缓冲区的末尾。成功时这些函数都返回 0,发生错误时则返回-1。

  从bufferevent的输入缓冲区移除数据

     size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
          int bufferevent_read_buffer(struct bufferevent *bufev,struct evbuffer *buf);

      这些函数从 bufferevent 的输入缓冲区移除数据。bufferevent_read()至多从输入缓冲区移除 size 字节的数据,将其存储到内存中 data 处。函数返回实际移除的字节数。 bufferevent_read_buffer()函数抽空输入缓冲区的全部内容,将其放置到 buf 中,成功时返 回0,失败时返回 -1。

  bufferevent的清空操做

     int bufferevent_flush(struct bufferevent *bufev,short iotype, enum bufferevent_flush_mode state);

数据缓冲Bufferevent总结:1.读取写入的高低水位

              2.调用bufferevent_socket_new()建立基于套接字的 bufferevent

              3.调用bufferevent_socket_connect启动连接

              4.操做回调和启用/禁用

              5.操做bufferevent中的数据

              6.bufferevent的清空操做

              7.调用bufferevent_free()释放bufferevent操做
*************************************************华丽的分割线*****************************************************************

数据封装evBuffer
    libevent 的 evbuffer 实现了为向后面添加数据和从前面移除数据而优化的字节队列。evbuffer 用于处理缓冲网络 IO 的“缓冲”部分。它不提供调度 IO 或者当 IO 就绪时触发 IO 的 功能:这是 bufferevent 的工做。 

  建立和释放evbuffer 

    struct evbuffer *evbuffer_new(void);
    void evbuffer_free(struct evbuffer *buf);

      这两个函数的功能很简明: evbuffer_new() 分配和返回一个新的空 evbuffer ; evbuffer_free()释放 evbuffer 和其内容。 

  evbuffer添加数据 

    int evbuffer_add(struct evbuffer *buf, const void *data, size_tdatlen);
      这个函数添加 data 处的 datalen 字节到 buf 的末尾,
      成功时返回0,失败时返回-1。 

  evbuffer数据移动 

    int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src);
      int evbuffer_remove_buffer(struct evbuffer *src,struct evbuffer *dst,size_t datlen);

      evbuffer_add_buffer()将 src 中的全部数据移动到 dst 末尾,成功时返回0,失败时返回-1
      evbuffer_remove_buffer()函数从 src 中移动 datlen 字节到 dst 末尾,尽可能少进行复制。若是字节数小于 datlen,全部字节被移动。函数返回移动的字节数。 
*************************************************华丽的分割线*****************************************************************

结合以上介绍,写一个关于数据缓冲Bufferevent和evbuffer一些操做的案例:使用套接字基于TCP协议,将客户端将小写字母发送到服务端,转换成大写字母,再回写给客户端输出到屏幕上

  sever.c以下

#include<event2/listener.h>
#include<event2/bufferevent.h>
#include<event2/buffer.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<string.h>
#include<strings.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#define SERV_PORT 9999

//若是客户端有数据写过来,那么会触发当前的回调函数
static void echo_read_cb(struct bufferevent *bev,void *ctx)
{
    //input 就是当前bufferevent的输入缓冲区地址,若是想获得用户数据就从input中获取
    struct evbuffer *input = bufferevent_get_input(bev);
    
    //output 就是当前bufferevent的输出缓冲区地址,若是客户端写数据 就将数据写到output中就能够了
    struct evbuffer *output = bufferevent_get_output(bev);

    //将input缓冲区中数据取出来进行大小写转换
    int i,ret;
    char *buf = malloc(BUFSIZ);
    bzero(buf,BUFSIZ);
    void* ptr = (void*)buf;
    evbuffer_remove(input,ptr,BUFSIZ);//将input缓冲区中数据移除,并复制一份到ptr所指向的空间中
    buf = (char*)ptr;
    for(i = 0;i < strlen(buf); i++ )
    {
        buf[i] = toupper(buf[i]);//大小写转换
    }
    ret = evbuffer_add(output,(void*)buf,strlen(buf));//将buf中的数据内容添加到output缓冲区
    if(ret == -1)
    {
        perror("evbuffer_add error");
    }
    free(buf);
}

static void echo_event_cb(struct bufferevent *bev,short events,void *ctx)
{
    if(events & BEV_EVENT_ERROR)
    {
        perror("Error from bufferevent");
    }
    if(events & (BEV_EVENT_EOF | BEV_EVENT_ERROR))
    {
        bufferevent_free(bev);
    }
}

//fd -- 服务器已经accept成功的cfd fd就是能够直接跟客户端通讯的套接字
static void accept_conn_cb(struct evconnlistener *listener,evutil_socket_t fd,struct sockaddr *address,int socklen,void *ctx)
{
    struct event_base *base = evconnlistener_get_base(listener);
    //建立一个bufferevent绑定fd 和 base
    struct bufferevent *bev = bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);
    //当前刚建立好的bufferevent事件 注册一些回调函数
    bufferevent_setcb(bev,echo_read_cb,NULL,echo_event_cb,NULL);
    //启动监听bufferevent 读事件和写事件
    bufferevent_enable(bev,EV_READ|EV_WRITE);
}

static void accept_error_cb(struct evconnlistener *litener,void *ctx)
{
    struct event_base *base = evconnlistener_get_base(litener);
    int err = EVUTIL_SOCKET_ERROR();
    fprintf(stderr,"Got an error %d (%s) on the listener.""Shutting down.\n",err,evutil_socket_error_to_string(err));
    event_base_loopexit(base,NULL);
}

int main(int argc,char *argv[])
{
    struct event_base *base;
    struct evconnlistener *listener;
    struct sockaddr_in sin;
    int port = SERV_PORT;
    if(argc>1)
    {
        port = atoi(argv[1]);
    }
    if(port<=0||port>65535)
    {
        puts("Invalid port");
        return 1;
    }

    //建立一个 eventbase句柄,在内核开辟一个监听事件的根节点
    base = event_base_new();
    if(!base)
    {
        puts("Couldn't open event base");
        return 1;
    }
    memset(&sin,0,sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(0);
    sin.sin_port = htons(port);

    //将listen fd 封装一个事件 默认在里面已经执行了bind和linsten指令
    listener = evconnlistener_new_bind(base,accept_conn_cb,NULL,LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE,-1,(struct sockaddr*)&sin,sizeof(sin));
    if(!listener)
    {
        perror("Couldn't create listener");
        return 1;
    }

    evconnlistener_set_error_cb(listener,accept_error_cb);
    //开启循环监听事件
    event_base_dispatch(base);
    return 0;
}

 

 

使用以下命令进行编译:

          gcc server.c -l event -o server

测试效果:

      启动服务端,客户端用命令 nc 127.0.0.1 9999进行启动

      

***************************************************写在后面*****************************************************************

这里只是关于libevent库的一些简单使用介绍,介绍了一些经常使用的一些函数和一些名词术语的意义,若是要详细的去了解,仍是须要去阅读libevent库相关书籍,也能够对其用法有必定的了解后,去研究一下它的源码,对我的的编码水平提升会有质的飞跃。

努力不必定有结果,有结果不必定是努力
相关文章
相关标签/搜索