热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Libevent:深入理解7种Bufferevents的基本概念

很多时候,应用程序除了能响应事件之外,还希望能够处理一定量的数据缓存。比如,当写数据的时候,一般会经历下列步骤:l决定向一个

         很多时候,应用程序除了能响应事件之外,还希望能够处理一定量的数据缓存。比如,当写数据的时候,一般会经历下列步骤:

l  决定向一个链接中写入一些数据;将数据放入缓冲区中;

l  等待该链接变得可写;

l  写入尽可能多的数据;

l  记住写入的数据量,如果还有数据需要写入,则需要再次等待链接变得可写。

        

         这种IO缓冲模式很常见,因此Libevent为此提供了一种通用机制。bufferevent”由一个底层传输系统(比如socket),一个读缓冲区和一个写缓冲区组成。普通的events,是在底层传输系统准备好读或写的时候就调用回调函数。而bufferevent则不同,它在已经写入或者读出数据之后才调用回调函数。

 

         Libevent有多种bufferevent,它们共享通用的接口。截至本文撰写时,有下列bufferevent类型:

         基于socket的bufferevents:在底层流式socket上发送和接收数据,使用event_*接口作为其后端。

         异步IO的bufferevents:使用WindowsIOCP接口在底层流式socket上发送和接收数据的bufferevent。(仅限于Windows,实验性的)

         过滤型bufferevent:在数据传送到底层bufferevent对象之前,对到来和外出的数据进行前期处理的bufferevent,比如对数据进行压缩或者转换

         成对的bufferevent:两个相互传送数据的bufferevent

 

         注意:截止Libevent2.0.2-alpha版本,bufferevent接口还没有完全覆盖所有的bufferevent类型。换句话说,并不是下面介绍的每一个接口都能用于所有的bufferevent类型。Libevent开发者会在未来的版本中解决该问题。

         还要注意:bufferevent目前仅能工作在流式协议上,比如TCP未来可能会支持数据报协议,比如UDP。

         本文所有的函数和类型都是在文件中声明。与evbuffers相关的函数在中声明,有关信息参考下一章。

 

一:bufferevent和evbuffers

       每一个bufferevent都有一个输入缓冲区和一个输出缓冲区。它们的类型都是“structevbuffer”。如果bufferevent上有数据输出,则需要将数据写入到输出缓冲区中,如果bufferevent上有数据需要读取,则需要从输入缓冲区中进行抽取。

         evbuffer接口支持很多操作,会在以后的章节中进行讨论。

 

二:回调函数和“水位线”

         每一个bufferevent都有两个数据相关的回调函数:读回调函数和写回调函数。默认情况下,当从底层传输系统读取到任何数据的时候会调用读回调函数;当写缓冲区中足够多的数据已经写入到底层传输系统时,会调用写回调函数。通过调整bufferevent的读取和写入“水位线”(watermarks),可以改变这些函数的默认行为。

         每个bufferevent都有4个水位线:

         读低水位线:当bufferevent的输入缓冲区的数据量到达该水位线或者更高时,bufferevent的读回调函数就会被调用。该水位线默认为0,所以每一次读取操作都会导致读回调函数被调用。

         读高水位线:如果bufferevent的输入缓冲区的数据量到达该水位线时,那么bufferevent就会停止读取,直到输入缓冲区中足够多的数据被抽走,从而数据量再次低于该水位线。默认情况下该水位线是无限制的,所以从来不会因为输入缓冲区的大小而停止读取操作。

         写低水位线:当写操作使得输出缓冲区的数据量达到或者低于该水位线时,才调用写回调函数。默认情况下,该值为0,所以输出缓冲区被清空时才调用写回调函数。

         写高水位线:并非由bufferevent直接使用,对于bufferevent作为其他bufferevent底层传输系统的时候,该水位线才有特殊意义。所以可以参考后面的过滤型bufferevent。

 

         bufferevent同样具有“错误”或者“事件”回调函数,用来通知应用程序关于非数据引起的事件,比如关闭连接或者发生错误。定义了下面的event标志:

         BEV_EVENT_READING:读操作期间发生了事件。具体哪个事件参见其他标志。

         BEV_EVENT_WRITING:写操作期间发生了事件。具体哪个事件参见其他标志。

BEV_EVENT_ERROR:在bufferevent操作期间发生了错误,调用EVUTIL_SOCKET_ERROR函数,可以得到更多的错误信息。

         BEV_EVENT_TIMEOUT:bufferevent上发生了超时

         BEV_EVENT_EOF:bufferevent上遇到了EOF标志

         BEV_EVENT_CONNECTED:在bufferevent上请求链接过程已经完成

 

三:延迟回调函数

         默认情况下,当相应的条件发生的时候,bufferevent回调函数会立即执行。(evbuffer的回调也是这样的,随后会介绍)当依赖关系变得复杂的时候,这种立即调用就会有问题。比如一个回调函数用来在evbuffers A变空时将数据移入到该缓冲区,而另一个回调函数在evbuffer A变满时从其中取出数据进行处理。如果所有这些调用都发生在栈上的话,在依赖关系足够复杂的时候,有栈溢出的风险。

         为了解决该问题,可以通知bufferevent(以及evbuffer)应该延迟回调函数。当条件发生时,延迟回调函数不是立即调用,而是在event_loop()调用中被排队,然后在常规的event回调之后执行。

 

四:bufferevent的选项标志

         创建bufferevent,可以使用下列一个或多个标志来改变其行为,这些标志有:

         BEV_OPT_CLOSE_ON_FREE:当释放bufferevent时,关闭底层的传输系统。这将关闭底层套接字,释放底层bufferevent等。

         BEV_OPT_THREADSAFE:自动为bufferevent分配锁,从而在多线程中可以安全使用。

         BEV_OPT_DEFER_CALLBACKS:设置该标志,bufferevent会将其所有回调函数进行延迟调用(就像上面描述的那样)

         BEV_OPT_UNLOCK_CALLBACKS:默认情况下,当设置bufferevent为线程安全的时候,任何用户提供的回调函数调用时都会锁住bufferevent的锁。设置该标志可以在提供的回调函数被调用时不锁住bufferevent的锁。

 

五:基于socket的bufferevent

         最简单的bufferevents就是基于socket类型的bufferevent。基于socketbufferevent使用Libevent底层event机制探测底层网络socket何时准备好读和写,而且使用底层网络调用(比如readv,writev,WSASendWSARecv)进行传送和接受数据。

 

1:创建一个基于socket的bufferevent

         可以使用bufferevent_socket_new创建一个基于socket的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参数是一个可选的socket文件描述符。如果希望以后再设置socket文件描述符,可以将fd置为-1。

 

         提示:要确保提供给bufferevent_socket_newsocket是非阻塞模式。Libevent提供了便于使用的evutil_make_socket_nonblocking来设置非阻塞模式。

 

         bufferevent_socket_new成功时返回一个bufferevent,失败时返回NULL。

 

2:在基于socket的bufferevent上进行建链

         如果一个bufferevent的socket尚未建链,则可以通过下面的函数建立新的连接:

int  bufferevent_socket_connect(struct  bufferevent *bev,

                    struct  sockaddr*address,  int  addrlen);

         address和addrlen参数类似于标准的connect函数。如果该bufferevent尚未设置socket,则调用该函数为该bufferevent会分配一个新的流类型的socket,并且置其为非阻塞的。

         如果bufferevent已经设置了一个socket,则调用函数bufferevent_socket_connect会告知Libeventsocket尚未建链,在建链成功之前,不应该在其上进行读写操作。

         在建链成功之前,向输出缓冲区添加数据是可以的。

         该函数如果在建链成功时,返回0,如果发生错误,则返回-1.

#include 

#include 

#include 

#include 

 

void  eventcb(struct  bufferevent *bev,  short  events,  void  *ptr)

{

    if (events & BEV_EVENT_CONNECTED) {

         /* We're connected to127.0.0.1:8080.   Ordinarily we'd do

            something here, like start readingor writing. */

    } else if (events & BEV_EVENT_ERROR) {

         /* An error occured while connecting.*/

    }

}

 

int  main_loop(void)

{

    struct  event_base *base;

    struct  bufferevent *bev;

    struct  sockaddr_in sin;

 

    base = event_base_new();

 

    memset(&sin, 0, sizeof(sin));

    sin.sin_family = AF_INET;

    sin.sin_addr.s_addr = htonl(0x7f000001); /*127.0.0.1 */

    sin.sin_port = htons(8080); /* Port 8080 */

 

    bev = bufferevent_socket_new(base,  -1,  BEV_OPT_CLOSE_ON_FREE);

 

    bufferevent_setcb(bev,  NULL,  NULL,  eventcb, NULL);

 

    if (bufferevent_socket_connect(bev,  (struct  sockaddr *)&sin,  sizeof(sin)) <0) {

        /* Error starting connection */

        bufferevent_free(bev);

        return -1;

    }//&#xff08;非阻塞情况下&#xff0c;应该就是返回-1啊&#xff1f;&#xff09;

 

    event_base_dispatch(base);

    return 0;

}

         bufferevent_base_connect()函数是在Libevent-2.0.2-alpha版本引入的&#xff0c;在这之前&#xff0c;需要手动调用connect函数&#xff0c;并且当连接建立的时候&#xff0c;bufferevent会将其作为写事件进行报告。

         注意&#xff1a;如果使用bufferevent_socket_connect进行建链的话&#xff0c;会得到BEV_EVENT_CONNECTED事件。如果自己手动调用connect&#xff0c;则会得到write事件。

         如果在手动调用connect的情况下&#xff0c;仍然想在建链成功的时候得到BEV_EVENT_CONNECTED事件&#xff0c;可以在connect返回-1&#xff0c;并且errnoEAGAINEINPROGRESS之后&#xff0c;调用bufferevent_socket_connect(bev,  NULL,  0)函数。

 

3&#xff1a;通过hostname建链

         经常性的&#xff0c;可能希望将解析主机名和建链操作合成一个单独的操作&#xff0c;可以使用下面的接口&#xff1a;

int  bufferevent_socket_connect_hostname(struct  bufferevent *bev,

              struct evdns_base  *dns_base,  int  family, const  char *hostname,

              int port);

int  bufferevent_socket_get_dns_error(struct  bufferevent *bev);

         该函数解析主机名&#xff0c;查找family类型的地址&#xff08;family的类型可以是AF_INET, AF_INET6和AF_UNSPEC&#xff09;。如果解析主机名失败&#xff0c;会以错误event调用回调函数。如果成功了&#xff0c;则会像 bufferevent_connect一样&#xff0c;接着进行建链。

         dns_base参数是可选的。如果该参数为空&#xff0c;则Libevent会一直阻塞&#xff0c;等待主机名解析完成&#xff0c;一般情况下不会这么做。如果提供了该参数&#xff0c;则Libevent使用它进行异步的主机名解析。参考第九章了解DNS更多内容。

         类似于bufferevent_socket_connect&#xff0c;该函数会告知Libevent&#xff0c;bufferevent上已存在的socket尚未建链&#xff0c;在解析完成&#xff0c;并且建链成功之前&#xff0c;不应该在其上进行读写操作。

         如果发生了错误&#xff0c;有可能是DNS解析错误。可以通过调用函数bufferevent_socket_get_dns_error函数得到最近发生的错误信息。如果该函数返回的错误码为0&#xff0c;则表明没有检查到任何DNS错误。

/*Don&#39;t actually copy this code: it is a poor way to implement an

   HTTP client. Have a look at evhttp instead.

*/

#include 

#include 

#include 

#include 

#include 

 

#include 

 

void  readcb(struct  bufferevent *bev,  void *ptr)

{

    char  buf[1024];

    int  n;

    struct  evbuffer  *input &#61; bufferevent_get_input(bev);

    while ((n &#61; evbuffer_remove(input,  buf,  sizeof(buf)))> 0) {

        fwrite(buf, 1, n, stdout);

    }

}

 

void  eventcb(struct  bufferevent *bev,  short  events,  void  *ptr)

{

    if (events & BEV_EVENT_CONNECTED) {

         printf("Connect okay.\n");

    } else if (events &(BEV_EVENT_ERROR|BEV_EVENT_EOF)) {

         struct event_base *base &#61; ptr;

         if (events & BEV_EVENT_ERROR) {

                 int err &#61; bufferevent_socket_get_dns_error(bev);

                 if (err)

                         printf("DNSerror: %s\n", evutil_gai_strerror(err));

         }

         printf("Closing\n");

         bufferevent_free(bev);

         event_base_loopexit(base, NULL);

    }

}

 

int  main(int  argc, char  **argv)

{

    struct  event_base  *base;

    struct  evdns_base  *dns_base;

    struct  bufferevent  *bev;

 

    if (argc !&#61; 3) {

        printf("Trivial HTTP 0.xclient\n"

               "Syntax: %s [hostname][resource]\n"

               "Example: %s www.google.com/\n",argv[0],argv[0]);

        return 1;

    }

 

    base &#61; event_base_new();

    dns_base &#61; evdns_base_new(base,  1);

 

    bev &#61; bufferevent_socket_new(base,  -1,  BEV_OPT_CLOSE_ON_FREE);

    bufferevent_setcb(bev,  readcb,  NULL,  eventcb, base);

    bufferevent_enable(bev,  EV_READ|EV_WRITE);

   evbuffer_add_printf(bufferevent_get_output(bev), "GET %s\r\n",argv[2]);

    bufferevent_socket_connect_hostname(

        bev,  dns_base,  AF_UNSPEC,  argv[1],  80);

    event_base_dispatch(base);

    return 0;

}

 

六&#xff1a;一般性的bufferevent操作

         本节介绍的函数可以工作在多种bufferevent实现上。

1&#xff1a;释放bufferevent

void  bufferevent_free(struct  bufferevent *bev);

         该函数释放bufferevent。bufferevent在内部具有引用计数&#xff0c;所以即使当释放bufferevent时&#xff0c;如果bufferevent还有未决的延迟回调&#xff0c;那该bufferevent在该回调完成之前也不会删除。

         bufferevent_free函数会尽快释放bufferevent。然而&#xff0c;如果bufferevent的输出缓冲区中尚有残留数据要写&#xff0c;该函数也不会再释放bufferevent之前对缓冲区进行flush

         如果设置了BEV_OPT_CLOSE_ON_FREE标志&#xff0c;并且该buffereventsocket或者其他底层bufferevent作为其传输系统&#xff0c;则在释放该bufferevent时&#xff0c;会关闭该传输系统。

 

2&#xff1a;回调函数、水位线、使能操作

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的一个或多个回调函数。当读取了数据&#xff0c;写入数据或者event发生的时候&#xff0c;就会相应的调用readcb、writecb和eventcb函数。这些函数的第一个参数就是发生event的bufferevent&#xff0c;最后一个参数是bufferevent_setcb的cbarg参数&#xff1a;可以使用该参数传递数据到回调函数。event回调函数的events参数是event标志的位掩码&#xff1a;参考上面的“回调函数和水位线”一节。

         可以通过传递NULL来禁止一个回调。注意bufferevent上的所有回调函数共享一个cbarg&#xff0c;所以改变改值会影响到所有回调函数。

         可以通过向bufferevent_getcb函数传递指针来检索bufferevent当前设置的回调函数&#xff0c;该函数会将*readcb_ptr设置为当前的读回调函数&#xff0c;*writecb_ptr设置为写回调函数&#xff0c;*eventcb_ptr设置为当前的event回调函数&#xff0c;并且*cbarg_ptr设置为当前回调函数的参数。如果任何一个指针设置为NULL&#xff0c;则会被忽略。

 

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使能或者禁止。如果禁止了读取和写入操作&#xff0c;则bufferevent不会读取和写入数据。

         当输出缓冲区为空时&#xff0c;禁止写操作是不必要的&#xff1a;bufferevent会自动停止写操作&#xff0c;而且在有数据可写时又会重启写操作。

         类似的&#xff0c;当输入缓冲区达到它的高水位线的时候&#xff0c;没必要禁止读操作&#xff1a;bufferevent会自动停止读操作&#xff0c;而且在有空间读取的时候&#xff0c;又重新开启读操作。

         默认情况下&#xff0c;新创建的bufferevent会使能写操作&#xff0c;而禁止读操作。

         可以调用bufferevent_get_enabled函数得到该bufferevent当前使能哪些事件。

 

void  bufferevent_setwatermark(struct  bufferevent  *bufev,  short  events,

    size_t  lowmark,  size_t  highmark);

         bufferevent_setwatermark调整一个bufferevent的读水位线和写水位线。如果在events参数中设置了EV_READ参数&#xff0c;则会调整读水位线&#xff0c;如果设置了EV_WRITE标志&#xff0c;则会调整写水位线。将高水位线标志置为0&#xff0c;表示“无限制”。

#include 

#include 

#include 

#include 

 

#include 

#include 

#include 

 

struct info {

    const  char *name;

    size_t  total_drained;

};

 

void  read_callback(struct  bufferevent *bev,  void  *ctx)

{

    struct  info *inf &#61; ctx;

    struct  evbuffer *input &#61; bufferevent_get_input(bev);

    size_t  len &#61; evbuffer_get_length(input);

    if (len) {

        inf->total_drained &#43;&#61; len;

        evbuffer_drain(input,  len);

        printf("Drained  %lu  bytes  from%s\n",

             (unsigned  long) len,  inf->name);

    }

}

 

void  event_callback(struct  bufferevent *bev,  short  events, void *ctx)

{

    struct  info *inf &#61; ctx;

    struct  evbuffer *input &#61; bufferevent_get_input(bev);

    int  finished&#61; 0;

 

    if (events & BEV_EVENT_EOF) {

        size_t  len &#61; evbuffer_get_length(input);

        printf("Got  a  close from %s. We drained %lu bytes from it, "

            "and have %lu left.\n",inf->name,

            (unsigned long)inf->total_drained, (unsigned long)len);

        finished &#61; 1;

    }

    if (events & BEV_EVENT_ERROR) {

        printf("Got an error from %s:%s\n",

            inf->name,evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));

        finished &#61; 1;

    }

    if (finished) {

        free(ctx);

        bufferevent_free(bev);

    }

}

 

struct bufferevent  *setup_bufferevent(void)

{

    struct  bufferevent *b1 &#61; NULL;

    struct  info *info1;

 

    info1 &#61; malloc(sizeof(struct info));

    info1->name &#61; "buffer 1";

    info1->total_drained &#61; 0;

 

    /* ... Here we should set up thebufferevent and make sure it gets

       connected... */

 

    /* Trigger the read callback only wheneverthere is at least 128 bytes

       of data in the buffer. */

    bufferevent_setwatermark(b1,  EV_READ,  128,  0);

 

    bufferevent_setcb(b1,  read_callback,  NULL,  event_callback, info1);

 

    bufferevent_enable(b1,  EV_READ); /* Start reading. */

    return b1;

}

 

七&#xff1a;在bufferevent中操作数据

         如果不能操作读写的数据&#xff0c;则从网络中读写数据没有任何意义。bufferevent提供函数可以操作读写的数据。

struct evbuffer *bufferevent_get_input(struct  bufferevent *bufev);

struct evbuffer *bufferevent_get_output(struct  bufferevent *bufev);

         这两个函数可以返回读写缓冲区中的数据。在evbuffer类型上所能进行的所有操作&#xff0c;可以参考下一章。

 

         注意&#xff0c;应用程序只能从输入缓冲区中移走&#xff08;而不是添加&#xff09;数据&#xff0c;而且只能向输出缓冲区添加&#xff08;而不是移走&#xff09;数据。

         如果bufferevent上的写操作因为数据太少而停滞&#xff08;或者读操作因为数据太多而停滞&#xff09;&#xff0c;则向输出缓冲区中添加数据&#xff08;或者从输入缓冲区中移走数据&#xff09;可以自动重启写&#xff08;读&#xff09;操作。

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表示成功&#xff0c;返回-1表示发生了错误。

 

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中&#xff0c;该函数返回0表示成功&#xff0c;返回-1表示失败。

         注意bufferevent_read函数中&#xff0c;data缓冲区必须有足够的空间保存size个字节。

#include 

#include 

 

#include 

 

void  read_callback_uppercase(struct  bufferevent  *bev,  void *ctx)

{

        /* This callback removes the data frombev&#39;s input buffer 128

           bytes at a time, uppercases it, andstarts sending it

           back.

 

           (Watch out!  In practice, you shouldn&#39;t use toupper toimplement

           a network protocol, unless you knowfor a fact that the current

           locale is the one you want to beusing.)

         */

 

        char  tmp[128];

        size_t  n;

        int  i;

        while (1) {

                n &#61; bufferevent_read(bev,  tmp,  sizeof(tmp));

                if (n <&#61; 0)

                        break; /* No more data. */

                for (i&#61;0; i

                        tmp[i] &#61;toupper(tmp[i]);

                bufferevent_write(bev,  tmp,  n);

        }

}

 

structproxy_info {

        struct  bufferevent  *other_bev;

};

void  read_callback_proxy(struct  bufferevent  *bev,  void* ctx)

{

        /* You might use a function like thisif you&#39;re implementing

           a simple proxy: it will take datafrom one connection (on

           bev), and write it to another,copying as little as

           possible. */

        struct  proxy_info *inf &#61; ctx;

 

        bufferevent_read_buffer(bev,  bufferevent_get_output(inf->other_bev));

}

 

structcount {

        unsigned long last_fib[2];

};

 

void  write_callback_fibonacci(struct  bufferevent *bev,  void * ctx)

{

        /* Here&#39;s a callback that adds someFibonacci numbers to the

           output buffer of bev.  It stops once we have added 1k of

           data; once this data is drained,we&#39;ll add more. */

        struct count *c &#61; ctx;

 

        struct  evbuffer *tmp &#61; evbuffer_new();

        while (evbuffer_get_length(tmp) <1024) {

                 unsigned long next &#61;c->last_fib[0] &#43; c->last_fib[1];

                 c->last_fib[0] &#61;c->last_fib[1];

                 c->last_fib[1] &#61; next;

 

                 evbuffer_add_printf(tmp,"%lu", next);

        }

 

        /* Now we add the whole contents of tmpto bev. */

        bufferevent_write_buffer(bev,  tmp);

 

        /* We don&#39;t need tmp any longer. */

        evbuffer_free(tmp);

}

 

八&#xff1a;读写超时

         同其他events一样&#xff0c;某段时间过去之后&#xff0c;bufferevent还没有成功的读或写任何数据&#xff0c;则可以触发某个超时事件。

void  bufferevent_set_timeouts(struct  bufferevent  *bufev,

     conststruct  timeval  *timeout_read,  const  struct timeval *timeout_write);

         将timeout设置为NULL&#xff0c;意味着移除超时时间&#xff1b;然而在Libevent 2.1.2-alpha版本之前&#xff0c;这种方式并非在所有event类型上都有效。&#xff08;对于较老版本的&#xff0c;取消超时时间的有效方法是&#xff0c;可以将超时时间设置为好几天&#xff0c;并且/或者使eventcb函数忽略BEV_TIMEOUT事件&#xff09;。

         当bufferevent试图读取数据时&#xff0c;等待了timeout_read秒还没有数据&#xff0c;则读超时事件就会触发。当bufferevent试图写数据时&#xff0c;至少等待了timeout_write秒&#xff0c;则写超时事件就会触发。

         注意&#xff0c;只有在bufferevent读或写的时候&#xff0c;才会对超时时间进行计时。换句话说&#xff0c;如果bufferevent上禁止了读操作&#xff0c;或者当输入缓冲区满&#xff08;达到高水位线&#xff09;时&#xff0c;则读超时时间不会使能。类似的&#xff0c;如果写操作未被使能&#xff0c;或者没有数据可写&#xff0c;则写超时时间也会被禁止

 

         当读或写超时发生的时候&#xff0c;则bufferevent上相应的读写操作就会被禁止。相应的event回调函数就会以BEV_EVENT_TIMEOUT|BEV_EVENT_READINGBEV_EVENT_TIMEOUT|BEV_EVENT_WRITING进行调用。

 

九&#xff1a;在bufferevent上进行flush

int  bufferevent_flush(struct  bufferevent *bufev,

 short  iotype, enum  bufferevent_flush_mode  state);

         对一个bufferevent进行flush&#xff0c;使bufferevent尽可能多的从底层传输系统上读取或者写入数据&#xff0c;而忽略其他可能阻止写入的限制条件。该函数的细节依赖于不同类型的bufferevent。

         iotype参数可以是EV_READ,EV_WRITE, 或 EV_READ|EV_WRITE&#xff0c;指明处理读操作、写操作&#xff0c;还是两者都处理。state参数应该是BEV_NORMAL, BEV_FLUSH, 或 BEV_FINISHED。BEV_FINISHED 指明另一端会被告知已无数据可发送&#xff1b;BEV_NORMAL和BEV_FLUSH之间的区别依赖于bufferevent的类型。

         bufferevent_flush函数返回-1表示失败&#xff0c;返回0表示没有任何数据被flush&#xff0c;返回1表示由数据被flush。

         目前&#xff08;Libevent2.0.5-beta&#xff09;&#xff0c;bufferevent_flush函数只在某些bufferevent类型上进行了实现&#xff0c;特别是基于socketbufferevent并不支持该操作。

 

十&#xff1a;特定类型的bufferevent函数

         下列bufferevent函数并不是所有bufferevent类型都支持&#xff1a;

int  bufferevent_priority_set(struct  bufferevent *bufev,  int  pri);

int  bufferevent_get_priority(struct  bufferevent *bufev);

         该函数将实现bufev的events的优先级调整为pri&#xff0c;关于优先级更多的信息&#xff0c;可以参考event_priority_set函数。

         该函数返回0表示成功&#xff0c;返回-1表示失败&#xff0c;该函数只能工作在基于socket的bufferevent上。

 

int  bufferevent_setfd(struct  bufferevent *bufev,  evutil_socket_t  fd);

evutil_socket_t bufferevent_getfd(struct  bufferevent  *bufev);

         该函数设置或者返回一个基于fd的event的文件描述符。只有基于socket的bufferevent支持setfd操作。这些函数返回-1表示失败&#xff0c;setfd返回0表示成功。

 

struct event_base  * bufferevent_get_base(struct  bufferevent  *bev);

         该函数返回一个bufferevent的event_base。

 

struct bufferevent  *bufferevent_get_underlying(struct  bufferevent  *bufev);

       如果bufferevent作为其他bufferevent的底层传输系统的话&#xff0c;则该函数返回该底层bufferevent。参考过滤型bufferevent&#xff0c;获得关于这种情况的更多信息。

 

十一&#xff1a;在bufferevent上手动加锁或者解锁

         类似于evbuffers&#xff0c;有时希望保证在bufferevent上的一系列操作是原子性的。Libevent提供了可以手动加锁和解锁bufferevent的函数。

void  bufferevent_lock(struct  bufferevent *bufev);

void  bufferevent_unlock(struct  bufferevent *bufev);

         注意&#xff0c;如果一个bufferevent在创建时没有指定BEV_OPT_THREADSAFE 标志&#xff0c;或者Libevent的线程支持功能没有激活&#xff0c;则加锁一个bufferevent没有效果。

         通过该函数对bufferevent进行加锁的同时&#xff0c;也会加锁evbuffers。这些函数都是递归的&#xff1a;对一个已经加锁的bufferevent再次加锁是安全的。当然&#xff0c;对于每次锁定都必须进行一次解锁。

 

十二&#xff1a;过时的bufferevent函数

         在Libevent1.4和Libevent2.0之间&#xff0c;bufferevent的后台代码经历了大量的修改。在老接口中&#xff0c;访问bufferevent结构的内部是很正常的&#xff0c;而且&#xff0c;还会经常使用依赖于这种访问方式的宏。

         让问题变得更加复杂的是&#xff0c;老的代码中&#xff0c;有时会使用以“evbuffer”为前缀的bufferevent函数。

         下表是一个Libevent2.0之前版本的函数概述

Current name

Old name

bufferevent_data_cb

evbuffercb

bufferevent_event_cb

everrorcb

BEV_EVENT_READING

EVBUFFER_READ

BEV_EVENT_WRITE

EVBUFFER_WRITE

BEV_EVENT_EOF

EVBUFFER_EOF

BEV_EVENT_ERROR

EVBUFFER_ERROR

BEV_EVENT_TIMEOUT

EVBUFFER_TIMEOUT

bufferevent_get_input(b)

EVBUFFER_INPUT(b)

bufferevent_get_output(b)

EVBUFFER_OUTPUT(b)

         老版本的函数定义在event.h中&#xff0c;而不是event2/bufferevent.h中。

         如果仍然需要访问bufferevent内部结构中的普通部分&#xff0c;可以包含event2/bufferevent_struct.h。我们不建议这样做&#xff1a;bufferevent结构的内容在不同的Libevent版本中经常会发生改变。如果包含了event2/bufferevent_compat.h文件&#xff0c;可以使用本节介绍的宏和名字。

         老版本的代码中&#xff0c;创建bufferevent结构的接口有所不同&#xff1a;

struct bufferevent  *bufferevent_new(evutil_socket_t  fd,

    evbuffercb  readcb,  evbuffercb  writecb,  everrorcb  errorcb,  void  *cbarg);

int  bufferevent_base_set(struct  event_base  *base,  struct bufferevent  *bufev);

         bufferevent_new()函数仅能创建基于socket的bufferevent&#xff0c;而且还是在不推荐使用的“当前”event_base上。可以通过调用bufferevent_base_set来调整一个socket bufferevent的event_base。

 

         老版本的代码设置超时的秒数&#xff0c;而不是设置结构体timeval&#xff1a;

void  bufferevent_settimeout(struct  bufferevent  *bufev,

                    int  timeout_read, int  timeout_write);

         最后注意&#xff0c;在Libevent2.0之前的版本中&#xff0c;底层的evbuffer实现是非常低效的&#xff0c;因此使用bufferevent构建高性能的程序是不太可能的。

 

原文&#xff1a;http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html

转:https://www.cnblogs.com/gqtcgq/p/7247255.html



推荐阅读
author-avatar
odoresampey_768
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有