zoukankan      html  css  js  c++  java
  • 【转】libevent学习笔记【使用篇】——7. evbuffer:缓冲IO的实用功能

    转自:http://blog.csdn.net/windeal3203/article/details/52864994 

    原文:http://blog.csdn.net/windeal3203/article/details/52864994 
    译自:http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html

      libevent的`evbuffer`实现了为向后面添加数据和从前面移除数据而优化的字节队列。 
      `evbuffer`用于处理缓冲网络IO的“缓冲”部分。它不提供调度IO或者当IO就绪时触发IO的功能:这是bufferevent的工作。 
      除非特别说明,本章描述的函数都在`event2/buffer.h`中声明。 
      

    1.创建和释放evbuffer

    struct evbuffer *evbuffer_new(void);
    void evbuffer_free(struct evbuffer *buf);
    • 1
    • 2

    这两个函数的功能很简明:evbuffer_new()分配和返回一个新的空evbuffer;而evbuffer_free()释放evbuffer和其内容。 
    这两个函数从libevent 0.8版就存在了。

    2. Evbuffers 和 线程安全

    int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
    void evbuffer_lock(struct evbuffer *buf);
    void evbuffer_unlock(struct evbuffer *buf);
    • 1
    • 2
    • 3

    默认情况下,在多个线程中同时访问evbuffer是不安全的。如果需要这样的访问,可以在evbuffer 上调用evbuffer_enable_locking()。如果lock参数为NULLlibevent会使用evthread_set_lock_creation_callback提供的锁创建函数创建一个锁。否则,libeventlock参数用作锁。 
    evbuffer_lock()evbuffer_unlock()函数分别请求和释放evbuffer上的锁。可以使用这两个函数让一系列操作是原子的。如果evbuffer没有启用锁,这两个函数不做任何操作。 
    (注意:对于单个操作,不需要调用evbuffer_lock()evbuffer_unlock():如果evbuffer启用了锁,单个操作就已经是原子的。只有在需要多个操作连续执行,不让其他线程介入的时候,才需要手动锁定evbuffer
    这些函数都在2.0.1-alpha版本中引入。

    3. 检查evbuffer

    size_t evbuffer_get_length(const struct evbuffer *buf);
    • 1

    该函数返回在evbuffer中保存的字节数。 
    它在Libevent 2.0.1-alpha中引入。

    size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);
    • 1

    这个函数返回连续地存储在evbuffer前面的字节数。evbuffer中的数据可能存储在多个分隔开的内存块中,这个函数返回当前第一个块中的字节数。 
    它在libevent 2.0.1-alpha中引入。

    4 向evbuffer中添加数据:基础

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

    这个函数向buf 的末尾添加 datalen 字节的数据data 。 函数在成功时返回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);
    • 1
    • 2

    这些函数添加格式化的数据到buf末尾。格式参数fmt和其他参数的处理分别与C库函数printfvprintf相同。函数返回添加的字节数。

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

    这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳datlen字节,而不需要更多的内存分配。

    Example

    /* 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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    函数evbuffer_add() 和 evbuffer_add_printf() 在Libevent0.8 中引入。 
    函数evbuffer_expand()在Libevent 0.9引入, 而evbuffer_add_vprintf() 在Libevent 1.1中第一次出现。

    5 从一个evbuffer 向另一个evbuffer移动数据。

    为提高效率,libevent具有将数据从一个evbuffer移动到另一个的优化函数。

    int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src);
    int evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
        size_t datlen);
    • 1
    • 2
    • 3

    函数evbuffer_add_buffer()src中的所有数据移动到dst末尾,成功时返回0,失败时返回-1。 
    evbuffer_remove_buffer()函数从src中移动datlen字节到dst末尾,尽量少进行复制。如果字节数小于datlen,所有字节被移动。函数返回移动的字节数。 
    evbuffer_add_buffer()在0.8版本引入;evbuffer_remove_buffer()是2.0.1-alpha版本新增加的。

    6. 添加数据到evbuffer前面

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

    除了将数据移动到目标缓冲区前面不同外,这两个函数的行为分别与evbuffer_add()evbuffer_add_buffer()相同。 
    使用这些函数时要当心,永远不要对与bufferevent共享的evbuffer使用。 
    这些函数是2.0.1-alpha版本新添加的。

    7. 重新排列evbuffer的内部布局

    有时候需要取出evbuffer前面的N字节,将其看作连续的字节数组。要做到这一点,首先必须确保缓冲区的前面确实是连续的。

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

    evbuffer_pullup()函数“线性化”buf前面的size字节,必要时将进行复制或者移动,以保证这些字节是连续的,占据相同的内存块。如果size是负的,函数会线性化整个缓冲区。如果size大于缓冲区中的字节数,函数返回NULL。否则,evbuffer_pullup()返回指向buf中首字节的指针。 
    调用evbuffer_pullup()时使用较大的size参数可能会非常慢,因为这可能需要复制整个缓冲区的内容。

    Example

    #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;
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    注意: 
    使用 与 evbuffer_get_contiguous_space() 的返回值相等的大小 来调用evbuffer_pullup()不会导致任何数据复制或者移动。 
    evbuffer_pullup()函数由2.0.1-alpha版本新增加:先前版本的libevent总是保证evbuffer中的数据是连续的,而不计开销。

    8. 从evbuffer中删除数据

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

    evbuffer_remove()函数从buf前面复制和移除datlen字节到data内存中。如果可用字节少于datlen,函数复制所有字节。函数失败时返回-1,否则返回复制了的字节数。 
    evbuffer_drain()函数的行为与evbuffer_remove()相同,只是它不进行数据复制:而只是将数据从缓冲区前面移除。成功时返回0,失败时返回-1。 
    evbuffer_drain()由Libevent 0.8版引入,evbuffer_remove()在Libevent0.9版首次出现。

    9. 从evbuffer中拷贝出数据

    有时候需要获取缓冲区前面数据的副本,而不清除数据。比如说,可能需要查看某特定类型的记录是否已经完整到达,而不清除任何数据(像evbuffer_remove那样),或者在内部重新排列缓冲区(像evbuffer_pullup那样)。

    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);
    • 1
    • 2
    • 3
    • 4

    evbuffer_copyout()的行为与evbuffer_remove() 类似,但是它不从缓冲区移除任何数据。也就是说,它从buf前面复制datlen字节到data处的内存中。如果可用字节少于datlen,函数会复制所有字节。失败时返回-1,否则返回复制的字节数。 
    evbuffer_copyout_from()的行为与evbuffer_copyout()有些类似, 不同的是evbuffer_copyout_from() 从制定的位置pos 开始复制, 而不是buffer的起始位置。 从下文“在evbuffer中搜索”一节可以获取evbuffer_ptr结构体相关的信息。 
    如果从缓冲区复制数据太慢,可以使用evbuffer_peek()。 
    Example:

    #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;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    evbuffer_copyout() 在Libevent 2.0.5-alpha中第一次出现;evbuffer_copyout_from()在 Libevent 2.1.1-alpha.中加入。

    10. 面向行的输入

    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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    很多互联网协议使用基于行的格式。evbuffer_readln()函数从evbuffer前面取出一行,用一个新分配的空字符结束的字符串返回这一行。如果n_read_out不是NULL,则它被设置为返回的字符串的字节数。如果没有整行供读取,函数返回空。返回的字符串不包括行结束符。 
    evbuffer_readln()理解4种行结束符号:

    • EVBUFFER_EOL_LF: 
      行尾是单个换行符(也就是 ,ASCII值是0x0A
    • EVBUFFER_EOL_CRLF_STRICT: 
      行尾是一个回车符,后随一个换行符(也就是 ,ASCII值是0x0D 0x0A
    • EVBUFFER_EOL_CRLF: 
      行尾是一个可选的回车,后随一个换行符(也就是说,可以是 或者 )。这种格式对于解析基于文本的互联网协议很有用,因为标准通常要求 的行结束符,而不遵循标准的客户端有时候只使用
    • EVBUFFER_EOL_ANY: 
      行尾是任意数量、任意次序的回车和换行符。这种格式不是特别有用。它的存在主要是为了向后兼容。 
      (注意,如果使用event_se_mem_functions()覆盖默认的malloc,则evbuffer_readln返回的字符串将由你指定的malloc替代函数分配).

    *Example:

    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);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    evbuffer_readln()在 Libevent 1.4.14-stable之后的版本中可用. EVBUFFER_EOL_NUL 在Libevent 2.1.1-alpha.中加入。

    11. 在evbuffer中搜索

    evbuffer_ptr结构体指示evbuffer中的一个位置,包含可用于在evbuffer中迭代的数据。

    struct evbuffer_ptr {
            ev_ssize_t pos;
            struct {
                    /* internal fields */
            } _internal;
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    pos是唯一的公有字段,用户代码不应该访问其他字段。pos指示evbuffer中的一个位置,以到开始处的偏移量表示。

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    evbuffer_search()函数在缓冲区中查找含有len个字符的字符串what。函数返回包含字符串位置的evbuffer_ptr结构体,或者在没有找到字符串时返回-1。如果提供了start参数,则从指定的位置开始搜索;否则,从开始处进行搜索。 
    evbuffer_search_range()函数和evbuffer_search行为类似,只是它只考虑在end之前出现的what。 
    evbuffer_search_eol()函数像evbuffer_readln()一样检测行结束,但是不复制行,而是返回指向行结束符的evbuffer_ptr。如果eol_len_out非空,则它被设置为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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    evbuffer_ptr_set()函数操作buffer中的位置pos。如果how等于EVBUFFER_PTR_SET,指针被移动到缓冲区中的绝对位置position;如果等于EVBUFFER_PTR_ADD,则向前移动position字节。函数成功时返回0,失败时返回-1。

    Example:

    #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;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    警告: 
    任何修改evbuffer或者其布局的调用都会使得evbuffer_ptr失效,不能再安全地使用。 
    这些接口是2.0.1-alpha版本新增加的。

    12. 检测数据而不复制

    有时候需要读取evbuffer中的数据而不进行(像evbuffer_copyout()那样的)复制,也不重新排列内部内存布局(像evbuffer_pullup()那样)。有时候可能需要查看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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    调用evbuffer_peek()的时候,通过vec_out给定一个evbuffer_iovec数组,数组的长度是n_vec。函数会让每个结构体包含指向evbuffer内部内存块的指针(iov_base)和块中数据长度。 
    如果len小于0,evbuffer_peek()会试图填充所有evbuffer_iovec结构体。否则,函数会进行填充,直到使用了所有结构体,或者见到len字节为止。如果函数可以给出所有请求的数据,则返回实际使用的结构体个数;否则,函数返回给出所有请求数据所需的结构体个数。 
    如果ptrNULL,函数从缓冲区开始处进行搜索。否则,从ptr处开始搜索。

    Example:

    {
        /* 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;
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    注意:

    • 修改evbuffer_iovec所指的数据会导致不确定的行为
    • 如果任何函数修改了evbuffer,则evbuffer_peek()返回的指针会失效
    • 如果在多个线程中使用evbuffer,确保在调用evbuffer_peek()之前使用evbuffer_lock(),在使用完evbuffer_peek()给出的内容之后进行解锁

    这个函数是2.0.2-alpha版本新增加的。

    13. 直接向evbuffer添加数据

    有时候需要能够直接向evbuffer添加数据,而不用先将数据写入到字符数组中,然后再使用evbuffer_add()进行复制。有一对高级函数可以完成这种功能:evbuffer_reserve_space()evbuffer_commit_space()。跟evbuffer_peek()一样,这两个函数使用evbuffer_iovec结构体来提供对evbuffer内部内存的直接访问。

    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);
    • 1
    • 2
    • 3
    • 4

    evbuffer_reserve_space()函数给出evbuffer内部空间的指针。函数会扩展缓冲区以至少提供size字节的空间。到扩展空间的指针,以及其长度,会存储在通过vec传递的向量数组中,n_vec是数组的长度。 
    n_vec的值必须至少是1。如果只提供一个vector,libevent会确保请求的所有连续空间都在单个扩展区中,但是这可能要求重新排列缓冲区,或者浪费内存。为取得更好的性能,应该至少提供2个向量。函数返回提供请求的空间所需的向量数。 
    写入到向量中的数据不会是缓冲区的一部分,直到调用evbuffer_commit_space(),使得写入的数据进入缓冲区。如果需要提交少于请求的空间,可以减小任何evbuffer_iovec结构体的iov_len字段,也可以提供较少的向量。函数成功时返回0,失败时返回-1。 
    提示和警告:

    • 调用任何重新排列evbuffer或者向其添加数据的函数都将使之前从evbuffer_reserve_space()获取的指针失效。
    • 当前实现中,不论用户提供多少个向量,evbuffer_reserve_space()从不使用多于两个。这一点在未来版本可能会有变化。
    • 如果在多个线程中使用evbuffer,确保在调用evbuffer_reserve_space()之前使用evbuffer_lock()进行锁定,然后在提交后解除锁定。

    Example:

    /* 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 */
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    Bad Example:

    /* 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);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    这个函数及其提出的接口从2.0.2-alpha版本就存在了。

    14. 使用evbuffer的网络IO

    libevent中evbuffer的最常见使用场合是网络IO。将evbuffer用于网络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);
    • 1
    • 2
    • 3
    • 4

    evbuffer_read()函数从套接字fd读取至多howmuch字节到buffer末尾。函数成功时返回读取的字节数,0表示EOF,失败时返回-1。注意,错误码可能指示非阻塞操作不能立即成功,应该检查错误码EAGAIN(或者Windows中的WSAWOULDBLOCK)。如果howmuch为负,evbuffer_read() 会尝试猜测要读取多少数据。 
    evbuffer_write_atmost()函数试图将buffer前面至多howmuch字节写入到套接字fd中。成功时函数返回写入的字节数,失败时返回-1。跟evbuffer_read()一样,应该检查错误码,看是真的错误,还是仅仅指示非阻塞IO不能立即完成。如果为howmuch给出负值,函数会试图写入buffer的所有内容。 
    调用evbuffer_write()与使用负的howmuch参数调用evbuffer_write_atmost()一样:函数会试图尽量清空buffer的内容。 
    在Unix中,这些函数应该可以在任何支持readwrite的文件描述符上正确工作。在Windows中,仅仅支持套接字。 
    注意,如果使用bufferevent,则不需要调用这些函数,bufferevent的代码已经为你调用了。 
    evbuffer_write_atmost()函数在2.0.1-alpha版本中引入。

    15. evbuffer和回调

    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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    回调函数在 有数据被添加到evbuffer中,或者从中移除数据的时候,会被调用。函数收到缓冲区指针、一个evbuffer_cb_info结构体指针,和用户提供的参数。evbuffer_cb_info结构体的orig_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);
    • 1
    • 2
    • 3

    evbuffer_add_cb()函数为evbuffer添加一个回调函数,返回一个不透明的指针,随后可用于代表这个特定的回调实例。cb参数是将被调用的函数,cbarg是用户提供的将传给这个函数的指针。 
    可以为单个evbuffer设置多个回调,添加新的回调不会移除原来的回调。

    Example:

    #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);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    注意:释放非空evbuffer不会清空其数据,释放evbuffer也不会为回调释放用户提供的数据指针。 
    如果不想让缓冲区上的回调永远激活,可以移除或者禁用回调:

    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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以通过添加回调时候获取的evbuffer_cb_entry来移除回调,也可以通过回调函数和参数指针来移除。成功时函数返回0,失败时返回-1。 
    evbuffer_cb_set_flags()evbuffer_cb_clear_flags()函数分别为回调函数设置或者清除给定的标志。当前只有一个标志是用户可见的:EVBUFFER_CB_ENABLED。这个标志默认是打开的。如果清除这个标志,对evbuffer的修改不会调用回调函数。

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

    bufferevent回调一样,可以让evbuffer回调不在evbuffer被修改时立即运行,而是延迟到某event_base的事件循环中执行。如果有多个evbuffer,它们的回调潜在地让数据添加到evbuffer中,或者从中移除,又要避免栈崩溃,延迟回调是很有用的。 
    如果回调被延迟,则最终执行时,它可能是多个操作结果的总和。 
    bufferevent一样,evbuffer具有内部引用计数的,所以即使还有未执行的延迟回调,释放evbuffer也是安全的。 
    整个回调系统是2.0.1-alpha版本新引入的。evbuffer_cb_(set|clear)_flags()函数从2.0.2-alpha版本开始存在。

    16. 为基于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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个函数通过引用向evbuffer末尾添加一段数据。不会进行复制:evbuffer只会存储一个到data处的datlen字节的指针。因此,在evbuffer使用这个指针期间,必须保持指针是有效的。evbuffer会在不再需要这部分数据的时候调用用户提供的cleanupfn函数,带有提供的data指针、datlen值和extra指针参数。函数成功时返回0,失败时返回-1。

    Example:

    #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);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    evbuffer_add_reference() 从2.0.2-alpha.开始引入。

    17. 向evbuffer添加文件

    一些操作系统提供了将文件写入到网络,而不需要将数据复制到用户空间的方法。如果存在,可以使用下述接口访问这种机制:

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

    evbuffer_add_file()要求一个打开的可读文件描述符fd(注意:不是套接字)。函数将文件中offset处开始的length字节添加到output末尾。成功时函数返回0,失败时返回-1。 
    *注意: 
    在2.0.2-alpha版中,对于使用这种方式添加的数据的可靠操作只有:通过evbuffer_write*()将其发送到网络、使用evbuffer_drain()清空数据,或者使用evbuffer_*_buffer()将其移动到另一个evbuffer中。不能使用evbuffer_remove()取出数据,或使用evbuffer_pullup()进行线性化等。 
    如果操作系统支持splice()或者sendfile(),则调用evbuffer_write()时libevent会直接使用这些函数来将来自fd的数据发送到网络中,而根本不将数据复制到用户内存中。如果不存在splice()sendfile(),但是支持mmap(),libevent将进行文件映射,而内核将意识到永远不需要将数据复制到用户空间。否则,libevent会将数据从磁盘读取到内存。 
    清空数据或者释放evbuffer时文件描述符将被关闭。 
    这一节描述的函数都在2.0.1-alpha版本中引入。

    18. 文件片段的细粒度控制

    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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

      evbuffer_file_segment_new() 创建并返回一个新的 evbuffer_file_segment 对象来表示保存在fd 对应的文件中从offset 开始的length字节的片段。 函数在错误时,返回NULL。

    文件段通过sendfilesplicemmapCreateFileMappingmalloc()-and_read()中合适的函数进行实现。. 它们使用系统支持的最轻量级的机制进行创建,并且在需要的时候会过渡到重量级的机制上。(比如,如果系统支持sendfilemmap,则会仅使用sendfile实现文件段,直到真正需要检查文件段内容时,在这一刻,会使用mmap),可以通过下列标志更加细粒度的控制文件段的行为:

    • EVBUF_FS_CLOSE_ON_FREE: 
      如果设置了该标志,则通过函数evbuffer_file_segment_free释放文件段将会关闭底层文件。
    • EVBUF_FS_DISABLE_MMAP: 
      如果设置了该标志,则文件段将永远不会使用mmap类型的后端(CreateFileMappingmmap),即使它们非常合适。
    • EVBUF_FS_DISABLE_SENDFILE: 
      如果设置了该标志,则文件段将永远不会使用sendfile类型的后端(sendfilesplice),即使它们非常合适。
    • EVBUF_FS_DISABLE_LOCKING: 
      如果设置了该标志,则不会在文件段上分配锁:在多线程环境中使用文件段将是不安全的。

      一旦得到一个evbuffer_file_segment结构,则可以使用evbuffer_add_file_segment函数将其中的一部分或者所有内容添加到evbuffer中。这里的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);
    • 1
    • 2
    • 3
    • 4
    • 5

    可以在文件段上添加一个回调函数,当文件段的最后一个引用被释放,并且文件段被释放时,该回调函数被调用。该回调函数决不能在试图重新将该文件段添加到任何buffer上。

    这些文件段函数第一次出现在Libevent 2.1.1-alpha; evbuffer_file_segment_add_cleanup_cb() 在 2.1.2-alpha中加入

    19. 通过引用将evbuffer添加到另一个evbufer中

    也可以通过引用将evbuffer添加到另一个evbuffer中:而不是移动一个evbuffer中内容到另一个evbuffer中,当将evbuffer的引用添加到另一个evbuffer中时,它的行为就好像复制了所有字节一样。

    int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
        struct evbuffer *inbuf);
    • 1
    • 2

    evbuffer_add_buffer_reference()函数的行为就好像你已经从 outbuf复制了所有字节到 inbuf, 但实际上并没有进行任何不必要的拷贝。 .函数在成功是返回0, 失败时返回-1.

    注意,inbuf内容后续的变化将不会反馈到outbuf中:该函数是通过引用添加evbuffer当前的内容,而不是evbuffer本身。

    注意,不能嵌套buffer的引用:如果一个evbuffer是evbuffer_add_buffer_reference函数中的outbuf,则其不能作为另一个的inbuf。

    这个函数在Libevent 2.1.1-alpha中引进。

    20.使一个evbuffer仅能添加或者仅能移除

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

    可以使用这些函数暂时性的禁止evbuffer前端或后端的改变。bufferevent会在内部使用这些函数,用来防止输出缓冲区前端,或者输入缓冲区后端的意外改变。

    evbuffer_freeze() 函数在Libevent 2.0.1-alpha引入。

    21. 废弃的函数

    在Libevent2.0中,evbuffer的接口发生了很多变化。在此之前,每一个evbuffer都是通过一连续的内存块实现的,这使得访问非常低效。

    event.h头文件以前会暴露evbuffer结构的内部,但是现在不会了;在1.4到2.0之前,任何依赖于它们进行工作的代码都被改变了。

    为了访问evbuffer中的字节数,使用EVBUFFER_LENGTH宏,使用EVBUFFER_DATA()宏得到实际的数据。这些宏都定义在event2/buffer_compat.h中。小心,EVBUFFER_DATA(b)evbuffer_pullup(b,-1)的别名,它是非常昂贵的。

    一些其它的不推荐再使用的接口:

    char *evbuffer_readline(struct evbuffer *buffer);
    unsigned char *evbuffer_find(struct evbuffer *buffer,
        const unsigned char *what, size_t len);
    • 1
    • 2
    • 3

    evbuffer_readline函数类似于当前的evbuffer_readln(buffer,NULL, EVBUFFER_EOL_ANY)。 
    evbuffer_find()函数会寻找bufferwhat字符串的第一次出现,并且返回指向它的指针。不像evbuffer_search,它只会寻找第一个匹配的字符串。为了兼容仍使用该函数的老代码,它现在会将直到本地字符串的结尾的整个buffer进行线性化。

    回调接口也变得不同: 
    下面是废弃的回调接口:

    typedef void (*evbuffer_cb)(struct evbuffer *buffer,
        size_t old_len, size_t new_len, void *arg);
    void evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg);
    • 1
    • 2
    • 3

    同一时刻一个evbuffer只能有一个回调函数,所以设置新的回调函数会移除前一个回调,并且设置回调为NULL,则是移除回调函数的首选方法。 
    与取得evbuffer_cb_info_structure结构不同,该函数以evbuffer的旧长度和新长度来调用。因此,如果old_len大于new_len,则数据被抽取,如果new_len大于old_len,则数据被添加。不能将回调延迟,所以,在一次回调中不能将添加和删除进行批量化。

    这些废弃的函数在event2/buffer_compat.h.中依然可用。

  • 相关阅读:
    斐波那契数列 的两种实现方式(Java)
    单链表反转
    单链表合并
    两个有序list合并
    list去重 转载
    RemoveAll 要重写equals方法
    Java for LeetCode 138 Copy List with Random Pointer
    Java for LeetCode 137 Single Number II
    Java for LeetCode 136 Single Number
    Java for LeetCode 135 Candy
  • 原文地址:https://www.cnblogs.com/cxt-janson/p/10856648.html
Copyright © 2011-2022 走看看