本文地址:https://segmentfault.com/a/1190000005601925
Libevent的辅助函数和数据类型
头文件是<event2/util.h>
。以下只列出我自己会用到的部分。
基本类型
evutil_socket_t
Socket的抽象。除了Windows之外,其他系统都是一个int类型。如果考虑Windows的兼容性的话,建议用这个类型。
标准整型
以下是几种数据长度的定义
----------------------------------------------------------
Type 位宽 符号数 最大值 最小值
----------------------------------------------------------
ev_uint64_t 64 x EV_UINT64_MAX 0
ev_int64_t 64 √ EV_INT64_MAX EV_INT64_MIN
ev_uint32_t 32 x EV_UINT32_MAX 0
ev_int32_t 32 √ EV_INT32_MAX EV_INT32_MIN
ev_uint16_t 16 x EV_UINT16_MAX 0
ev_int16_t 16 √ EV_INT16_MAX EV_INT16_MIN
ev_uint8_t 8 x EV_UINT8_MAX 0
ev_int8_t 8 √ EV_INT8_MAX EV_INT8_MIN
其他一些类型
ev_ssize_t
ev_off_t
适配函数的宏
#define evutili_timer_add(tvp, uvp, vvp)
#define evutili_timer_sub(tvp, uvp, vvp)
计算timeval数据加减的宏,vvp = tvp +/- uvp。注意三者都要使用指针
#define evutil_timerclear(tvp)
#define evutil_timerisset(tvp)
将timeval清零,或者判断是否被清零
#define evutil_timercmp(tvp, uvp, cmp)
判断timeval的先后,其中cmp是比较富豪,比如==
, <=
, >=
, <
, >
, !=
int evutil_gettimeofday (struct timeval *tv, struct timezone *tz);
Socket相关的函数
#define evutil_socket_geterror (sock)
#define evutil_socket_error_to_string (errcode)
获得指定socket的error code,以及转为可读的string
int evutil_make_socket_nonblocking (evutil_sopcket_t sock);
将一个socket非阻塞。
字符串操作
ev_int64_t evutil_strtoll (const char *s, char **endptr, int base);
int evutil_snprintf (char *but, size_t buflen, const char *format, ...);
int evutil_vsnprintf (char *bug, size_t buflen, const char *format, va_list ap);
数据结构体
#define evutil_offsetof (type, field)
Bufferevent:概念和基本知识
传统的libevent使用方法:
-
当需要放数据的时候,存入数据到buffer
-
等待socket可写
-
尽量向socket中写更多的data
-
如果还有data未写入,则再等待socket可写
使用头文件<event2/bufferevent.h>
可以使用bufferevent,节省read/write调用,只需要将数据放入/取出一个buffer即可
目前bufferevent只支持TCP,未来可能支持UDP
每个bufferevent有一个read buffer和一个write buffer,都是struct evbuffer
。这个后文再讲。
回调和bufferevent
Bufferevent使用叫做watermarks
(水位线)的东西来定义回调函数的调用时机。有以下几个watermarks:
Read low-water mark
:当read buffer的量大于等于这么多时,调用callback。默认是0,即一有数据就回调。
Read high-water mark
:当read buffer的量大于等于这么多时,停止read,直到buffer里面的数据低于这个值为止,重新开始read。默认是无限。
Write low-water mark
:当write buffer的量小于等于这么多时,调用回调。默认是0
Write high-water mark
:bufferevent未直接使用这个值。参见后文
Bufferevent也有错误回调和事件回调,用于告知一些非数据时间和错误。如下:
BEV_EVENT_READING
BEV_EVENT_WRITING
BEV_EVENT_ERROR
:操作发生错误。需要调用EVUTIL_SOCKET_ERROR()
来判断出现了什么错误
BEV_EVENT_TIMEOUT
BEV_EVENT_EOF
BEV_EVENT_CONNECTED
:请求连接已经完成
延迟回调
一般情况下,bufferevent的callback时立刻调用的。但是如果调用关系很复杂的话可能会出bug,这个时候可以将bufferevent设置为延迟的(defered
),这样会使得回调函数放在event loop中被执行,单线程。
Bufferevent的选项
BEV_OPT_CLOSE_ON_FREE
:当bufferevent释放时,关闭底层传输
BEV_OPT_THREADSAFE
:为bufferevent使用lock
BEV_OPT_DEFER_CALLBACKS
:将callback设为延迟的
BEV_OPT_UNLOCK_CALLBACKS
:默认情况下如果有THREADSAFE标志,调用callback时会加锁。使用这个标志是的即便有THREADSAFE标志,调用callback也不加锁
使用基于socket的bufferevent
struct bufferevent *bufferevent_socket_new (
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);
这里的fd可以不指定,此时fd的参数是-1。如果指定了fd,这个fd必须是已经nonblock的。
int bufferevent_socket_connect (struct bufferevent *bev,
struct sockaddr *address,
int addrlen);
这是对connect()
的封装。如果bev的fd是-1,那么会自动调用socket()
,并且设置nonblock。,随后再异步调用connect()
;如果fd已经指定了,那么只是告诉bev去做connect()
操作。
正常情况下,这会引起BEV_EVENT_CONNECTED
回调
int bufferevent_socket_connect_hostname (
struct bufferevent *bev,
struct event_base *dns_base,
int family,
const char *hostname,
int port);
这是connect()
封装的另一个版本,但是目标改为hostname。这会导致bufferevent自动去解析DNS。其中family
可选以下值:AF_INET
, AF_INET6
, AF_UNSPEC
。
dns_base参数可选。如果是NULL,那么bufferevent会一直阻塞直到DNS解析完成——当然不推荐这么做。如果带了参数,则libevent会异步处理DNS请求。
剩下的工作与上面的connect封装相同。
int bufferevent_socket_get_dns_error (struct bufferevent *bev);
通用的bufferevent操作
void bufferevent_free (struct bufferevent *bev);
释放bfferevent。如果callback是defered的,那么bufferevent会等到callback返回之后才释放。如果指定了BEV_OPT_CLOSE_ON_FREE,那么socket也会被close掉。
typedef void (*bufferevent_data_cb) (struct bufferevent *bev, void *ctx);
typedef void (*bufferevent_event_cb) (struct bufferevent *bev, short events, void *ctx);
void buffevent_setcb (struct buffevent *bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb,
void *cbarg);
void bufferevent_get_cb (struct buffevent *bufev,
bufferevent_data_cb *readcb_ptr,
bufferevent_data_cb *writecb_ptr,
bufferevent_event_cb *eventcb_ptr,
void **cbarg_ptr);
设置。获取bufferevent的callback。如果不想使用某个callback,则传入NULL。
void bufferevent_enable (struct bufferevent *bufev, short events);
void bufferevent_disable (struct bufferevent *bufev, short events);
short bufferevent_getenabled (struct bufferevent *bufev);
使能/禁用指定的的callback。默认情况下,刚初始化的bufferevent,write使能,而read禁止。
void bufferevent_setwatermark (struct buffevent *bev,
short events,
size_t lowmark,
size_t highmark);
设置watermark。对于high-watermark,0表示无限。
struct evbuffer *bufferevent_get_input (struct bufferevent *bev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bev);
获取到bufferevent中对应的read/write buffer。
int bufferevent_write (struct bufferevent *ev, const void *data, size_t size);
int bufferevent_write_buffer (struct bufferevent *bev, struct evbuffer *buf);
函数一:直接向bufferevent附加数据函数二:将evbuffer的全部内容附加到bufferevent中并晴空evbuffer
size_t bufferevent_read (struct bufferevent *bev, void *data, size_t size);
int bufferevent_read_buffer (struct bufferevent *bev, struct evbuffer *buf);
函数一:直接从bufferevent中读出数据,返回数据长度函数二:将bufferevent中的全部数据抽取到evbuffer中
void bufferevent_set_timeouts (struct bufferevent *bev,
const struct timeval *timeout_read,
const struct timeval *timeout_write);
设置timeout,使得当一段时间没有数据时,触发回调函数。此时的实践中会包含 BEV_EVENT_TIMEOUT
位
int bufferevent_flush (struct bufferevent *bufev,
short iotype,
enum bufferevent_flush_mode state);
强制读/写尽可能多的数据。这个函数目前对socket没有作用。
类型特定的bufferevent函数
以下几个函数的含义正如字面意思,就不特别说明了
int bufferevent_priority_set (struct bufferevent *bev, int pri);
int bufferevent_get_priority (struct bufferevent *bev);
int bufferevent_setfd (struct bufferevent *bev, evutil_socket_t fd);
evutil_socket_t bufferevent_getfd (struct bufferevent *bev);
struct event_base *bufferevent_get_base (struct bufferevent *bev);
struct bufferevent *bufferevent_get_underlying (struct bufferevent *bev);
void bufferevent_lock (struct bufferevent *bev);
void buyfferevent_unlock(struct bufferevent *bev);
Bufferevents:高级主题
这里讲了很多bufferevent的高级功能。本文章只是列出其中会使用到的部分
限制每次read/write的长度
int bufferevent_set_max_single_read (struct bufferevent *bev, size_t size);
int bufferevent_set_max_single_write(struct bufferevent *bev, size_t size);
同时也有相对应的get函数
速率(带宽)限制
#define EV_RATE_LIMIT_MAX EV_SSIZE_MAX
struct ev_token_bucket_cfg;
struct ev_token_bucket_cfg *ev_token_bucket_cfg_new (
size_t read_rate, size_t read_burst,
size_t write_rate, size_t write_burst,
const struct timeval *tick_len);
void ev_token_bucket_cfg_free (struct ev_token_bucket_cfg *cfg);
int bufferevent_set_rate_limit (struct bufferevent *bev,
struct ev_token_bucket_cfg *cfg);
其中ev_token_bucket_cfg_new()
的前四个函数的单位均为bytes/tick
,而tick
的单位由tick_len
指定。如果tick_len
为NULL,那么默认为1秒。
Bufferevents 和 SSL
这里其实是一个很重要的内容,讲的是如何在bufferevent中使用SSL。Libevent将SSL深度耦合了进来,使得你可以很方便地使用bufferevent来完成SSL通信。
呃,缺点就是libevent和OpenSSL的缺点的集合。其实对于嵌入式开发来说,因为CPU是分立的,所以性能上的缺点并不明显,最大的问题是占用磁盘空间啊!Libevent的库本身就不小,加上OpenSSL更是超大。我弄懂libevent的时候,我们的系统已经准备改用其他的异步I/O和SSL库,所以我也就不看了
另外吐槽一下:我们这么多年了还是没时间把libevent和OpenSSL完全替换的工作做完,在这期间我自己都把libev、libuv、PolarSSL(mbedTLS)、cyaSSL看了……