Libevent源码阅读——API简介

重新读下Libevent的源码,使用最新的Libevent版本libevent-release-2.1.8-stable,本篇主要是Libevent event_base的设置、Libevent 事件循环的启动和停止、Libevent事件的创建和处理、Libevent http服务相关API的介绍

基础API

日志打印回调设置API event_set_log_callback

Libevent记录内部的错误和警告日志,如果编译了日志支持功能,也会记录调试信息,日志信息默认输出到stderr,可以通过提供自己的日志函数的方法来覆盖该行为。调用event_set_log_callback()传入event_log_cb类型的函数改变默认行为,传入NULL置为默认行为。默认调试日志是禁止的,可以通过event_enable_debug_logging()函数打开调试(DEBUG)日志,EVENT_DBG_NONE为默认行为,EVENT_DBG_ALL打开所有支持的调试日志。

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
//Libevent日志登记
#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG 1
#define EVENT_LOG_WARN 2
#define EVENT_LOG_ERR 3

//severity libevent日志等级
//msg libevent日志信息
typedef void (*event_log_cb)(int severity, const char *msg);

//log_fn libevent默认的日志回调函数
static event_log_cb log_fn = NULL;

//设置libevent新的日志回调函数
void event_set_log_callback(event_log_cb cb)
{
log_fn = cb;
}

#define EVENT_DBG_ALL 0xffffffffu
#define EVENT_DBG_NONE 0

void event_enable_debug_logging(ev_uint32_t which)
{
event_debug_logging_mask_ = which;
}

Github示例代码

致命错误退出回调设置API event_set_fatal_callback

1
2
3
4
5
6
7
8
typedef void (*event_fatal_cb)(int err);

static event_fatal_cb fatal_fn = NULL;

void event_set_fatal_callback(event_fatal_cb cb)
{
fatal_fn = cb;
}

Libevent在遇到致命错误时默认调用exit()或abort()退出当前进程,Libevent提供接口在其退出前会调用一次。

更换内存管理函数的API event_set_mem_functions

1
2
3
4
5
6
7
8
9
10
11
12
static void *(*mm_malloc_fn_)(size_t sz) = NULL;
static void *(*mm_realloc_fn_)(void *p, size_t sz) = NULL;
static void (*mm_free_fn_)(void *p) = NULL;

void event_set_mem_functions(void *(*malloc_fn)(size_t sz),
void *(*realloc_fn)(void *ptr, size_t sz),
void (*free_fn)(void *ptr))
{
mm_malloc_fn_ = malloc_fn;
mm_realloc_fn_ = realloc_fn;
mm_free_fn_ = free_fn;
}

默认情况下Libevent会使用原生的malloc、calloc、realloc、free。
注意:更换内存管理函数将会影响LibEvent后续所有调用allocate、resize和free内存的函数.因此你需要确保在LibEvent调用其它函数之前替换掉这些函数.否则LibEvent将会调用你提供的free函数来释放从C语言库版本的malloc分配的内存.

  • 你的malloc和realloc函数需要返回和C语言库相同的内存对齐.
  • 你的realloc函数需要正确处理realloc(NULL,sz),也就是说当做(malloc(sz)处理).
  • 你的realloc函数需要正确处理realloc(ptr,0),也就是说当做free(ptr)处理.
  • 你的free函数不必去处理free(NULL).
  • 你的malloc函数不必去处理malloc(0).
  • 如果你不止一个线程使用LibEvent,那么你提供的的内存管理替代函数必须是线程安全的.

Github示例代码

释放LibEvent全局结构体 libevent_global_shutdown

当进程退出时所有内存都会被释放,但残留的结构体会导致某些调试工具认为Libevent存在内存泄露,使用libevent_global_shutdown可以释放所有库内部的全局数据结构。但libevent_global_shutdown不会释放返回到Libevent外部的结构体,如events、event_base、bufferevents等。
调用libevent_global_shutdown()函数将会使得别的LibEvent的函数产生不可预知的行为.除了程序调用了最后一个LibEvent的函数否则不要调用它。
Github示例代码

其他

以下设置请看官方文档或Libevent中文帮助手册

  1. 线程和锁
  2. 调试锁的使用
  3. 调试事件的使用
  4. Libevent版本的检查

event_base创建

默认的event_base

event_base_new()函数分配和返回一个默认参数的event_base,event_base_new()函数检查环境变量,然后分配一个指向新的event_base的指针,如果错误,返回NULL。

1
struct event_base *event_base_new(void);

复杂的event_base

创建复杂的event_base需要传入event_config。event_config通过event_config_new获得。

1
struct event_config *event_config_new(void);

获得event_config后对其进行设置,然后调用event_base_new_with_config()创建event_base。

1
struct event_base *event_base_new_with_config(const struct event_config *cfg);

使用完event_config后需要调用event_config_free()释放event_config。

1
void event_config_free(struct event_config *cfg);

event_config的设置需要调用别的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int event_config_avoid_method(struct event_config *cfg, const char *method);

enum event_method_feature {
EV_FEATURE_ET = 0x01,
EV_FEATURE_O1 = 0x02,
EV_FEATURE_FDS = 0x04,
EV_FEATURE_EARLY_CLOSE = 0x08
};
int event_config_require_features(struct event_config *cfg, int features);

enum event_base_config_flag {
EVENT_BASE_FLAG_NOLOCK = 0x01,
EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};
int event_config_set_flag(struct event_config *cfg, int flag);

event_config_avoid_method()可通过名字让libevent避免使用特定的可用后端。
event_config_require_features()可让libevent不使用不能提供所有指定特征的后端。
event_config_set_flag()让libevent在创建event_base时设置一个或多个运行时标志。

event_config_require_features()

可识别的选项值有:

  1. EV_FEATURE_ET 要求支持ET模式的后端(边沿触发)
  2. EV_FEATURE_O1 要求添加、删除单个事件,或者确定哪个事件激活的操作时O(1)复杂度的后端
  3. EV_FEATURE_FDS 要求支持任意文件描述符,而不仅仅是套接字的后端
  4. EV_FEATURE_EARLY_CLOSE 要求后台方法可以使用EV_CLOSED检测链接关闭,而不需要读完所有未决数据才能判断 支持EV_CLOSED的后台方法不是所有OS内核都支持的

设置成功返回0,失败返回-1

event_config_set_flag()

可识别的选项值有:

  1. EVENT_BASE_FLAG_NOLOCK 不要为 event_base分配锁.设置这个选项可以为event_base节省一点用于锁定和解锁的时间,但是让在多个线程中访问 event_base成为不安全的
  2. EVENTBASE_FLAG_IGNORE_ENV 选择使用的后端时,不要检测``EVENT*``环境变量.
  3. EVENT_BASE_FLAG_STARTUP_IOCP 仅用于 Windows,让 libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用
  4. EVENT_BASE_FLAG_NO_CACHE_TIME 不是在事件循环每次准备执行超时回调时检测当前时间,而是在每次超时回调后进行检测.注意:这会消耗更多的CPU时间
  5. EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST 如果决定使用epoll后端,可以安全地使用更快的基于 changelist的后端.epoll-changelist后端可以在后端的分发函数调用之间,同样的fd多次修改其状态的情况下,避免不必要的系统调用.但是如果传递任何使用 dup ()或者其变体克隆的 fd给libevent, epoll-changelist后端会触发一个内核bug,导致不正确的结果.在不使用epoll后端的情况下,这个标志是没有效果的.也可以通过设置 EVENT_EPOLL_USE_CHANGELIST:环境变量来打开epoll-changelist选项.
  6. EVENT_BASE_FLAG_PRECISE_TIMER 使用更加精确的定时机制

设置成功返回0,失败返回-1

获得特定event_base的配置信息

1
2
const char *event_base_get_method(const struct event_base *base);
int event_base_get_features(const struct event_base *base);

event_base_get_method返回一个指针,指向event_base所选择的后端的名称
event_base_get_features返回event_base所选后端支持的特征值的比特掩码

示例代码

运行循环

1
2
3
4
5
#define EVLOOP_ONCE	0x01
#define EVLOOP_NONBLOCK 0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
int event_base_loop(struct event_base *base, int flags);
int event_base_dispatch (struct event_base *base);

event_base_loop会运行一个event_base直到没有event注册进来,循环运行,不断重复判断是否有注册的event触发。
flags标记可改变event_base_loop的行为:

  1. EVLOOP_ONCE 循环将等待某些事件成为激活的,执行激活的事件直到没有更多的事件可以执行,然会返回
  2. EVLOOP_NONBLOCK:循环不等待事件被触发,循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调。
  3. EVLOOP_NO_EXIT_ON_EMPTY:没有事件仍不退出,而是由其他函数触发退出

event_base_dispatch采用默认的配置调用event_base_loop();

1
2
3
4
int event_base_dispatch(struct event_base *event_base)
{
return (event_base_loop(event_base, 0));
}

停止循环

1
2
3
4
int event_base_loopexit(struct event_base *event_base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *event_base);
int event_base_got_break(struct event_base *event_base);
int event_base_got_exit(struct event_base *event_base);

event_base_loopexit()

要求event_base在指定时间后停止,如果tv为NULL,则立即停止。但该函数实际会使部分event_base在执行完全部的callback之后才返回。

event_base_loopbreak()

要求event_base立即停止,无视其他的active事件而停止。如果当前没有callback,则会导致event_base等到执行完下一个callback之后才退出。

event_base_got_break() 和 event_base_got_exit()

event_base_got_break 如果循环因为event_base_loopbreak()退出,event_base_got_break返回true,否则返回false
event_base_got_exit 如果循环因为event_base_loopexit()退出,event_base_got_exit返回true,否则返回false

示例代码

event

event简介

libevent的基本操作单元是事件event,每个事件代表一组条件:

  1. 文件描述符已经就绪,可以读取或者写入
  2. 文件描述符变为就绪状态,可以读取或者写入(仅对于边沿触发IO)
  3. 超时事件
  4. 信号
  5. 用户手动触发

当一个event被设置好,并且关联到一个event_base里面时,它被称为“initialized”。此时你可以执行add,这使得它进入pending(等待、未决的)状态。当event被触发或超时时,它的状态称为active,这个情况下对应的callback会被调用。如果event被配置为persist,那么它在callback执行前后都会保持pending的状态。可以通过delete来使得一个event从pending状态重新变成nonpending。

event API 介绍

1
2
3
4
5
6
7
8
9
typedef void (*event_callback_fn)(evutil_socket_t, short, void *);

struct event * event_new(struct event_base *base, \
evutil_socket_t fd, \
short events, \
void (*cb)(evutil_socket_t, short, void *), \
void *arg);

void event_free(struct event *ev);

event创建 event_new

event_new 创建一个新的event。其中fd是文件描述符,需要自行初始化之后再作为参数传入。event_free()释放event的资源。如果event是active或者是pending状态,则函数会将event先变成非active且非pending的状态,然后再释放它。
参数events表示event的需要关注绑定该fd上的哪些事件。

  1. EV_TIMEOUT:超时
  2. EV_READ:有数据可读
  3. EV_WRITE:数据可写
  4. EV_SIGNAL:系统发出的信号(signal)
  5. EV_PERSIST:持续事件
  6. EV_ET:边沿触发

cb是event被触发后调用的回调函数,cb的类型为event_callback_fn。
arg为用户数据在调用回调函数时传给回调函数。

EV_PERSIST 事件持久化

默认情况下,每当未决事件成为激活的(因为fd已经准备好读取或者写入,或者因为超时),事件将在其回调被执行前成为非未决的。如果想让事件再次成为未决的,可以在回调函数中再次对其调用event_add()。
如果设置了EV_PERSIST标志,事件就是持久的。这意味着即使其回调被激活,事件还是会保持为未决状态。如果想在回调中让事件成为非未决的,可以对其调用event_del()。每次执行事件回调的时候,持久事件的超时值会被复位。

超时事件的创建

纯超时事件不需要fd(传-1即可)。libevent定义了创建超时事件的宏:

1
2
3
4
5
6
7
#define evtimer_assign(ev, b, cb, arg) \
event_assign((ev), (b), -1, 0, (cb), (arg))
#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
#define evtimer_add(ev, tv) event_add((ev), (tv))
#define evtimer_del(ev) event_del(ev)
#define evtimer_pending(ev, tv) event_pending((ev), EV_TIMEOUT, (tv))
#define evtimer_initialized(ev) event_initialized(ev)

信号事件的创建

信号事件不需要传入fd,而是传入signum。libevent定义了创建信号事件的宏:

1
2
3
4
5
6
7
8
#define evsignal_add(ev, tv)		event_add((ev), (tv))
#define evsignal_assign(ev, b, x, cb, arg) \
event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
#define evsignal_new(b, x, cb, arg) \
event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
#define evsignal_del(ev) event_del(ev)
#define evsignal_pending(ev, tv) event_pending((ev), EV_SIGNAL, (tv))
#define evsignal_initialized(ev) event_initialized(ev)

事件的初始化

1
int event_assign(struct event *, struct event_base *, evutil_socket_t, short, event_callback_fn, void *);

可以使用event_new在创建event时初始化event,也可以使用event_assign初始化未初始化的event,event_assign的参数与event_new的参数意义相同。
不要对已经在event_base中未决的事件调用event_assign(),这可能会导致难以诊断的错误。如果已经初始化和成为未决的,调用event_assign()之前需要调用event_del()。libevent提供了方便的宏将event_assign()用于仅超时事件或者信号事件。

event添加监听与取消监听

1
2
3
4
5
6
7
int event_add(struct event *ev, const struct timeval *tv);
#define evtimer_add(ev, tv) event_add((ev), (tv))
#define evsignal_add(ev, tv) event_add((ev), (tv))

int event_del(struct event *ev);
#define evtimer_del(ev) event_del(ev)
#define evsignal_del(ev) event_del(ev)

event_add 添加事件监听, tv为指定的超时值,如果为NULL表示不超时。event_del 取消事件监听。

event设置优先级

1
2
int event_priority_init(int npriorities);
int event_priority_set(struct event *ev, int pri);

event_priority_init()初始化优先级等级。即设置event_base的优先级数目
event_priority_set()设置ev的优先级。pri是[0, npriorities)的一个值。

event状态检测

1
2
3
4
5
6
7
8
int event_pending(const struct event *ev, short event, struct timeval *tv);
evutil_socket_t event_get_fd(const struct event *ev);
struct event_base *event_get_base(const struct event *ev);
short event_get_events(const struct event *ev);
event_callback_fn event_get_callback(const struct event *ev);
void *event_get_callback_arg(const struct event *ev);
int event_get_priority(const struct event *ev);
void event_get_assignment(const struct event *event, struct event_base **base_out, evutil_socket_t *fd_out, short *events_out, event_callback_fn *callback_out, void **arg_out);

event_pending

event_pending函数确定给出的event是未决的还是活动的.如果EV_READ、EV_WRITE、EV_SIGNAL、EV_TIMEOUT被设置为event参数,函数会返回event是未决的或者活动的所有标志.如果提供了tv_out并且设置了EV_TIMEOUT标志给event参数,当前event是未决的或者活跃在超时上,tv_out设置为保存event超时后的时间。

event_get_fd

函数返回了event配置的文件描述符或者信号值。

event_get_base

返回event配置的event_base。

event_get_events

返回事件的标志(EV_READ、EV_WRITE等)

event_get_callback 和 event_get_callback_arg

event_get_callback()和event_get_callback_arg()函数返回了event的回掉函数和它的参数指针

event_get_priority

返回了事件当前分配的优先级

event_get_assignment

拷贝了event分配的所有字段到提供的指针。如果指针为空,则忽略。

手动激活事件

1
void event_active(struct event *ev, int res, short ncalls);

使ev以标志res(EV_READ、EV_WRITE、EV_TIMEOUT的组合)激活,ev不需要预先的被未决,激活event也不需要使其未决。

evbuffer

evbuffer 用于处理缓冲网络 IO 的”缓冲”部分.

创建和释放evbuffer

1
2
struct evbuffer * evbuffer_new(void);
void evbuffer_free(struct evbuffer *buffer);

evbuffer_new() 分配和返回一个新的空evbuffer;而evbuffer_free()释放evbuffer和其内容

evbuffer与线程安全

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

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

检查evbuffer

1
size_t evbuffer_get_length(const struct evbuffer *buffer);

返回evbuffer存储的字节数

1
size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);

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

向evbuffer添加数据

1
2
3
4
int evbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen);
int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...);
int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap);
int evbuffer_expand(struct evbuffer *buf, size_t datlen);

evbuffer_add()添加data处的datlen字节到buf的末尾.
evbuffer_add_printf()和evbuffer_add_vprintf()添加格式化的数据到buf末尾.
evbuffer_expand()修改缓冲区最后一块,或者添加一个新的块,使缓冲区足以容纳datlen字节,而不需要更多的内存分配

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

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

evbuffer_add_buffer()将inbuf的所有数据移动到outbuf末尾.
evbuffer_remove_buffer()从src中移动datlen字节到dst末尾,尽量少进行复制.如果字节数小于datlen,所有字节被移动.

添加数据到evbuffer前面

1
2
int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t datlen);
int evbuffer_prepend_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf);

将数据移动到目标缓冲区前面

从evbuffer中移除数据

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

evbuffer_drain()函数从buf前面移除len字节内存
evbuffer_remove()函数从buf前面复制和移除datlen字节到data_out处的内存中.如果可用字节少于datlen,复制所有字节.

从evbuffer中复制出数据

1
ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data_out, size_t datlen);

前面复制 datlen 字节到 data_out 处的内存中。如果可用字节少于 datlen,函数会复制所有字节。失败时返回-1,否则返回复制的字节数。

面向行的输入

1
2
3
4
5
6
7
8
9
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 前面取出一行,用一个新分配的空字符结束的字符串返回这一行。如果 n_read_out 不是 NULL,则它被设置为返回的字符串的字节数。如果没有整行供读取,函数返回空。返回的字符串不包括行结束符。evbuffer_readln()理解4种行结束格式

  1. EVBUFFER_EOL_ANY,行尾是单个换行符
  2. EVBUFFER_EOL_CRLF,行尾是一个回车符,后随一个换行符
  3. EVBUFFER_EOL_CRLF_STRICT,行尾是一个可选的回车,后随一个换行符
  4. EVBUFFER_EOL_LF,行尾是任意数量、任意次序的回车和换行符。

在evbuffer中搜索

1
2
3
4
5
6
7
8
9
10
struct evbuffer_ptr {
ev_ssize_t pos;
struct {
void *chain;
size_t pos_in_chain;
} internal_;
};
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。函数返回包含字符串位置,或者在没有找到字符串时包含-1的 evbuffer_ptr 结构体。如果提供了 start 参数,则从指定的位置开始搜索;否则,从开始处进行搜索。
evbuffer_search_range()函数和 evbuffer_search 行为相同,只是它只考虑在 end 之前出现 的 what。
evbuffer_search_eol()函数像 evbuffer_readln()一样检测行结束,但是不复制行,而是返回指向行结束符的 evbuffer_ptr。如果 eol_len_out 非空,则它被设置为 EOL 字符串长度。

1
2
3
4
5
enum evbuffer_ptr_how {
EVBUFFER_PTR_SET,
EVBUFFER_PTR_ADD
};
int evbuffer_ptr_set(struct evbuffer *buf, struct evbuffer_ptr *pos, size_t position, enum evbuffer_ptr_how how);

evbuffer_ptr_set 函数操作 buffer 中的位置 pos。如果 how 等于 EVBUFFER_PTR_SET,指针被移动到缓冲区中的绝对位置 position;如果等于 EVBUFFER_PTR_ADD,则向前移动 position 字节。成功时函数返回0,失败时返回-1。
任何修改 evbuffer 或者其布局的调用都会使得 evbuffer_ptr 失效,不能再安全地使用。

检测数据而不复制

1
2
3
4
5
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, int n_vec);

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

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

直接向evbuffer添加数据

1
2
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 字段,也可以提供较少的向量。函数成功时返回0,失败时返回-1。

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

使用evbuffer的网络IO

1
2
3
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 *buf, evutil_socket_t fd, int howmuch);

vbuffer_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 中,这些函数应该可以在任何支持 read 和 write 的文件描述符上正确工作。在 Windows 中,仅仅支持套接字.

evbuffer和回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
struct evbuffer_cb_entry {
LIST_ENTRY(evbuffer_cb_entry) next;
union {
evbuffer_cb_func cb_func;
evbuffer_cb cb_obsolete;
} cb;
void *cbarg;
ev_uint32_t flags;
};
struct evbuffer_cb_entry * evbuffer_add_cb(struct evbuffer *buffer, evbuffer_cb_func cb, void *cbarg);

向 evbuffer 添加数据,或者从中移除数据的时候,回调函数会被调用。函数收到缓冲区指针、一个 evbuffer_cb_info 结构体指针,和用户提供的参数。evbuffer_cb_info 结构体的 orig_size 字段指示缓冲区改变大小前的字节数,n_added 字段指示向缓冲区添加了多少字节;n_deleted 字段指示移除了多少字节。

evbuffer_add_cb()函数为 evbuffer 添加一个回调函数,返回一个不透明的指针,随后可用于代表这个特定的回调实例。cb 参数是将被调用的函数,cbarg 是用户提供的将传给这个函数的指针。可以为单个 evbuffer 设置多个回调,添加新的回调不会移除原来的回调
注意:释放非空 evbuffer 不会清空其数据,释放 evbuffer 也不会为回调释放用户提供的数据指针。如果不想让缓冲区上的回调永远激活,可以移除或者禁用回调:

1
2
3
4
5
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 来移除回调,也可以通过回调函数和参数指针来移除。成功时函数返回0,失败时返回-1。evbuffer_cb_set_flags()和 evbuffer_cb_clear_flags()函数分别为回调函数设置或者清除给定的标志。当前只有一个标志是用户可见的:EVBUFFER_CB_ENABLED。这个标志默认是打开的。如果清除这个标志,对 evbuffer 的修改不会调用回调函数.

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

跟 bufferevent 回调一样,可以让 evbuffer 回调不在 evbuffer 被修改时立即运行,而是延迟到某 event_base 的事件循环中执行。如果有多个 evbuffer,它们的回调潜在地让数据添加到 evbuffer 中,或者从中移除,又要避免栈崩溃,延迟回调是很有用的。如果回调被延迟,则最终执行时,它可能是多个操作结果的总和。与 bufferevent 一样,evbuffer 具有内部引用计数的,所以即使还有未执行的延迟回调,释放 evbuffer 也是安全的。

为基于evbuffer的IO避免数据复制

1
2
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 函数,带有提供的data 指针、datlen 值和 extra 指针参数。函数成功时返回0,失败时返回-1

接受TCP连接

evconnlistener机制提供了监听和接受TCP连接的方法.

创建和释放evconnlistener

1
2
3
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()函数文档。如果 backlog 是负的,libevent 会试图挑选一个较好的值;如果为0,libevent 认为已经对提供的套接字调用了 listen()。
两个函数的不同在于如何建立监听套接字。evconnlistener_new()函数假定已经将套接字绑定到要监听的端口,然后通过 fd 传入这个套接字。如果要 libevent 分配和绑定套接字,可以调用 evconnlistener_new_bind(),传输要绑定到的地址和地址长度。
要释放连接监听器,调用 evconnlistener_free()

可标识的标志

1
2
3
4
5
6
7
8
9
10
11
12
13
//默认情况下,连接监听器接收新套接字后,会将其设置为非阻塞的,以便将其用于 libevent。如果不想要这种行为,可以设置这个标志。
#define LEV_OPT_LEAVE_SOCKETS_BLOCKING (1u<<0)
//如果设置了这个选项,释放连接监听器会关闭底层套接字。
#define LEV_OPT_CLOSE_ON_FREE (1u<<1)
//如果设置了这个选项,连接监听器会为底层套接字设置 close-on-exec 标志。
#define LEV_OPT_CLOSE_ON_EXEC (1u<<2)
//某些平台在默认情况下,关闭某监听套接字后,要过一会儿其他套接字才可以绑定到同一个端口。设置这个标志会让 libevent 标记套接字是可重用的,这样一旦关闭,可以立即打开其他套接字,在相同端口进行监听。
#define LEV_OPT_REUSEABLE (1u<<3)
//为监听器分配锁,这样就可以在多个线程中安全地使用了
#define LEV_OPT_THREADSAFE (1u<<4)
#define LEV_OPT_DISABLED (1u<<5)
#define LEV_OPT_DEFERRED_ACCEPT (1u<<6)
#define LEV_OPT_REUSEABLE_PORT (1u<<7)

连接监听器回调

1
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);

stener参数是接收连接的连接监听器。sock参数是新接收的套接字。addr和len参数是接收连接的地址和地址长度。ptr是调用evconnlistener_new()时用户提供的指针。

禁用和启用evconnlistener

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

暂时禁止或者重新允许监听新连接

设置evconnlistener的回调函数

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

函数调整 evconnlistener 的回调函数和其参数。

检测evconnlistener

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

分别返回监听器关联的套接字和 event_base

侦测错误

1
2
typedef void (*evconnlistener_errorcb)(struct evconnlistener *, void *);
void evconnlistener_set_error_cb(struct evconnlistener *lev, evconnlistener_errorcb errorcb);

如果使用 evconnlistener_set_error_cb()为监听器设置了错误回调函数,则监听器发生错误时回调函数就会被调用。第一个参数是监听器,第二个参数是调用 evconnlistener_new() 时传入的 ptr。

bufferevent

bufferevent 和 evbuffer

每个bufferevent都有一个输出缓冲区和一个输入缓冲区,类型都是”struct evbuffer”,有数据要写入到bufferevent时,添加数据到输出缓冲区;bufferevent中有数据供读取的时候,从输入缓冲区抽取数据.

回调和水位

每个bufferevent有两个数据相关的回调:读取回调和写入回调.默认情况下,从底层传输端口读取任意量的数据后会调用读取回调;输出去有足够的数据被清空到底层传输端口后写入回调会被调用.

每个bufferevent有四个水位:

  1. 读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。
  2. 读取高水位:输入缓冲区中的数据量达到此级别后,bufferevent 将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。
  3. 写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。
  4. 写入高水位:bufferevent 没有直接使用这个水位。它在 bufferevent 用作另外一个bufferevent 的底层传输端口时有特殊意义。

错误或事件回调(向应用通知非面向数据的事件)

  1. BEV_EVENT_READING:读取操作时发生某事件
  2. BEV_EVENT_WRITING:写入操作时发生某事件
  3. BEV_EVENT_EOF:遇到文件结束指示
  4. BEV_EVENT_ERROR:操作时发生错误,调用EVUTIL_SOCKET_ERROR()获取更错错误信息
  5. BEV_EVENT_TIMEOUT:发生超时
  6. BEV_EVENT_CONNECTED:请求的连接过程已经完成

延迟回调

默认情况下,bufferevent的回调在相应的条件发生时立即被执行.在依赖关系复杂的情况下,立即调用会产生问题.要解决该问题,可以请求bufferevent延迟其回调.条件满足时,延迟回调不会立即被调用,而是在event_loop()调用中被排队.然后在通常的事件回调后执行.

buffervent的选项标记bufferevent_options

  1. BEV_OPT_CLOSE_ON_FREE:释放bufferevent时关闭底层传输端口.将关闭底层套接字,释放底层bufferevent等.
  2. BEV_OPT_THREADSAFE:自动为bufferevent分配锁,可以安全的在多个线程中使用bufferevent
  3. BEV_OPT_DEFER_CALLBACKS:设置该标记,bufferevent延迟所有回调
  4. BEV_OPT_UNLOCK_CALLBACKS:默认情况下,如果设置bufferevent为线程安全的,则bufferevent会在调用用户提供的回调时进行锁定.设置该选项让Libevent在执行回调时不锁定.

基于套接字的bufferevent

创建基于套接字的bufferevent

1
2
3
struct bufferevent *
bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
int options);

base时event_base反应堆 fd是套接字的文件描述符 options是bufferevent选项

在基于套接字的bufferevent上启动连接

1
int bufferevent_socket_connect(struct bufferevent *bev, const struct sockaddr *sa, int socklen);

如果还没有为 bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。如果已经为 bufferevent 设置套接字,调用 bufferevent_socket_connect()将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。连接完成之前可以向输出缓冲区添加数据。如果连接成功启动,函数返回0;如果发生错误则返回-1。
注意:如果使用bufferevent_socket_connect() 发起连接,将只会收到 BEV_EVENT_CONNECTED 事件。如果自己调用 connect(),则连接上将被报告为写入事件

通过主机名启动连接

1
2
int bufferevent_socket_connect_hostname(struct bufferevent *bev, struct evdns_base *evdns_base, int family, const char *hostname, int port);
int bufferevent_socket_get_dns_error(struct bufferevent *bev);

bufferevent_socket_connect_hostnam解析hostname,通过其family类型地址(允许的地址族类型有AF_INET,IF_INET6和AF_UNSPEC).如果名字解析失败,函数将调用事件回调,报告错误事件.如果解析成功,函数将启动连接请求.
dns_base参数可选:如果为NULL,等待名字查找完成期间调用线程将被阻塞.如果提供dns_base参数,libevent将使用它异步查询主机名.
函数返回的错误可能是DNS主机名查询错误,可以调用bufferevent_socket_get_dns_error() 来获取最近的错误。返回值0表示没有检测到 DNS 错误。

操作回调、水位和启用/禁用

1
2
3
4
5
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);
void bufferevent_setcb(struct bufferevent *bufev,
bufferevent_data_cb readcb, bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg);

bufferevent_setcb()函数修改bufferevent的一个或多个回调.readcb、writecb、eventcb函数分别在读取到足够的数据、写入足够的数据、发生错误时被调用.每个回调的第一个参数都是发生了事件的bufferevent,最后一个参数都是调用bufferevent_setcb()时用户提供的cbarg参数(通过cbarg参数向回调函数传递参数).事件回调的events参数是一个表示事件标志的位掩码.
要禁用回调,传递NULL而不是回调函数.注意:bufferevent的所有回调函数共享单个cbarg,修改它需要特别小心

1
2
3
int bufferevent_enable(struct bufferevent *bufev, short event);
int bufferevent_disable(struct bufferevent *bufev, short event);
short bufferevent_get_enabled(struct bufferevent *bufev);

bufferevent_enable()函数开启bufferevent的EV_READ、EV_WRITE事件
bufferevent_disable()函数禁用bufferevent的EV_READ、EV_WRITE事件
没有启用读取或写入事件时,bufferevent将不会试图进行数据的读取和写入

没有必要在输出缓冲区空时禁用写入事件:bufferevent 将自动停止写入,然后在有数据等待写入时重新开始
没有必要在输入缓冲区高于高水位时禁用读取事件:bufferevent 将自动停止读取,然后在有空间用于读取时重新开始读取
默认情况下,新创建的 bufferevent 的写入是启用的,但是读取没有启用
bufferevent_get_enabled()可以确定bufferevent上当前开启的事件.

1
void bufferevent_setwatermark(struct bufferevent *bufev, short events, size_t lowmark, size_t highmark);

bufferevent_setwatermark()函数设置单个bufferevent的读取水位、写入水位.如果events设置为EV_READ调整读取水位,events设置为EV_WARITE调整写入水位.
对于高水位,0表示”无限”

bufferevent中数据的操作

1
2
struct evbuffer * bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer * bufferevent_get_output(struct bufferevent *bufev);

bufferevent_get_input()返回输入缓冲区
bufferevent_get_output()返回输出缓冲区

1
2
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);

bufferevent_write()将内从从data处开始的size字节数据添加到输出缓冲区末尾.
bufferevent_write_buffer()移除buf的所有内容,将其放置到输出缓冲区末尾.

1
2
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);

bufferevent_read()至多从输入缓冲区移除size字节的数据,将其存储到内存中data处,返回实际移除的字节数
bufferevent_read_buffer()抽空输入缓冲区的所有内容,将其放置到buf中,成功返回0,失败返回-1

读写超时

1
2
3
int bufferevent_set_timeouts(struct bufferevent *bufev,
const struct timeval *tv_read,
const struct timeval *tv_write);

bufferevent_set_timeouts()设置超时时间为NULL会移除超时回调
试图读取数据的时候,如果至少等待了 timeout_read 秒,则读取超时事件将被触发。试图写入数据的时候,如果至少等待了 timeout_write 秒,则写入超时事件将被触发。
注意,只有在读取或者写入的时候才会计算超时。即如果 bufferevent 的读取被禁止,或者输入缓冲区满(达到其高水位),则读取超时被禁止。如果写入被禁止,或者没有数据待写入,则写入超时被禁止。读取或者写入超时发生时,相应的读取或者写入操作被禁止,然后超时事件回调被调用,带有标志BEV_EVENT_TIMEOUT | BEV_EVENT_READING或者BEV_EVENT_TIMEOUT | BEV_EVENT_WRITING。

清空bufferevent

1
2
3
int bufferevent_flush(struct bufferevent *bufev,
short iotype,
enum bufferevent_flush_mode mode);

清空 bufferevent 要求 bufferevent 强制从底层传输端口读取或者写入尽可能多的数据,而忽略其他可能保持数据不被写入的限制条件。函数的细节功能依赖于 bufferevent 的具体类型。iotype 参数应该是 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE,用于指示应该处理读取、写入,还是二者都处理。state 参数可以是 BEV_NORMAL、BEV_FLUSH 或者BEV_FINISHED。BEV_FINISHED 指示应该告知另一端,没有更多数据需要发送了; 而 BEV_NORMAL 和 BEV_FLUSH 的区别依赖于具体的 bufferevent 类型。
失败时 bufferevent_flush()返回-1,如果没有数据被清空则返回0,有数据被清空则返回1

类型特定的bufferevent函数

1
int bufferevent_priority_set(struct bufferevent *bufev, int priority);

调整bufev的优先级为priority.成功返回0,失败返回-1,该函数仅作用域基于套接字的bufferevent

1
2
int bufferevent_setfd(struct bufferevent *bev, evutil_socket_t fd);
evutil_socket_t bufferevent_getfd(struct bufferevent *bev);

设置或返回基于fd的事件的文件描述符.只有基于套接字的bufferevent支持bufferevent_setfd.

1
struct event_base * bufferevent_get_base(struct bufferevent *bufev);

返回bufferevent的event_base

1
struct bufferevent * bufferevent_get_underlying(struct bufferevent *bev);

返回作为 bufferevent 底层传输端口的另一个 bufferevent。

手动锁定和解锁

1
2
void bufferevent_lock(struct bufferevent *bev);
void bufferevent_unlock(struct bufferevent *bev);

注意:如果创建 bufferevent 时没有指定 BEV_OPT_THREADSAFE 标志,或者没有激活 libevent 的线程支持,则锁定操作是没有效果的.用这个函数锁定 bufferevent 将自动同时锁定相关联的 evbuffer.这些函数是递归的:锁定已经持有锁的 bufferevent 是安全的.当然,对于每次锁定都必须进行一次解锁.

http服务相关

http Server的创建与开始

首先需要使用event_base_new或event_base_new_with_config创建一个event_base,然后使用evhttp_new创建evhttp。

1
struct evhttp *evhttp_new(struct event_base *base);

在需要释放evhttp时需要调用evhttp_free来释放。

1
void evhttp_free(struct evhttp* http);

再为evhttp设置回调函数,evhttp_set_cb为特定URL指定回调函数,evhttp_set_gencb注册通用回调函数,在没有指定URL回调函数的情况下该回调函数被调用。

1
2
3
4
5
6
7
8
9
10
//uri 特定的uri
//cb 回调函数
//cbarg 传入回调函数的参数
int evhttp_set_cb(struct evhttp *http, const char *uri, \
void (*cb)(struct evhttp_request *, void *), void *cbarg);

//cb 回调函数
//cbarg 传入回调函数的参数
void evhttp_set_gencb(struct evhttp *http,
void (*cb)(struct evhttp_request *, void *), void *cbarg);

再为evhttp绑定需要监听的ip和port。使用evhttp_bind_socket_with_handle函数。

1
struct evhttp_bound_socket *evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port);

可以使用libevent提供的相关函数打印监听端口信息。
最后使用event_base_dispatch进入事件循环。

http请求的处理

evhttp_request_get_uri得到当前请求的uri地址。

1
const char *evhttp_request_get_uri(const struct evhttp_request *req);

evhttp_request_get_command得到当前请求的类型。

1
2
3
4
5
6
7
8
9
10
11
12
enum evhttp_cmd_type {
EVHTTP_REQ_GET = 1 << 0,
EVHTTP_REQ_POST = 1 << 1,
EVHTTP_REQ_HEAD = 1 << 2,
EVHTTP_REQ_PUT = 1 << 3,
EVHTTP_REQ_DELETE = 1 << 4,
EVHTTP_REQ_OPTIONS = 1 << 5,
EVHTTP_REQ_TRACE = 1 << 6,
EVHTTP_REQ_CONNECT = 1 << 7,
EVHTTP_REQ_PATCH = 1 << 8
};
enum evhttp_cmd_type evhttp_request_get_command(const struct evhttp_request *req);

evhttp_uri_parse URI解析,得到evhttp_uri。

1
struct evhttp_uri *evhttp_uri_parse(const char *source_uri);

evhttp_parse_query对uri参数进行解析,结果保存在struct evkeyvalq结构体中。

1
void evhttp_parse_query(const char *uri, struct evkeyvalq *args);

evhttpdecode_uri URL解码,得到UTF编码的字符,得到数据所占内存需要自己释放。
evhttp_encode_uri URL编码,对所有非alphanumeric及-\
的字符都被类似于%和一个2位16进制字符替换(其中空格被+号替换)。

1
2
char *evhttp_encode_uri(const char *str);
char *evhttp_decode_uri(const char *uri);

参考文献

  1. libevent专栏
  2. Libevent中文帮助手册
  3. Libevent 官方文档学习笔记