热门标签 | 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



推荐阅读
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 如何在跨函数中使用内存?
    本文介绍了在跨函数中使用内存的方法,包括使用指针变量、动态分配内存和静态分配内存的区别。通过示例代码说明了如何正确地在不同函数中使用内存,并提醒程序员在使用动态分配内存时要手动释放内存,以防止内存泄漏。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
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社区 版权所有