• libevent简介[翻译]11 Evbuffers:缓冲IO的功能函数


    http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html

    Libevent的evbuffer函数实现了一个字节队列,用来优化从尾部增加数据从头部删除数据的性能。

    Evbuffers经常用作网络IO缓冲。你不用提供函数管理IO或是在IO准备好的时候触发,这就是bufferevents所做的事。

    创建和释放evbuffer

    接口

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

    这个函数非常简单清晰了,evbuffer_new()申请创建并返回一个空的evbuffer,evbuffer_free()删除里面所有的内容。

    Evbuffers和线程安全

    接口

    int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
    void evbuffer_lock(struct evbuffer *buf);
    void evbuffer_unlock(struct evbuffer *buf);
    

    默认情况下,多线程同时访问evbuffer是不安全的,可以在evbuffer上调用evbuffer_enable_locking()。如果lock参数是NULL,libevent会申请一个新的lock,并且用这个lock创建一个函数,提供给evthread_set_lock_creation_callback。如果lock不是NULL,那就是用传递的lock。

    evbuffer_lock()evbuffer_unlock()用来请求和释放evbuffer上的锁。你可以使用它们保证操作的原子性。如果没有lock,函数不会有任何作用。

    注意,你不需要在每个操作都调用evbuffer_lock()evbuffer_unlock(),因为如果evbuffer已经锁住了,每个操作都是原子的。你只需在有多个操作需要执行,但是不希望其他线程进入的时候加锁。

    检查evbuffer

    接口

    size_t evbuffer_get_length(const struct evbuffer *buf);
    

    这个函数返回在evbuffer中的字节数

    接口

    size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);
    

    这个函数返回放在evbuffer前面连续的字节数。放在evbuffer的数据是被分割到多个块的内存,这个是返回第一个块的数据。

    向evbuffer增加数据

    接口

    int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
    

    这个函数消耗datlen的长度在buf的最后,成功返回0,失败返回-1.

    接口

    int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
    int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);
    

    这个函数是在buf的后面附加格式化的数据。

    接口

    int evbuffer_expand(struct evbuffer *buf, size_t datlen);
    

    这个函数修改最后一个块的内存,或是增加一个新的块,这样buffer就有足够大的空间而不用重新申请了。

    示例

    /* Here are two ways to add "Hello world 2.0.1" to a buffer. */
    /* Directly: */
    evbuffer_add(buf, "Hello world 2.0.1", 17);
    
    /* Via printf: */
    evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1);
    

    把数据从一个evbuffer移动到另一个

    为了效率,libevent已经又花了移动数据的函数

    接口

    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

    evbuffer_remove_buffer()移动最多datlen的数据从dst到src。

    在evbuffer前面增加数据

    接口

    int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size);
    int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);
    

    这两个函数与evbuffer_add()evbuffer_add_buffer()类似,只不过是把数据放到buffer前面。

    重新排列evbuffer内部的层

    有时候你想看evbuffer前面的N个字节的数据,把它看做一个连续的整体。如果你要这么做,你必须保证前N个字节的数据是连续的。

    接口

    unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);
    

    evbuffer_pullup()函数线性化buf第一个空间的字节,拷贝或是移动他们,确保他们都是在同一个块的连续内存。如果空间大于buffer,就返回NULL,否则返回第一个字节的地址。

    如果数据很多,调用evbuffer_pullup()会非常慢,因为需要拷贝数据

    示例

    #include <event2/buffer.h>
    #include <event2/util.h>
    
    #include <string.h>
    
    int parse_socks4(struct evbuffer *buf, ev_uint16_t *port, ev_uint32_t *addr)
    {
        /* Let's parse the start of a SOCKS4 request!  The format is easy:
         * 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
         * destip. */
        unsigned char *mem;
    
        mem = evbuffer_pullup(buf, 8);
    
        if (mem == NULL) {
            /* Not enough data in the buffer */
            return 0;
        } else if (mem[0] != 4 || mem[1] != 1) {
            /* Unrecognized protocol or command */
            return -1;
        } else {
            memcpy(port, mem+2, 2);
            memcpy(addr, mem+4, 4);
            *port = ntohs(*port);
            *addr = ntohl(*addr);
            /* Actually remove the data from the buffer now that we know we
               like it. */
            evbuffer_drain(buf, 8);
            return 1;
        }
    }
    

    注意

    调用evbuffer_pullup()如果size的大小等于evbuffer_get_contiguous_space()的返回值将不会拷贝或移动任何数据。

    从evbuffer删除数据

    接口

    int evbuffer_drain(struct evbuffer *buf, size_t len);
    int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);
    

    evbuffer_remove()删除buf前面的datlen的字节并且拷贝到内存。如果比datlen的长度小,那就全部拷贝。出错返回-1,正常返回拷贝的字节数。

    evbuffer_drain()evbuffer_remove()行为一样,但是,它不拷贝数据:仅仅是从buffer前面删除数据。

    从evbuffer拷贝数据出来

    有时候你想从buffer前面拷贝数据出来并且不想删除它。比如,你想看看某些数据是否达到,而不想删除数据,或者内部重新排列数据。

    接口

    ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data, size_t datlen);
    ev_ssize_t evbuffer_copyout_from(struct evbuffer *buf,
         const struct evbuffer_ptr *pos,
         void *data_out, size_t datlen);
    

    evbuffer_copyout()行为和evbuffer_remove()类似,但是不从buffer删除数据。也就是把钱datlen的数据从buf前面拷贝到内存。如果数据比datlen少,就拷贝全部的,出错返回-1,成功返回拷贝的字节数。

    evbuffer_copyout_from()的行为和evbuffer_copyout()类似,但是不是从buffer的前面拷贝字节,二是从pos指定的位置拷贝字节。

    如果拷贝数据太慢,可以使用evbuffer_peek()

    示例

    #include <event2/buffer.h>
    #include <event2/util.h>
    #include <stdlib.h>
    #include <stdlib.h>
    
    int get_record(struct evbuffer *buf, size_t *size_out, char **record_out)
    {
        /* Let's assume that we're speaking some protocol where records
           contain a 4-byte size field in network order, followed by that
           number of bytes.  We will return 1 and set the 'out' fields if we
           have a whole record, return 0 if the record isn't here yet, and
           -1 on error.  */
        size_t buffer_len = evbuffer_get_length(buf);
        ev_uint32_t record_len;
        char *record;
    
        if (buffer_len < 4)
           return 0; /* The size field hasn't arrived. */
    
       /* We use evbuffer_copyout here so that the size field will stay on
           the buffer for now. */
        evbuffer_copyout(buf, &record_len, 4);
        /* Convert len_buf into host order. */
        record_len = ntohl(record_len);
        if (buffer_len < record_len + 4)
            return 0; /* The record hasn't arrived */
    
        /* Okay, _now_ we can remove the record. */
        record = malloc(record_len);
        if (record == NULL)
            return -1;
    
        evbuffer_drain(buf, 4);
        evbuffer_remove(buf, record, record_len);
    
        *record_out = record;
        *size_out = record_len;
        return 1;
    }
    

    线性输入

    接口

    enum evbuffer_eol_style {
            EVBUFFER_EOL_ANY,
            EVBUFFER_EOL_CRLF,
            EVBUFFER_EOL_CRLF_STRICT,
            EVBUFFER_EOL_LF,
            EVBUFFER_EOL_NUL
    };
    char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out,
        enum evbuffer_eol_style eol_style);
    

    很多底层协议使用线性的格式。evbuffer_readln()从evbuffer的前面提取一行数据,然后返回一个新创建的NUL结尾的字符串。如果n_read_out不是NULL,*n_read_out就指向了字符串返回的长度。如果不是整行被读取,那么就返回NULL,行结束符并不在拷贝的字符串内。

    evbuffer_readln()有4中结束格式:

    • EVBUFFER_EOL_LF

    结束是一个换行符,也就是" "。ASCII是0x0A

    • EVBUFFER_EOL_CRLF_STRICT

    结束是一个回车符,也就是" ",ASCII是0x0D 0x0A

    • EVBUFFER_EOL_CRLF

    结束是一个可选的回车符,跟在换行符后面,也就是可以是" "或" "。这个格式在解析基于文本的Internet协议时非常有用。因为有的是标准的" "结束,而有的是" "

    • EVBUFFER_EOL_ANY

    结束是任何组合的换行符和回车符,这个格式不太有用,仅仅是为了兼容性。

    • EVBUFFER_EOL_NUL

    结束是一个数据0,也就是ASCII的NUL

    注意,如果使用event_set_mem_functions()代替默认的malloc,字符串通过evbuffer_readln返回,将会通过你自己定义的申请函数替换

    示例

    char *request_line;
    size_t len;
    
    request_line = evbuffer_readln(buf, &len, EVBUFFER_EOL_CRLF);
    if (!request_line) {
        /* The first line has not arrived yet. */
    } else {
        if (!strncmp(request_line, "HTTP/1.0 ", 9)) {
            /* HTTP 1.0 detected ... */
        }
        free(request_line);
    }
    

    在evbuffer中查找

    evbuffer_ptr结构体指向了evbuffer申请的空间,包含你可以遍历evbuffer的数据。

    接口

    struct evbuffer_ptr {
            ev_ssize_t pos;
            struct {
                    /* internal fields */
            } _internal;
    };
    

    pos是唯一公共的字段。另一个不能在你代码中使用。它指向一个evbuffer从开始到offset的位置

    接口

    struct evbuffer_ptr evbuffer_search(struct evbuffer *buffer,
        const char *what, size_t len, const struct evbuffer_ptr *start);
    struct evbuffer_ptr evbuffer_search_range(struct evbuffer *buffer,
        const char *what, size_t len, const struct evbuffer_ptr *start,
        const struct evbuffer_ptr *end);
    struct evbuffer_ptr evbuffer_search_eol(struct evbuffer *buffer,
        struct evbuffer_ptr *start, size_t *eol_len_out,
        enum evbuffer_eol_style eol_style);
    

    evbuffer_search()函数扫描buffer中的内容,查找到匹配what中字符串的位置。返回evbuffer_ptr指向字符串的位置,如果没找到,返回-1.如果start参数提供了,就指定了在查找开始的位置,否则,从字符串开始查找。

    evbuffer_search_range()evbuffer_search行为类似,只是值查找end之前的数据。

    evbuffer_search_eol()函数发现行结束的数据,与evbuffer_readln()类似,只不过不拷贝数据,而是返回这一行数据的开头位置。如果eol_len_out不是NULL,它就在末尾增加EOL标识。

    接口

    enum evbuffer_ptr_how {
            EVBUFFER_PTR_SET,
            EVBUFFER_PTR_ADD
    };
    int evbuffer_ptr_set(struct evbuffer *buffer, struct evbuffer_ptr *pos,
        size_t position, enum evbuffer_ptr_how how);
    

    evbuffer_ptr_set操作pos位置的数据。如果how是EVBUFFER_PTR_SET,指针就指向buffer中一个绝对的位置;如果是EVBUFFER_PTR_ADD,就向前移动position字节。

    示例

    #include <event2/buffer.h>
    #include <string.h>
    
    /* Count the total occurrences of 'str' in 'buf'. */
    int count_instances(struct evbuffer *buf, const char *str)
    {
        size_t len = strlen(str);
        int total = 0;
        struct evbuffer_ptr p;
    
        if (!len)
            /* Don't try to count the occurrences of a 0-length string. */
            return -1;
    
        evbuffer_ptr_set(buf, &p, 0, EVBUFFER_PTR_SET);
    
        while (1) {
             p = evbuffer_search(buf, str, len, &p);
             if (p.pos < 0)
                 break;
             total++;
             evbuffer_ptr_set(buf, &p, 1, EVBUFFER_PTR_ADD);
        }
    
        return total;
    }
    

    警告

    任何修改evbuffer或是layout的调用都会是evbuffer_ptr无效,并且不能安全使用。

    不拷贝数据来检查数据

    有时候你想读取evbuffer中的数据,而不拷贝它,不重新排版它。有时候你想查看evbuffer中间的数据。

    你可以这样做:

    接口

    struct evbuffer_iovec {
            void *iov_base;
            size_t iov_len;
    };
    
    int evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len,
        struct evbuffer_ptr *start_at,
        struct evbuffer_iovec *vec_out, int n_vec);
    

    当你调用evbuffer_peek()的时候,你需要给一个evbuffer_iovec的结构体,用vec_out传递进来。这个数组的长度是n_vec。它设置这些结构体,每一个都包指向evbuffer在内存中(iov_base)的块,并且长度也设置在哪个块中。

    如果len比0小,evbuffer_peek()就会试着填满所有的evbuffer_iovec结构体。否则,它会不断的填满它们,知道所有的都被使用了,或者len的字节是可见的。如果函数可以给你所有的你请求的数据,它会返回使用的evbuffer_iovec结构体的数量。否则,返回可能需要的数量。

    如果ptr是NULL,evbuffer_peek()从buffer第一个位置开始,否则,从ptr指定的位置开始

    示例

    {
        /* Let's look at the first two chunks of buf, and write them to stderr. */
        int n, i;
        struct evbuffer_iovec v[2];
        n = evbuffer_peek(buf, -1, NULL, v, 2);
        for (i=0; i<n; ++i) { /* There might be less than two chunks available. */
            fwrite(v[i].iov_base, 1, v[i].iov_len, stderr);
        }
    }
    
    {
        /* Let's send the first 4906 bytes to stdout via write. */
        int n, i, r;
        struct evbuffer_iovec *v;
        size_t written = 0;
    
        /* determine how many chunks we need. */
        n = evbuffer_peek(buf, 4096, NULL, NULL, 0);
        /* Allocate space for the chunks.  This would be a good time to use
           alloca() if you have it. */
        v = malloc(sizeof(struct evbuffer_iovec)*n);
        /* Actually fill up v. */
        n = evbuffer_peek(buf, 4096, NULL, v, n);
        for (i=0; i<n; ++i) {
            size_t len = v[i].iov_len;
            if (written + len > 4096)
                len = 4096 - written;
            r = write(1 /* stdout */, v[i].iov_base, len);
            if (r<=0)
                break;
            /* We keep track of the bytes written separately; if we don't,
               we may write more than 4096 bytes if the last chunk puts
               us over the limit. */
            written += len;
        }
        free(v);
    }
    
    {
        /* Let's get the first 16K of data after the first occurrence of the
           string "start
    ", and pass it to a consume() function. */
        struct evbuffer_ptr ptr;
        struct evbuffer_iovec v[1];
        const char s[] = "start
    ";
        int n_written;
    
        ptr = evbuffer_search(buf, s, strlen(s), NULL);
        if (ptr.pos == -1)
            return; /* no start string found. */
    
        /* Advance the pointer past the start string. */
        if (evbuffer_ptr_set(buf, &ptr, strlen(s), EVBUFFER_PTR_ADD) < 0)
            return; /* off the end of the string. */
    
        while (n_written < 16*1024) {
            /* Peek at a single chunk. */
            if (evbuffer_peek(buf, -1, &ptr, v, 1) < 1)
                break;
            /* Pass the data to some user-defined consume function */
            consume(v[0].iov_base, v[0].iov_len);
            n_written += v[0].iov_len;
    
            /* Advance the pointer so we see the next chunk next time. */
            if (evbuffer_ptr_set(buf, &ptr, v[0].iov_len, EVBUFFER_PTR_ADD)<0)
                break;
        }
    }
    
    **注意**
    
    - 修改通过evbuffer_iovec指向的数据会导致无法预料的行为
    
    - 如果任何一个函数被调用了去修改evbuffer,evbuffer_peek()获得的指针将会变成无效的
    
    - 如果你的evbuffer是多线程的,那么请确保在调用`evbuffer_peek()`的时候通过`evbuffer_lock()`加了锁,并且在操作完成后释放锁。
    
    # 直接想evbuffer添加数据
    
    有时候你想直接想evbuffer添加数据,而不是先写入到一个字符串数组,然后再通过evbuffer_add()添加到里面。有一对函数可以做到这个:`evbuffer_reserve_space()`和`evbuffer_commit_space()`。和`evbuffer_peek()`一样,这两个函数使用evbuffer_iovec结构体来直接访问内存中的evbuffer。
    
    **接口**
    
    ```cpp
    int evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
        struct evbuffer_iovec *vec, int n_vecs);
    int evbuffer_commit_space(struct evbuffer *buf,
        struct evbuffer_iovec *vec, int n_vecs);
    

    evbuffer_reserve_space()提供了指向evbuffer内部空间的指针。它把buffer扩充到至少能满足你提供的size大小。这个指针指向扩充的位置,并且他的长度,会被保存到你传入的vec vectores数组中。n_vec是数组的长度

    n_vec至少是1.如果只提供了一个vector,libevent将会保证你有一个连续的空间,但是可能会重新分配buffer或是浪费内存。为了性能,最好提供2个vecotre。函数返回你请求的vector使用的个数。

    你写入到vector中的数据并不会称为buffer的一元,需要调用evbuffer_commit_space(),使得数据写入到buffer。如果你想提交比你少的数据,你可以减少iov_len。你可以同样传入比你提供少的vector。

    注意和说明

    • 调用函数重新排列evbufer或是添加数据会是通过evbuffer_reserve_space()获取的指针失效

    • 当前的实现,evbuffer_reserve_space()不会使用超过两个vector,不管你提供了多少,这个会在未来解决

    • 任何时候调用evbuffer_reserve_space()都是安全的

    • 如果你的evbuffer在多线程中使用,确保调用evbuffer_lock()来加锁,并且在提交后释放锁。

    示例

    /* Suppose we want to fill a buffer with 2048 bytes of output from a
       generate_data() function, without copying. */
    struct evbuffer_iovec v[2];
    int n, i;
    size_t n_to_add = 2048;
    
    /* Reserve 2048 bytes.*/
    n = evbuffer_reserve_space(buf, n_to_add, v, 2);
    if (n<=0)
       return; /* Unable to reserve the space for some reason. */
    
    for (i=0; i<n && n_to_add > 0; ++i) {
       size_t len = v[i].iov_len;
       if (len > n_to_add) /* Don't write more than n_to_add bytes. */
          len = n_to_add;
       if (generate_data(v[i].iov_base, len) < 0) {
          /* If there was a problem during data generation, we can just stop
             here; no data will be committed to the buffer. */
          return;
       }
       /* Set iov_len to the number of bytes we actually wrote, so we
          don't commit too much. */
       v[i].iov_len = len;
    }
    
    /* We commit the space here.  Note that we give it 'i' (the number of
       vectors we actually used) rather than 'n' (the number of vectors we
       had available. */
    if (evbuffer_commit_space(buf, v, i) < 0)
       return; /* Error committing */
    

    错误的示例

    /* Here are some mistakes you can make with evbuffer_reserve().
       DO NOT IMITATE THIS CODE. */
    struct evbuffer_iovec v[2];
    
    {
      /* Do not use the pointers from evbuffer_reserve_space() after
         calling any functions that modify the buffer. */
      evbuffer_reserve_space(buf, 1024, v, 2);
      evbuffer_add(buf, "X", 1);
      /* WRONG: This next line won't work if evbuffer_add needed to rearrange
         the buffer's contents.  It might even crash your program. Instead,
         you add the data before calling evbuffer_reserve_space. */
      memset(v[0].iov_base, 'Y', v[0].iov_len-1);
      evbuffer_commit_space(buf, v, 1);
    }
    
    {
      /* Do not modify the iov_base pointers. */
      const char *data = "Here is some data";
      evbuffer_reserve_space(buf, strlen(data), v, 1);
      /* WRONG: The next line will not do what you want.  Instead, you
         should _copy_ the contents of data into v[0].iov_base. */
      v[0].iov_base = (char*) data;
      v[0].iov_len = strlen(data);
      /* In this case, evbuffer_commit_space might give an error if you're
         lucky */
      evbuffer_commit_space(buf, v, 1);
    }
    

    使用evbuffer的网络IO

    最常见的使用evbuffer的场景就是网络IO。操作网络IO的函数如下:

    接口

    int evbuffer_write(struct evbuffer *buffer, evutil_socket_t fd);
    int evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
            ev_ssize_t howmuch);
    int evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch);
    

    evbuffer_read()函数从fd中读取howmuch的字节数据到buffer中。如果成功,返回读了几个字节,附带0在遇到EOF的时候,-1表示失败。注意,error将会指出非阻塞操作没有正常执行;需要通过EAGAIN或是在Windows上的WSAEWOULDBLOCK来检查。如果howmuch是负数,evbuffer_read()试着读取更多的数据。

    evbuffer_write_atmost()试着从buffer前面写入到fd的howmuch字节。如果成功,返回写入了多少字节,-1表示失败。和evbuffer_read()类似,需要检测error code来判断是否真的出错了,还是表明非阻塞IO仅仅没完成。如果传入负数到howmuch,将会尽量写入更多的数据。

    调用evbuffer_write()evbuffer_write_atmost()类似,传入负数到howmuch,将会试着写入更多的数据。

    在Unix上,可以支持read和write调用中任意文件秒速符,在Windows上,只能支持socket。

    记住,当使用bufferevent,你不需要调用它的IO函数,bufferevent会自己这样做

    Evbuffers和回调函数

    有时候,使用evbuffer的用户想知道什么时候添加了数据什么时候从evbuffer删除了数据。为了支持这个,libevent提供了基本的evbuffer回调机制。

    接口

    struct evbuffer_cb_info {
            size_t orig_size;
            size_t n_added;
            size_t n_deleted;
    };
    
    typedef void (*evbuffer_cb_func)(struct evbuffer *buffer,
        const struct evbuffer_cb_info *info, void *arg);
    

    evbuffer会在evbuffer添加或是删除数据的时候回调。它接收buffer,一个指向evbuffer_cb_info结构体的指针,和一个用户自定义参数列表。evbuffer_cb_info结构体的orig_size字段记录了在它的size修改之前的字节数;他的n_added字段记录了多少字节添加了进来,n_deleted字段记录了多少字节删除了

    接口

    struct evbuffer_cb_entry;
    struct evbuffer_cb_entry *evbuffer_add_cb(struct evbuffer *buffer,
        evbuffer_cb_func cb, void *cbarg);
    

    evbuffer_add_cb()添加回调函数到evbuffer,返回一个不透明的指针,可以稍后用来指向这个特殊的回调函数实例。cb就是回调调用的函数,cbarg就是用户指定的回调的时候传入的参数。

    你可以为一个evbuffer添加多个回调,添加一个新的回调不会删除老的回调。

    示例

    #include <event2/buffer.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    /* Here's a callback that remembers how many bytes we have drained in
       total from the buffer, and prints a dot every time we hit a
       megabyte. */
    struct total_processed {
        size_t n;
    };
    void count_megabytes_cb(struct evbuffer *buffer,
        const struct evbuffer_cb_info *info, void *arg)
    {
        struct total_processed *tp = arg;
        size_t old_n = tp->n;
        int megabytes, i;
        tp->n += info->n_deleted;
        megabytes = ((tp->n) >> 20) - (old_n >> 20);
        for (i=0; i<megabytes; ++i)
            putc('.', stdout);
    }
    
    void operation_with_counted_bytes(void)
    {
        struct total_processed *tp = malloc(sizeof(*tp));
        struct evbuffer *buf = evbuffer_new();
        tp->n = 0;
        evbuffer_add_cb(buf, count_megabytes_cb, tp);
    
        /* Use the evbuffer for a while.  When we're done: */
        evbuffer_free(buf);
        free(tp);
    }
    

    注意,传递进去释放非空的evbuffer并不表示把它里面的数据排空,并且释放evbuffer并不会释放用户提供的数据指针指向的内容

    如果你不想是buffer的回调函数一直可用,你可以删除它或是禁止它。

    接口

    int evbuffer_remove_cb_entry(struct evbuffer *buffer,
        struct evbuffer_cb_entry *ent);
    int evbuffer_remove_cb(struct evbuffer *buffer, evbuffer_cb_func cb,
        void *cbarg);
    
    #define EVBUFFER_CB_ENABLED 1
    int evbuffer_cb_set_flags(struct evbuffer *buffer,
                              struct evbuffer_cb_entry *cb,
                              ev_uint32_t flags);
    int evbuffer_cb_clear_flags(struct evbuffer *buffer,
                              struct evbuffer_cb_entry *cb,
                              ev_uint32_t flags);
    

    你可以删除回调函数,通过evbuffer_cb_entry,你在添加的时候获得的,或是你是用的回调指针。

    evbuffer_cb_set_flags()evbuffer_cb_clear_flags()给回调函数设置或是清除一个给定的标识。当前只有一个用户定义的标识支持:EVBUFFER_CB_ENABLED。这个标识是默认设置的,如果清除了,evbuffer的提醒并不会引起回调调用。

    接口

    int evbuffer_defer_callbacks(struct evbuffer *buffer, struct event_base *base);
    

    在使用bufferevent的回调的时候,你可以是evbuffer的回调在evbuffer变化的时候不立马执行,但是也仅仅是在一个事件loop内延迟。如果你有很多evbuffer的回调导致数据从一个到另一个的添加或是删除,你可以避免堆栈碎片。

    当一个evbuffer的回调被延迟后,当他们最后被回调的时候,他们会同一总结多个操作。

    和bufferevent一样,evbuffer也是用引用计数,所以及时有延迟的回调还灭有执行,释放evbuffer也是安全的。

    避免使用基于evbuffer IO的数据拷贝

    作为一个网络应用,经常尽可能的避免数据拷贝。libevent提供了一些机制帮助实现这些功能。

    接口

    typedef void (*evbuffer_ref_cleanup_cb)(const void *data,
        size_t datalen, void *extra);
    
    int evbuffer_add_reference(struct evbuffer *outbuf,
        const void *data, size_t datlen,
        evbuffer_ref_cleanup_cb cleanupfn, void *extra);
    

    这个函数通过引用在evbuffer后面添加一块数据。没有数据拷贝,转而替换为把指向的detlen长度的数据指针直接保存到evbuffer。所以指针必须包含evbuffer可以使用的足够长的数据。当evbuffer不需要数据时,调用cleanupfn函数,传入数据指针,数据长度和额外的参数惊醒释放。

    示例

    #include <event2/buffer.h>
    #include <stdlib.h>
    #include <string.h>
    
    /* In this example, we have a bunch of evbuffers that we want to use to
       spool a one-megabyte resource out to the network.  We do this
       without keeping any more copies of the resource in memory than
       necessary. */
    
    #define HUGE_RESOURCE_SIZE (1024*1024)
    struct huge_resource {
        /* We keep a count of the references that exist to this structure,
           so that we know when we can free it. */
        int reference_count;
        char data[HUGE_RESOURCE_SIZE];
    };
    
    struct huge_resource *new_resource(void) {
        struct huge_resource *hr = malloc(sizeof(struct huge_resource));
        hr->reference_count = 1;
        /* Here we should fill hr->data with something.  In real life,
           we'd probably load something or do a complex calculation.
           Here, we'll just fill it with EEs. */
        memset(hr->data, 0xEE, sizeof(hr->data));
        return hr;
    }
    
    void free_resource(struct huge_resource *hr) {
        --hr->reference_count;
        if (hr->reference_count == 0)
            free(hr);
    }
    
    static void cleanup(const void *data, size_t len, void *arg) {
        free_resource(arg);
    }
    
    /* This is the function that actually adds the resource to the
       buffer. */
    void spool_resource_to_evbuffer(struct evbuffer *buf,
        struct huge_resource *hr)
    {
        ++hr->reference_count;
        evbuffer_add_reference(buf, hr->data, HUGE_RESOURCE_SIZE,
            cleanup, hr);
    }
    

    给evbuffer添加一个文件

    有些系统提供了通过网络写文件,避免了用户层的拷贝。如果可行,你可以通过一些简单的接口使用这种机制:

    接口

    int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset,
        size_t length);
    

    evbuffer_add_file()函数假设有一个打开的文件描述符fd,不是socket,可以读。在offset的位置增加length的字节,结束在output。

    警告

    在Libevent 2.0.x中,唯一一个依赖的事情,通过这种方式做数据添加,就是使用evbuffer_write*()发送到网络,通过evbuffer_drain()把数据传输完成,或者通过evbuffer_*_buffer()移动到另一个evbuffer。你不能可靠的通过evbuffer_remove()从buffer中提取出来,并且通过evbuffer_pullup()线性化,等等。Libevent 2.1.x将会试着去修复这个限制。

    如果你的系统支持splice()sendfile(),libevent会使通过evbuffer_write()用它把数据从fd发送到网络,不需要把数据先拷贝到RAM。如果splice/sendfile不存在,但是有mmap(),Libevent会mmap文件,你的内核可以希望的发现不需要把数据拷贝到用户层。否则,libevent会把数据从硬盘拷贝到RAM。

    当数据从evbuffer刷新干净或者evbuffer已经被释放,文件描述符就会被关闭。如果你不想这样做,或者你想细粒度的控制你的文件,可以参考file_segment。

    细粒度控制文件

    evbuffer_add_file()接口对于多次增加同一个文件是无效的,因为它有了文件的所有权。

    接口

    struct evbuffer_file_segment;
    
    struct evbuffer_file_segment *evbuffer_file_segment_new(
            int fd, ev_off_t offset, ev_off_t length, unsigned flags);
    void evbuffer_file_segment_free(struct evbuffer_file_segment *seg);
    int evbuffer_add_file_segment(struct evbuffer *buf,
        struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length);
    

    evbuffer_file_segment_new()创建并返回一个用来替换底层在fd中的从offset开始包含length字节的文件的新的evbuffer_file_segment结构体。出错返回NULL。

    文件片段通过sendfile, splice, mmap, CreateFileMapping, 或者 malloc()-and-read()实现。他们创建使用最轻量级的机制,如果需要转换到重量级的机制。比如,如果系统支持sendfile和mmap,文件片段就是用sendfile,直到你试着检查它的内容。这个时候,你需要mmap()。你可以通过这些标识来控制文件片段的粒度。

    • EVBUF_FS_CLOSE_ON_FREE

    evbuffer_file_segment_free()释放文件片段后会关闭底层文件。

    • EVBUF_FS_DISABLE_MMAP

    禁止使用内存拷贝机制。

    • EVBUF_FS_DISABLE_SENDFILE

    禁止使用sendfile机制

    • EVBUF_FS_DISABLE_LOCKING

    在使用文件片段的时候不会加锁

    如果你有evbuffer_file_segment,你可以通过evbuffer_add_file_segment()把一个或是多个添加到evbuffer中。offset在这里指向文件片段的offset,并不是文件本身。

    如果你不想使用一个文件片段了,可以调用evbuffer_file_segment_free()释放,存储空间知道没有evbuffer保持使用的时候才会真的释放。

    接口

    typedef void (*evbuffer_file_segment_cleanup_cb)(
        struct evbuffer_file_segment const *seg, int flags, void *arg);
    
    void evbuffer_file_segment_add_cleanup_cb(struct evbuffer_file_segment *seg,
            evbuffer_file_segment_cleanup_cb cb, void *arg);
    

    你可以为文件片段增加回调函数,当最后的文件片段被释放的时候,会被调用。这个回调不能还原文件片段或是把它添加到任何缓冲区中。

    把evbuffer添加到另一个引用

    你可以把一个evbuffer添加到另一个引用,而避免把内容从一个buffer移动到另一个。

    接口

    int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
        struct evbuffer *inbuf);
    

    evbuffer_add_buffer_reference()行为就相当于你把数据从outbuf拷贝到inbuf,但是并没有任何底层拷贝。

    注意,inbuf中内容的修改并不会反映给outbuf,因为你是把内容添加进去,而不是evbuffer自己。

    注意,你不能嵌套buffer的索引。

    是evbuffer只能添加或是删除

    接口

    int evbuffer_freeze(struct evbuffer *buf, int at_front);
    int evbuffer_unfreeze(struct evbuffer *buf, int at_front);
    

    你可以用这两个函数临时禁用evbuffer前端或是后端的修改。bufferevent底层的代码会使用它们来禁止前端output buffer或是后端input buffer的修改。

  • 相关阅读:
    class(类)和构造函数(原型对象)
    es6中export和export default的区别
    vue混入 (mixin)的使用
    ES6(Module模块化)
    vue-cli3构建和发布 实现分环境打包步骤(给不同的环境配置相对应的打包命令)
    Vue中使用Echarts 脱坑
    Nginx配置详解
    VUE面包屑组件
    更改 pip 默认下载源(pip 配置文件)
    常见免费API接口
  • 原文地址:https://www.cnblogs.com/studywithallofyou/p/13371807.html
Copyright © 2020-2023  润新知