Book: Programming with Libevent(2)--A Libevent Reference Manual(3)

A Libevent Reference Manual

Reference Link

Fast portable non-blocking network programming with Libevent

R7: Evbuffers: utility functionality for buffered IO

Libevent 的 evbuffer 功能实现了一个字节队列,针对将数据添加到末尾和从前面删除它进行了优化。

Evbuffers 通常用于执行缓冲网络 IO 的“缓冲”部分。它们不提供调度 IO 或在 IO 准备好时触发 IO 的功能:这就是 bufferevents 所做的。

除非另有说明,本章中的函数在 event2/buffer.h 中声明。

Creating or freeing an evbuffer

Interface

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

这些函数应该比较清楚:evbuffer_new()分配并返回一个新的空evbufferevbuffer_free()删除其中的一个和所有内容。

这些函数从 Libevent 0.8 开始就存在了。

Evbuffers and Thread-safety

Interface

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()。如果它的锁参数为 NULL,Libevent 使用提供给 evthread_set_lock_creation_callback 的锁创建函数分配一个新锁。否则,它使用参数作为锁。

evbuffer_lock()evbuffer_unlock() 函数分别获取和释放 evbuffer 上的锁。您可以使用它们使一组操作原子化。如果没有在 evbuffer 上启用锁定,这些函数什么都不做。

(请注意,您不需要围绕单个操作调用 evbuffer_lock()evbuffer_unlock() :如果在 evbuffer 上启用了锁定,则单个操作已经是原子的。您只需要在有多个操作时手动锁定 evbuffer需要在没有另一个线程对接的情况下执行。)

这些函数都是在 Libevent 2.0.1-alpha 中引入的。

Inspecting an evbuffer

Interface

size_t evbuffer_get_length(const struct evbuffer *buf);

此函数返回存储在 evbuffer 中的字节数。

它是在 Libevent 2.0.1-alpha 中引入的。

Interface

size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);

此函数返回连续存储在 evbuffer 前面的字节数。evbuffer 中的字节可以存储在多个单独的内存块中;此函数返回当前存储在第一个块中的字节数。

它是在 Libevent 2.0.1-alpha 中引入的。

Adding data to an evbuffer: basics

Interface

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

该函数将DATLEN以字节为单位的数据在2002年底 BUF。成功时返回 0,失败时返回 -1。

Interface

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

这些函数将格式化数据附加到buf的末尾。格式参数和其他剩余参数分别由 C 库函数“printf”和vprintf处理。这些函数返回附加的字节数。

Interface

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

此函数更改缓冲区中的最后一块内存,或添加一个新块,以便缓冲区现在足够大以包含 datlen 字节而无需任何进一步分配。

Examples

/* 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_add()evbuffer_add_printf() 函数是在 Libevent 0.8 中引入的;evbuffer_expand() 出现在 Libevent 0.9 中,而 evbuffer_add_vprintf() 首次出现在 Libevent 1.1 中。

Moving data from one evbuffer to another

为了提高效率,Libevent 优化了将数据从一个 evbuffer 移动到另一个的功能。

Interface

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

evbuffer_add_buffer() 函数将所有数据从src移动到dst的末尾。成功时返回 0,失败时返回 -1。

evbuffer_remove_buffer() 函数将datlen字节从src精确地移动到dst的末尾,尽可能少地复制。如果要移动的字节少于datlen,它会移动所有字节。它返回移动的字节数

我们在 Libevent 0.8 中引入了 evbuffer_add_buffer()evbuffer_remove_buffer() 是 Libevent 2.0.1-alpha 中的新功能。

Adding data to the front of an evbuffer

Interface

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() 相同,只是它们将数据移动到目标缓冲区的前面。

这些函数应谨慎使用,切勿在与 bufferevent 共享的 evbuffer 上使用。它们是 Libevent 2.0.1-alpha 中的新内容。

Rearranging the internal layout of an evbuffer

有时您想查看 evbuffer 前面的前 N ​​个字节数据,并将其视为连续的字节数组。为此,您必须首先确保缓冲区的前端确实是 连续的。

Interface

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

evbuffer_pullup() 函数“线性化” buf的第一个size字节, 根据需要复制或移动它们以确保它们都是连续的并占用相同的内存块。如果size为负,则该函数将整个缓冲区线性化。如果size大于缓冲区中的字节数,则函数返回 NULL。否则,evbuffer_pullup() 返回一个指向 buf 中第一个字节的指针。

使用大尺寸调用 evbuffer_pullup() 可能会很慢,因为它可能需要复制整个缓冲区的内容。

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;
    }
}

Note
调用大小等于 evbuffer_get_contiguous_space() 返回值的 evbuffer_pullup() 不会导致任何数据被复制或移动。

evbuffer_pullup() 函数是 Libevent 2.0.1-alpha 中的新函数:以前版本的 Libevent 始终保持 evbuffer 数据连续,无论成本如何。

Removing data from an evbuffer

Interface

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

evbuffer_remove() 函数将buf 前面的第一个datlen字节复制并删除到内存中的data 中。如果可用字节数少于datlen,则该函数将复制所有字节。失败时返回值为 -1,否则为复制的字节数。

evbuffer_drain() 函数的行为与 evbuffer_remove() 一样,不同之处在于它不复制数据:它只是从缓冲区的前面删除它。成功时返回 0,失败时返回 -1。

Libevent 0.8 引入了 evbuffer_drain()evbuffer_remove() 出现在 Libevent 0.9 中。

Copying data out from an evbuffer

有时您希望在缓冲区开始处获取数据的副本,而不是将其排空。例如,您可能想查看某种完整的记录是否已到达,而无需耗尽任何数据(如 evbuffer_remove 所做的那样),或在内部重新排列缓冲区(如 evbuffer_pullup() 所做的那样)。

Interface

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() 类似,但不会从缓冲区中排出任何数据。即,复制所述第一DATLEN 从前面字节的buf到在存储器数据。如果可用字节数少于datlen,则该函数将复制所有字节。失败时返回值为 -1,否则为复制的字节数。

evbuffer_copyout_from() 函数的行为类似于 evbuffer_copyout(),但不是从缓冲区的前面复制字节,而是从pos 中提供的位置开始复制它们。有关 evbuffer_ptr 结构的信息,请参阅下面的“在 evbuffer 中搜索”。

如果从缓冲区复制数据太慢,请改用 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;
}

evbuffer_copyout() 函数首次出现在 Libevent 2.0.5-alpha 中;evbuffer_copyout_from() 是在 Libevent 2.1.1-alpha 中添加的。

Line-oriented input

Interface

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);

许多 Internet 协议使用基于行的格式evbuffer_readln() 函数从 evbuffer 的前面提取一行,并在新分配的以 NUL 结尾的字符串中返回它。如果n_read_out不是 NULL,* n_read_out设置为返回的字符串中的字节数。如果没有要读取的整行,则该函数返回 NULL。行终止符不包含在复制的字符串中。

evbuffer_readln() 函数理解 4 种行终止格式:

  • EVBUFFER_EOL_LF
    一行的结尾是一个单独的换行符。(这也称为“\n”。它的 ASCII 值是 0x0A。)

  • EVBUFFER_EOL_CRLF_STRICT
    一行的结尾是一个回车,后跟一个换行符。(这也称为“\r\n”。ASCII 值为 0x0D 0x0A)。

  • EVBUFFER_EOL_CRLF
    行尾是一个可选的回车,后跟一个换行符。(换句话说,它是“\r\n”或“\n”。)这种格式在解析基于文本的 Internet 协议时很有用,因为标准通常规定了“\r\n”行终止符,但不符合标准的客户有时只会说“\n”。

  • EVBUFFER_EOL_ANY
    行尾是任意数量的回车和换行字符的任意序列。这种格式不是很有用;它的存在主要是为了向后兼容。

  • EVBUFFER_EOL_NUL
    行尾是一个值为 0 的单字节——即一个 ASCII NUL。

(请注意,如果您使用 event_set_mem_functions() 覆盖默认 malloc,则 evbuffer_readln 返回的字符串将由您指定的 malloc-replacement 分配。)

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);
}

evbuffer_readln() 接口在 Libevent 1.4.14-stable 及更高版本中可用。EVBUFFER_EOL_NUL 已在 Libevent 2.1.1-alpha 中添加。

Searching within an evbuffer

evbuffer_ptr 结构指向 evbuffer 中的一个位置,并包含可用于遍历 evbuffer 的数据。

Interface

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

该POS领域是唯一的公共领域; 其他的不应由用户代码使用。它将 evbuffer 中的位置表示为距开始的偏移量。

Interface

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() 函数扫描缓冲区中是否出现 len字符串what。它返回一个包含字符串位置的 evbuffer_ptr,如果未找到该字符串,则返回 -1。如果提供了 start参数,则它是搜索应该开始的位置;否则,从字符串的开头开始搜索。

evbuffer_search_range() 函数的行为与 evbuffer_search 一样,除了它只考虑在 evbuffer_ptr end之前 发生的事情。

evbuffer_search_eol() 函数将行尾检测为 evbuffer_readln(),但不是复制出该行,而是将 evbuffer_ptr 返回到行尾字符的开头。如果 eol_len_out 为非 NULL,则将其设置为 EOL 字符串的长度。

Interface

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 函数操作 evbuffer_ptr pos在buffer 中的位置。如果如何是EVBUFFER_PTR_SET,指针被移动到的绝对位置的位置在缓冲区内。如果是 EVBUFFER_PTR_ADD,则指针向前移动位置字节。此函数在成功时返回 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;
}

WARNING
任何修改 evbuffer 或其布局的调用都会使所有未完成的 evbuffer_ptr 值无效,并使它们无法安全使用。

这些接口在 Libevent 2.0.1-alpha 中是新的。

Inspecting data without copying it

有时,您希望读取 evbuffer 中的数据而不将其复制出来(如 evbuffer_copyout() 所做的那样),并且不想重新安排 evbuffer 的内部存储器(如 evbuffer_pullup() 所做的那样)。有时您可能想查看 evbuffer 中间的数据。

你可以这样做:

Interface

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() 时,你在vec_o​​ut 中给它一个 evbuffer_iovec 结构数组。数组的长度是n_vec。它设置这些结构,以便每个结构都包含一个指向 evbuffer 内部 RAM ( iov_base )块的指针,以及在该块中设置的内存长度

如果len小于 0,则 evbuffer_peek() 会尝试填充您提供的所有 evbuffer_iovec 结构。否则,它会填充它们,直到它们都被使用,或者至少有len个字节可见。如果该函数可以为您提供您要求的所有数据,它会返回它实际使用的 evbuffer_iovec 结构的数量。否则,它会返回为满足您的要求而需要的数字。

当ptr为 NULL 时,evbuffer_peek() 从缓冲区的开头开始。否则,它从ptr 中给出的指针开始。

Examples

{
    /* 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\n", and pass it to a consume() function. */
    struct evbuffer_ptr ptr;
    struct evbuffer_iovec v[1];
    const char s[] = "start\n";
    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;
    }
}

Notes

  • 修改 evbuffer_iovec 指向的数据会导致未定义的行为。

  • 如果调用任何修改 evbuffer 的函数,则 evbuffer_peek() 产生的指针可能会变得无效。

  • 如果您的 evbuffer 可以在多个线程中使用,请确保在调用 evbuffer_peek() 之前使用 evbuffer_lock() 锁定它,并在使用 evbuffer_peek() 给您的范围完成后解锁它

此功能是 Libevent 2.0.2-alpha 中的新功能。

Adding data to an evbuffer directly

有时你想直接在 evbuffer 中插入数据信息,而不是先将它写入字符数组,然后用 evbuffer_add() 复制它。您可以使用一对高级函数来执行此操作:evbuffer_reserve_space()evbuffer_commit_space()。与 evbuffer_peek() 一样,这些函数使用 evbuffer_iovec 结构来提供对 evbuffer 内部内存的直接访问。

Interface

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 内部空间的指针。它根据需要扩展缓冲区,至少为您提供 size个字节。指向这些范围的指针及其长度将存储在您使用vec传入的向量数组中;n_vec是这个数组的长度

n_vec的值必须至少为 1。如果您只提供一个向量,那么 Libevent 将确保您在单个范围内拥有您请求的所有连续空间,但它可能必须重新排列缓冲区或浪费内存才能做到所以。为了获得更好的性能,请至少提供 2 个向量。该函数返回您请求的空间所需的提供向量的数量。

在您调用 evbuffer_commit_space() 之前,您写入这些向量的数据不是缓冲区的一部分,这实际上使您写入的数据算作在缓冲区中。如果你想提交比你要求的更少的空间,你可以减少任何给定的 evbuffer_iovec 结构中的 iov_len 字段。您还可以传回比给定的更少的向量。evbuffer_commit_space() 函数在成功时返回 0,在失败时返回 -1。

Notes and Caveats

  • 调用任何重新排列 evbuffer 或向其添加数据的函数 evbuffer 将使您从 evbuffer_reserve_space() 获得的指针无效。

  • 在当前的实现中, 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 */

Bad Examples

/* 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);
}

自 Libevent 2.0.2-alpha 以来,这些函数已经存在于它们当前的接口中。

Network IO with evbuffers

Libevent 中 evbuffers 的最常见用例是网络 IO。在 evbuffer 上执行网络 IO 的接口是:

Interface

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读取最多多少字节到buffer的末尾。它在成功时返回读取的字节数,在 EOF 时返回 0,在错误时返回 -1。请注意,该错误可能表示非阻塞操作不会成功;您需要检查 EAGAIN(或 Windows 上的 WSAEWOULDBLOCK)的错误代码。如果howmuch是负数,evbuffer_read() 会尝试猜测自己读取多少。

evbuffer_write_atmost() 函数尝试将缓冲区 前端的多少字节写入套接字fd。它返回写入成功的字节数,失败时返回 -1。和evbuffer_read()一样,你需要检查错误代码,看错误是否真实,或者只是表明非阻塞IO无法立即完成。如果给howmuch一个负值,我们会尝试写入缓冲区的全部内容。

调用 evbuffer_write() 与使用负的howmuch参数调用 evbuffer_write_atmost() 相同:它尝试尽可能多地刷新缓冲区。

在 Unix 上,这些函数应该适用于任何支持读写的文件描述符。在 Windows 上,仅支持套接字。

Note
在使用bufferevents时,不需要调用这些IO函数;bufferevents 代码为您完成。

evbuffer_write_atmost() 函数是在 Libevent 2.0.1-alpha 中引入的。

Evbuffers and callbacks

evbuffers 的用户经常想知道数据何时添加到 evbuffer 或从 evbuffer 中删除。为了支持这一点,Libevent 提供了一个通用的 evbuffer 回调机制。

Interface

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 回调。它接收缓冲区、指向 evbuffer_cb_info 结构的指针和用户提供的参数。evbuffer_cb_info 结构的 orig_size 字段记录了缓冲区大小改变之前有多少字节;它的 n_ added 字段记录了向缓冲区添加了多少字节,其 n_deleted 字段记录了删除了多少字节。

Interface

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 上设置多个回调。添加新回调不会删除旧回调。

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);
}

Note
释放一个非空的 evbuffer 不算作从它排出数据,并且释放一个 evbuffer 不会释放用户提供的回调数据指针

如果您不希望回调在缓冲区上永久活动,您可以删除它(使其永久消失),或禁用它(将其关闭一段时间):

Interface

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_remove_cb() 函数在成功时返回 0,在失败时返回 -1。

evbuffer_cb_set_flags() 函数和 evbuffer_cb_clear_flags() 函数分别在给定的回调中设置或清除给定的标志。目前,仅支持一个用户可见标志: EVBUFFER_CB_ENABLED。默认情况下设置该标志。当它被清除时,对 evbuffer 的修改不会导致调用这个回调。

Interface

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

bufferevent 回调一样,当 evbuffer 更改时,您可以导致 evbuffer 回调不会立即运行,而是被 推迟并作为给定事件库的事件循环的一部分运行。如果您有多个 evbuffer,它们的回调可能会导致数据相互添加和删除,并且您希望避免破坏堆栈,那么这会很有帮助。

如果 evbuffer 的回调被延迟,那么当它们最终被调用时,它们可能会汇总多个操作的结果。

bufferevents 一样,evbuffers内部引用计数的,因此即使它具有尚未执行的延迟回调,释放 evbuffer 也是安全的。

整个回调系统在 Libevent 2.0.1-alpha 中是新的。evbuffer_cb_(set|clear)_flags() 函数从 2.0.2-alpha 开始就已经存在了。

Avoiding data copies with evbuffer-based IO

真正快速的网络编程通常要求尽可能少地复制数据。Libevent 提供了一些机制来帮助解决这个问题。

Interface

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 的末尾。不执行复制:相反,evbuffer 只存储一个指向存储在data 中的datlen字节的指针。因此,只要 evbuffer 正在使用该指针,它就必须保持有效。当 evbuffer 不再需要数据时,它将使用提供的“数据”指针、“数据”值和“额外”指针作为参数调用提供的cleanupfn函数。此函数在成功时返回 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);
}

evbuffer_add_reference() 函数从 2.0.2-alpha 开始就有了接口。

Adding a file to an evbuffer

一些操作系统提供了将文件写入网络的方法而无需将数据复制到用户空间。您可以使用简单的界面访问这些可用的机制:

Interface

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

evbuffer_add_file()函数假定它有一个开放的文件描述符(不是socket,仅此一次!)的fd,可用于阅读。它从文件中添加length个字节,从位置offset开始 ,到output的末尾。成功时返回 0,失败时返回 -1。

WARNING
在 Libevent 2.0.x 中,以这种方式添加的数据唯一可靠的做法是使用 evbuffer_write*() 将其发送到网络,使用 evbuffer_drain() 将其排出,或者使用 evbuffer_*_buffer() 将其移动到另一个 evbuffer。您无法使用 evbuffer_remove() 从缓冲区中可靠地提取它,使用 evbuffer_pullup() 对其进行线性化,等等。Libevent 2.1.x 尝试修复此限制。

如果您的操作系统支持 splice()sendfile(),Libevent 会在调用 evbuffer_write() 时使用它直接将数据从fd发送到网络,而根本不会将数据复制到用户 RAM 中。如果 splice/sendfile 不存在,但您有 mmap(),Libevent 将 mmap 文件,您的内核有望确定它永远不需要将数据复制到用户空间。否则,Libevent 只会将数据从磁盘读入 RAM

数据从 evbuffer 刷新后,或者当 evbuffer 被释放时,文件描述符将被关闭。如果这不是您想要的,或者您想要对文件进行更细粒度的控制,请参阅下面的 file_segment 功能。

这个函数是在 Libevent 2.0.1-alpha 中引入的。

Fine-grained control with file segments

evbuffer_add_file() 接口在多次添加同一个文件时效率很低,因为它需要文件的所有权

Interface

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() 函数创建并返回一个新的 evbuffer_file_segment 对象,以表示存储在fd中的底层文件的一部分,该文件从offset开始并包含长度字节。出错时,它返回NULL。

根据需要,文件段使用 sendfilesplicemmapCreateFileMappingmalloc()-and-read() 实现。它们是使用最轻量级的支撑机制创建的,并根据需要过渡到更重的机制。(例如,如果您的操作系统支持 sendfilemmap,则可以仅使用 sendfile 来实现文件段,直到您尝试实际检查其内容。此时,需要对它进行 mmap() 处理。)您可以控制具有以下标志的文件段的细粒度行为:

  • EVBUF_FS_CLOSE_ON_FREE
    如果设置了此标志,则使用 evbuffer_file_segment_free() 释放文件段将关闭底层文件

  • EVBUF_FS_DISABLE_MMAP
    如果设置了此标志,file_segment永远不会为此文件使用映射内存样式后端CreateFileMapping,mmap),即使这很合适。

  • EVBUF_FS_DISABLE_SENDFILE
    如果设置了此标志,file_segment 将永远不会为此文件使用 sendfile 样式的后端 (sendfile, splice),即使这很合适。

  • EVBUF_FS_DISABLE_LOCKING
    如果设置了此标志,则不会为文件段分配锁:在多个线程可以看到的任何方式中使用它都是不安全的。

一旦有了 evbuffer_file_segment,就可以使用 evbuffer_add_file_segment() 将其部分或全部添加到 evbuffer 中。 这里的offset参数是指文件段内的偏移量,而不是文件本身内的偏移量。

当你不想再使用一个文件段时,你可以用 evbuffer_file_segment_free() 释放它。在 evbuffer 不再持有对文件段的引用之前,不会释放实际存储

Interface

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);

您可以向文件段添加回调函数,该函数将在释放对文件段的最终引用并且文件段即将被释放时调用。此回调不得尝试恢复文件段、将其添加到任何缓冲区等。

这些文件段函数首先出现在 Libevent 2.1.1-alpha 中;evbuffer_file_segment_add_cleanup_cb() 是在 2.1.2-alpha 中添加的。

Adding an evbuffer to another by reference

您还可以通过引用将一个 evbuffer 添加到另一个:不是删除一个缓冲区的内容并将它们添加到另一个缓冲区,而是给一个 evbuffer 一个对另一个的引用,它的行为就好像您已经复制了所有字节

Interface

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

evbuffer_add_buffer_reference() 函数的行为就像您已将所有数据从outbuf复制到inbuf 一样,但不执行任何不必要的复制。如果成功则返回 0,失败时返回 -1。

请注意,对inbuf内容的后续更改不会反映在 outbuf 中:此函数通过引用添加 evbuffer 的当前内容,而不是 evbuffer 本身。

另请注意,您不能嵌套缓冲区引用:已经是一个 evbuffer_add_buffer_reference 调用的outbuf的缓冲区不能是另一个的 inbuf

Making an evbuffer add- or remove-only

Interface

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

您可以使用这些函数暂时禁用evbuffer 前端或后端的更改。bufferevent 代码在内部使用它们来防止意外修改输出缓冲区的前端或输入缓冲区的末尾

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

Obsolete evbuffer functions

evbuffer 接口在 Libevent 2.0 中发生了很大变化。在此之前,每个 evbuffers 都是作为一个连续的 RAM 块实现的,这使得访问效率非常低

event.h 标头用于公开 struct evbuffer 的内部结构。这些不再可用;对于依赖它们工作的任何代码,它们在 1.4 和 2.0 之间变化太大。

要访问 evbuffer 中的字节数,有一个 EVBUFFER_LENGTH() 宏。实际数据可通过 EVBUFFER_DATA() 获得。这些都在 event2/buffer_compat.h 中可用。不过要注意:EVBUFFER_DATA(b)evbuffer_pullup(b, -1) 的别名,它可能非常昂贵。

其他一些已弃用的接口是:

Deprecated Interface

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

evbuffer_readline() 函数的工作方式类似于当前的 evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY)

evbuffer_find() 函数将在缓冲区中搜索字符串的第一次出现,并返回指向它的指针。与 evbuffer_search() 不同,它只能找到第一个字符串。为了与使用此函数的旧代码保持兼容,它现在将整个缓冲区线性化到所定位字符串的末尾

回调接口也不同:

Deprecated Interface

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);

一个 evbuffer 一次只能设置一个回调,所以设置一个新的回调会禁用前一个回调,设置 NULL 的回调是禁用回调的首选方式。

该函数不是获得 evbuffer_cb_info_structure,而是使用 evbuffer 的新旧长度调用。因此,如果 old_len 大于 new_len,则数据被耗尽。如果 new_len 大于 old_len,则添加数据。不可能推迟回调,因此添加和删除从未被批处理到单个回调调用中

这里过时的函数在 event2/buffer_compat.h 中仍然可用。

R8: Connection listeners: accepting TCP connections

evconnlistener 机制为您提供了一种侦听和接受传入 TCP 连接的方法。

本节中的所有函数和类型都在 event2/listener.h 中声明。除非另有说明,否则它们首先出现在 Libevent 2.0.2-alpha 中。

Creating or freeing an evconnlistener

Interface

struct evconnlistener *evconnlistener_new(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    evutil_socket_t fd);
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
    evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
    const struct sockaddr *sa, int socklen);
void evconnlistener_free(struct evconnlistener *lev);

两个 evconnlistener_new*() 函数都分配并返回一个新的连接侦听器对象。连接侦听器使用 event_base 来记录给定侦听器套接字上何时有新的 TCP 连接。当新连接到达时,它会调用您提供的回调函数

在这两个函数中,base参数是一个 event_base,侦听器应该使用它来侦听连接。该CB功能是接收到一个新的连接时要调用的回调; 如果cb为 NULL,则在设置回调之前,将侦听器视为已禁用。在PTR指针将被传递给回调。该 flags 参数控制听者的行为-更多有关信息如下。 backlog参数控制,所述网络堆栈应该允许在任何时间一个还未被接受的状态,以等待挂起连接的最大数目; 有关更多详细信息,请参阅系统的 listen() 函数的文档。如果 backlognegative,Libevent 试图为 backlog 选择一个合适的值;如果为零,Libevent 假定您已经在提供它的套接字上调用了 listen()

这些函数在设置侦听器套接字的方式上有所不同。evconnlistener_new() 函数假定您已经将套接字绑定到要侦听的端口,并且您将套接字作为fd传入 。如果您希望 Libevent 自己分配和绑定到套接字,请调用 evconnlistener_new_bind(),并传入您要绑定到的 sockaddr 及其长度。

Tip
[使用 evconnlistener_new 时,通过使用 evutil_make_socket_nonblocking 或手动设置正确的套接字选项,确保您的侦听套接字处于非阻塞模式。当侦听套接字处于阻塞模式时,可能会发生未定义的行为。]

释放连接侦听器,请将其传递给 evconnlistener_free()

Recognized flags

这些是您可以传递给evconnlistener_new() 函数的flags参数的标志。您可以提供任意数量的这些,或一起提供。

  • LEV_OPT_LEAVE_SOCKETS_BLOCKING
    默认情况下,当连接侦听器接受新的传入套接字时,它会将其设置为非阻塞,以便您可以将其与 Libevent 的其余部分一起使用。如果您不希望出现这种行为,请设置此标志。

  • LEV_OPT_CLOSE_ON_FREE
    如果设置了此选项,连接侦听器会在您释放它时关闭其底层套接字

  • LEV_OPT_CLOSE_ON_EXEC
    如果设置了此选项,则连接侦听器会在底层侦听器套接字上设置 close-on-exec 标志。有关更多信息,请参阅 fcntlFD_CLOEXEC 的平台文档。

  • LEV_OPT_REUSEABLE
    默认情况下,在某些平台上,一旦侦听器套接字关闭,在一段时间过去之前,没有其他套接字可以绑定到同一端口。设置此选项会使 Libevent 将套接字标记为可重用,这样一旦它关闭,就可以打开另一个套接字来侦听同一端口。

  • LEV_OPT_THREADSAFE
    为侦听器分配锁,以便从多个线程使用它是安全的。Libevent 2.0.8-rc 中的新功能。

  • LEV_OPT_DISABLED
    将侦听器初始化为禁用,而不是启用。您可以使用 evconnlistener_enable() 手动打开它。Libevent 2.1.1-alpha 中的新功能。

  • LEV_OPT_DEFERRED_ACCEPT
    如果可能,告诉内核不要宣布套接字已被接受,直到接收到一些数据并且它们准备好读取。如果您的协议不是从客户端传输数据开始,请不要使用此选项 ,因为在这种情况下,此选项有时会导致内核永远不会告诉您有关连接的信息。并非所有操作系统都支持此选项:对于不支持的操作系统,此选项无效。Libevent 2.1.1-alpha 中的新功能。

The connection listener callback

Interface

typedef void (*evconnlistener_cb)(struct evconnlistener * backlog,
    evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);

当接收到新连接时,调用提供的回调函数。该 listener 参数是,接收到连接的连接监听器。该socket的说法是新的socket本身。addrLEN参数是从接收到该连接的地址分别与该地址的长度。的PTR的说法是,传递给evconnlistener_new()的用户提供的指针

Enabling and disabling an evconnlistener

Interface

int evconnlistener_disable(struct evconnlistener *lev);
int evconnlistener_enable(struct evconnlistener *lev);

这些功能暂时禁用或重新启用侦听新连接。

Adjusting an evconnlistener’s callback

Interface

void evconnlistener_set_cb(struct evconnlistener *lev,
    evconnlistener_cb cb, void *arg);

此函数调整现有 evconnlistener回调和回调参数。它是在 2.0.9-rc 中引入的。

Inspecting an evconnlistener

Interface

evutil_socket_t evconnlistener_get_fd(struct evconnlistener *lev);
struct event_base *evconnlistener_get_base(struct evconnlistener *lev);

这些函数分别返回侦听器的关联套接字event_base

evconnlistener_get_fd() 函数首次出现在 Libevent 2.0.3-alpha 中。

Detecting errors

您可以设置一个错误回调,在侦听器上的 accept() 调用失败时通知该回调。如果您遇到错误情况,除非您解决它,否则该情况会锁定进程,这可能很重要。

Interface

typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr);
void evconnlistener_set_error_cb(struct evconnlistener *lev,
    evconnlistener_errorcb errorcb);

如果您使用 evconnlistener_set_error_cb() 在侦听器上设置错误回调,则每次在侦听器上发生错误时都会调用该回调。它将接收侦听器作为其第一个参数,并将该参数作为ptr传递给 evconnlistener_new() 作为其第二个参数。

这个函数是在 Libevent 2.0.8-rc 中引入的。

Example code: an echo server.

Example

#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>

#include <arpa/inet.h>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

static void
echo_read_cb(struct bufferevent *bev, void *ctx)
{
        /* This callback is invoked when there is data to read on bev. */
        struct evbuffer *input = bufferevent_get_input(bev);
        struct evbuffer *output = bufferevent_get_output(bev);

        /* Copy all the data from the input buffer to the output buffer. */
        evbuffer_add_buffer(output, input);
}

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

static void
accept_conn_cb(struct evconnlistener *listener,
    evutil_socket_t fd, struct sockaddr *address, int socklen,
    void *ctx)
{
        /* We got a new connection! Set up a bufferevent for it. */
        struct event_base *base = evconnlistener_get_base(listener);
        struct bufferevent *bev = bufferevent_socket_new(
                base, fd, BEV_OPT_CLOSE_ON_FREE);

        bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);

        bufferevent_enable(bev, EV_READ|EV_WRITE);
}

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

        event_base_loopexit(base, NULL);
}

int
main(int argc, char **argv)
{
        struct event_base *base;
        struct evconnlistener *listener;
        struct sockaddr_in sin;

        int port = 9876;

        if (argc > 1) {
                port = atoi(argv[1]);
        }
        if (port<=0 || port>65535) {
                puts("Invalid port");
                return 1;
        }

        base = event_base_new();
        if (!base) {
                puts("Couldn't open event base");
                return 1;
        }

        /* Clear the sockaddr before using it, in case there are extra
         * platform-specific fields that can mess us up. */
        memset(&sin, 0, sizeof(sin));
        /* This is an INET address */
        sin.sin_family = AF_INET;
        /* Listen on 0.0.0.0 */
        sin.sin_addr.s_addr = htonl(0);
        /* Listen on the given port. */
        sin.sin_port = htons(port);

        listener = evconnlistener_new_bind(base, accept_conn_cb, NULL,
            LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
            (struct sockaddr*)&sin, sizeof(sin));
        if (!listener) {
                perror("Couldn't create listener");
                return 1;
        }
        evconnlistener_set_error_cb(listener, accept_error_cb);

        event_base_dispatch(base);
        return 0;
}

posted @ 2021-09-10 10:28  flybird2008  阅读(4)  评论(0)    收藏  举报  来源