大部分情况下,程序除了响应事件之外,还需要做数据缓存。例如,当我们需要写数据时,通常的模式如下:
1.确定要给一个连接写数据,将数据写入缓冲区
2.等待连接可写
3.写尽可能多的数据
4.记下已经写入多少数据,如果数据还能写完,并等待连接再一次可写
这种IO模式很常见,
Libevent 为此提供了一个通用的机制。bufferevent由一个底层传输,写入和读出缓冲区组成。用回调函数取代常规事件,当底层传输可读或可写的时候,当已经读取或写入足够的数据后,bufferevent 调用用户提供的回调函数。
bufferevent有以下几种,他们都公用一个接口:
socket-based bufferevents 从socket读取或写入数据。
asynchronous-IO bufferevents 用window的IOCP接口向底层的socket写入读取数据,仅在window上可用,
filtering bufferevents 在把数据传给底层的bufferevent对数据进行处理。
paired bufferevents 把数据传给另外一个bufferevent
一、Bufferevents and evbuffers
每个 bufferevent 都有一个input和output缓冲区,他们都是evbuffer类型。当有数据要写入bufferevent 时,把数据添加到output,要从bufferevent 读取数据时,从input读取。
二、回调和水位
每个bufferevent 都有两个数据相关的回调函数:一个写回调和读回调。当有数据从底层读出时,调用读回调函数。当有足够的空的输出缓冲区时调用写回调函数。你可以调整水位来覆盖这些回调函数的行为。
每个bufferevent 都有四个水位:
Read low-water mark 当input缓冲区的数据达到或高于此水位时,调用读回调函数。
Read high-water mark 如果input缓冲区的数据没有达到此水位则停止读数据,直到input中水位低于此。
Write low-water mark 当output缓冲区的是数据达到或低于该水位时,调用写回调函数
Write high-water mark bufferevent 没有直接使用该水位
bufferevent 也有错误或者事件回调函数被调用,用于通知程序非面向数据的事件,比如:一个连接被关闭或出错。
BEV_EVENT_READING bufferevent读操作时出错
BEV_EVENT_WRITING bufferevent写操作时出错
BEV_EVENT_ERROR bufferevent操作时出错
BEV_EVENT_TIMEOUT bufferevent出现超时
BEV_EVENT_EOF bufferevent遇到文件结束符
BEV_EVENT_CONNECTED 结束对bufferevent的连接请求
三、延迟回调
默认情况下,当相应的条件成立时bufferevent的回调函数会立即被调用。当依赖关系变得复杂的时候这种立即调用会有麻烦。例如:有一个回调函数把数据写入evbuffer A,另一个回调函数从evbuffer A读取数据,他们都在栈上执行,有导致栈溢出的风险。
要解决此问题,你可以告知bufferevent 它的回调应该延迟。当条件成立时,延迟回调函数不是立即执行,而是进入event_loop()的调用队列,然后在定期的事件被调用。
四、
bufferevent的选项标记
BEV_OPT_CLOSE_ON_FREE
当bufferevent 被释放后,关闭底层传输。这将关闭socket和底层bufferevent。
BEV_OPT_THREADSAFE
自动为bufferevent分配锁,这样可以从多个线程使用bufferevent
BEV_OPT_DEFER_CALLBACKS
当设置此标记,bufferevent 延迟所有的回调函数
BEV_OPT_UNLOCK_CALLBACKS
默认情况下,当bufferevent 设置为线程安全时,回调被调用的时候会进行锁定,设置此选项可以使调用函数被调用时解锁。
五、基于socket的bufferevent
最简单的bufferevent类型是基于socket的。基于socket的bufferevent使用了libevent底层的事件机制来发现网络socket何时可读/可写的,并且使用底层的网络调用来传送和接收数据。
创建基于socket的bufferevent
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);
该函数创建一个基于socket的bufferevent。如果你想后面再设置文件描述符,fd可以为-1。函数成功则返回一个bufferevent ,释放返回NULL。
启动基于socket的bufferevent的连接
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);
address 和addrlen是调用标准connect函数的参数。如果bufferevent还没有socket,调用该函数则会分配一个非阻塞的socket。
如果bufferevent已经有socket,该函数通知Libevent ,socket还没连接,在成功连接之前不要进行读写操作。
在成功连接之前向output缓冲区写数据是没问题的。
成功连接则返回0,出错则返回-1.
通过主机名进行连接
该函数解析了int bufferevent_socket_connect_hostname(struct bufferevent *bev, struct evdns_base *dns_base, int family, constchar *hostname, int port); int bufferevent_socket_get_dns_error(struct bufferevent *bev);
hostname
,查找family的地址。如果解析失败,它通过一个错误事件调用回调函数。
dns_base 是可选的,如果为NULL,Libevent 阻塞等待名字查找完成,这通常不是我们想要的。如果提供该参数,Libevent通过它来异步的查找主机名。六、通用的bufferevent操作
1、释放一个bufferevent
该函数释放一个bufferevent。bufferevent是内部引用计数的,当你释放它时,如果bufferevent还要未决的延迟回调函数,它会等到回调函数执行万了才进行删除。void bufferevent_free(struct bufferevent *bev);
该函数尝试尽可能释放bufferevent ,如果有未决的数据要写入bufferevent ,bufferevent 被释放之前是不会被刷新的。
如果设置BEV_OPT_CLOSE_ON_FREE ,bufferevent 有一个socket,或者底层关联socket作为传输,释放bufferevent 就会关闭传送。2.操作回调,水位,启用/禁用操作。
bufferevent_setcb()改变bufferevent一个或多个回调函数,当有足够的数据可读,可写或出现错误事件时分别调用回调函数readcb, writecb, 和eventcb。每个函数的第一个参数都是发生事件的bufferevent ,最后一个参数的值是用户调用bufferevent_callcb时传入的,你可以用它给回调函数传数据。事件回调函数的events 参数是事件标记的掩码。typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx); typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx); void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg); void bufferevent_getcb(struct bufferevent *bufev, bufferevent_data_cb *readcb_ptr, bufferevent_data_cb *writecb_ptr, bufferevent_event_cb *eventcb_ptr, void **cbarg_ptr);
用NULL替代回调函数就可以禁用该回调函数。所有的回调函数共享一个cbarg 值,所以改变其中的值会影响其他函数。
给bufferevent_getcb()传入指针来获取目前设置的回调函数。当output缓冲区为空时不用禁用写,bufferevent 自动停止写数据,当有数据要写时再重启。你可以对bufferevent禁用EV_READ, EV_WRITE, 或 EV_READ|EV_WRITE事件。当禁用读写,bufferevent就不会进行数据读写。void bufferevent_enable(struct bufferevent *bufev, short events); void bufferevent_disable(struct bufferevent *bufev, short events); short bufferevent_get_enabled(struct bufferevent *bufev);
你可以用bufferevent_get_enabled()获取
bufferevent
目前哪个事件启用。该函数调整bufferevent的读写水位。void bufferevent_setwatermark(struct bufferevent *bufev, short events, size_t lowmark, size_t highmark);
七、操作bufferevent的数据
这两个函数很强大,他们返回缓冲区,struct evbuffer *bufferevent_get_input(struct bufferevent *bufev); struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
这两个函数向bufferevent的output缓冲区写入数据。int bufferevent_write(struct bufferevent *bufev, constvoid *data, size_t size); int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
从bufferevent的input 缓冲区读数据size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size); int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);
八、读写超时
尝试读操作并等待timeout_read秒之后将触发读超时。void bufferevent_set_timeouts(struct bufferevent *bufev, conststruct timeval *timeout_read, conststruct timeval *timeout_write);
尝试写操作并等待
timeout_write
秒之后将触发写超时。
如果bufferevent禁用了读操作或者input缓冲区已经满了,则读超时也就不可用了。同理,写超时也一样。
九、清空bufferevent
int bufferevent_flush(struct bufferevent *bufev, short iotype, enum bufferevent_flush_mode state);
冲洗bufferevent就是告知bufferevent强制从底层读写尽可能多的数据,忽略其他数据不可写入的限制。具体功能依赖与bufferevent的类型。
十、特定类型bufferevent的函数int bufferevent_priority_set(struct bufferevent *bufev, int pri); int bufferevent_get_priority(struct bufferevent *bufev);
bufferevent_priority_set用来调整
bufferevent的优先级,只能用于基于socket的bufferevent。这两个函数用于设置或返回基于文件的事件的文件描述符。只要基于socket的bufferevent支持setfd()、int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd); evutil_socket_t bufferevent_getfd(struct bufferevent *bufev);
该函数返回bufferevent的event_basestruct event_base *bufferevent_get_base(struct bufferevent *bev);
该函数返回由于其他bufferevent 的底层传输的bufferevent 。struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev);
十一、bufferevent 手动加锁和解锁
如果创建bufferevent时不用 BEV_OPT_THREADSAFE或者Libevent的不支持多线程,就不能给bufferevent加锁。void bufferevent_lock(struct bufferevent *bufev); void bufferevent_unlock(struct bufferevent *bufev);
锁住bufferevent 自然也会把它不关联的evbuffers也加锁。这些函数是递归的,你可以给已经加锁的bufferevent 再加锁。