写入数据的时候通常的运行模式是
1.决定要向连接写入一些数据,把数据放入到缓冲区中, 2.等待连接可以写入, 3.写入尽量多的数据, 4.记住写入了多少数据,如果还有更多数据要写入,等待连接再次可以写入
- libevent为此提供了一个通用的机制即bufferevent,在读取或者写入了足够量的数据之后调用用户提供的回调
- 基于套接字的 bufferevent:使用 event_*接口作为后端,通过底层流式套接字发送或者接收数据的 bufferevent
- 异步 IO bufferevent:使用 Windows IOCP 接口
- 过滤型 bufferevent:将数据传输到底层 bufferevent 对象之前,处理输入或者输出数据的 bufferevent:比如说,为了压缩或者转换数据。
- 成对的 bufferevent:相互传输数据的两个 bufferevent。
请注意
:当前 bufferevent 只能用于像 TCP 这样的面向流的协议
0. 所有接口摘要
//5.1 创建基于套接字的buffevent
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
int options);
//5.2 在基于套接字的 t bufferevent 上启动连接
int bufferevent_socket_connect(struct bufferevent *bev,struct sockaddr *sa, int socklen);
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);
void bufferevent_free(struct bufferevent *bufev);
//6.2 操作回调、水位和启用/禁用
void bufferevent_setcb(struct bufferevent *bufev,//发生了事件的bufferevent
bufferevent_data_cb readcb, //读取足够多数据时的回调
bufferevent_data_cb writecb, //写入足够多数据时的回调
bufferevent_event_cb eventcb, //发生错误时的回调
void *cbarg);
int bufferevent_enable(struct bufferevent *bufev, short event); //启用
int bufferevent_disable(struct bufferevent *bufev, short event);//禁用事件
short bufferevent_get_enabled(struct bufferevent *bufev);
void bufferevent_setwatermark(struct bufferevent *bufev, short events,
size_t lowmark, size_t highmark);//调整水位
//6.3 操作 bufferevent 中的数据
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
int bufferevent_write(struct bufferevent *bufev, const void *data,size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev,struct evbuffer *buf);
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);
int bufferevent_flush(struct bufferevent *bufev,short iotype,enum bufferevent_flush_mode mode);
//6.4 读写超时
int bufferevent_set_timeouts(struct bufferevent *bufev,
const struct timeval *tv_read,
const struct timeval *tv_write);
//7.类型特定的 bufferevent 函数
int bufferevent_priority_set(struct bufferevent *bufev, int priority);
int bufferevent_setfd(struct bufferevent *bev, evutil_socket_t fd);
//8. 锁
void bufferevent_lock(struct bufferevent *bev);
void bufferevent_unlock(struct bufferevent *bev);
1. bufferevent 和 evbuffer
每个 bufferevent 都有一个输入缓冲区和一个输出缓冲区,它们的类型都是“struct evbuffer” 。有数据要写入到 bufferevent 时,添加数据到输出缓冲区;bufferevent 中有数据供读取的时候,从输入缓冲区抽取(drain)数据。
2. 回调和水位
- 每个 bufferevent 有两个数据相关的回调:一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。通过调整 bufferevent 的读取和写入“水位(watermarks)”可以覆盖这些函数的默认行为。
- 每个 bufferevent 有四个水位:读取低水位、读取高水位、写入低水位、写入高水位
- bufferevent 也有“错误”或者“事件”回调
- BEV_EVENT_READING:读取操作时发生某事件
- BEV_EVENT_WRITING:写入操作时发生某事件
- BEV_EVENT_ERROR : 操作时发生错误。关于错误的更多信息,请调用EVUTIL_SOCKET_ERROR()
- BEV_EVENT_TIMEOUT:发生超时
- BEV_EVENT_EOF:遇到文件结束指示
- BEV_EVENT_CONNECTED:请求的连接过程已经完成
3. 延迟回调
可以请求 bufferevent(或者 evbuffer)延迟其回调。条件满足时,延迟回调不会立即调用,而是在 event_loop()调用中被排队,然后在通常的事件回调之后执行
4. bufferevent 的选项标志
- BEV_OPT_CLOSE_ON_FREE:释放 bufferevent 时关闭底层传输端口。这将关闭底层套接字,释放底层 bufferevent 等
- BEV_OPT_THREADSAFE:自动为 bufferevent 分配锁,这样就可以安全地在多个线程中使用 bufferevent
- BEV_OPT_DEFER_CALLBACKS:设置这个标志时,bufferevent 延迟所有回调,如上所述
- BEV_OPT_UNLOCK_CALLBACKS:默认情况下,如果设置 bufferevent 为线程安全的,则 bufferevent 会在调用用户提供的回调时进行锁定。设置这个选项会让 libevent在执行回调的时候不进行锁定
5. 与基于套接字的 bufferevent 一起工作
它使用 libevent 的底层事件机制来检测底层网络套接字是否已经就绪,可以进行读写操作,并且使用底层网络调用(如 readv、writev、WSASend、WSARecv)来发送和接收数据。
5.1 创建基于套接字的buffevent
struct bufferevent *bufferevent_socket_new(
struct event_base *base, evutil_socket_t fd,
int options)
{
struct bufferevent_private *bufev_p;
struct bufferevent *bufev;
if ((bufev_p = mm_calloc(1, sizeof(struct bufferevent_private)))== NULL)
return NULL;
if (bufferevent_init_common(bufev_p, base, &bufferevent_ops_socket,
options) < 0) {
mm_free(bufev_p);
return NULL;
}
bufev = &bufev_p->bev;
evbuffer_set_flags(bufev->output, EVBUFFER_FLAG_DRAINS_TO_FD);
event_assign(&bufev->ev_read, bufev->ev_base, fd,
EV_READ|EV_PERSIST, bufferevent_readcb, bufev);
event_assign(&bufev->ev_write, bufev->ev_base, fd,
EV_WRITE|EV_PERSIST, bufferevent_writecb, bufev);
evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev);
evbuffer_freeze(bufev->input, 0);
evbuffer_freeze(bufev->output, 1);
return bufev;
}
5.2 在基于套接字的 t bufferevent 上启动连接
如果 bufferevent 的套接字还没有连接上,可以启动新的连接
int bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *sa, int socklen)
{
struct bufferevent_private *bufev_p =
EVUTIL_UPCAST(bev, struct bufferevent_private, bev);
evutil_socket_t fd;
int r = 0;
int result=-1;
int ownfd = 0;
_bufferevent_incref_and_lock(bev);
if (!bufev_p)
goto done;
fd = bufferevent_getfd(bev);
if (fd < 0) {
if (!sa)
goto done;
fd = socket(sa->sa_family, SOCK_STREAM, 0);
if (fd < 0)
goto done;
if (evutil_make_socket_nonblocking(fd)<0)
goto done;
ownfd = 1;
}
if (sa) {
r = evutil_socket_connect(&fd, sa, socklen);
if (r < 0)
goto freesock;
}
bufferevent_setfd(bev, fd);
if (r == 0) {
if (! be_socket_enable(bev, EV_WRITE)) {
bufev_p->connecting = 1;
result = 0;
goto done;
}
} else if (r == 1) {
/* The connect succeeded already. How very BSD of it. */
result = 0;
bufev_p->connecting = 1;
event_active(&bev->ev_write, EV_WRITE, 1);
} else {
/* The connect failed already. How very BSD of it. */
bufev_p->connection_refused = 1;
bufev_p->connecting = 1;
result = 0;
event_active(&bev->ev_write, EV_WRITE, 1);
}
goto done;
freesock:
_bufferevent_run_eventcb(bev, BEV_EVENT_ERROR);
if (ownfd)
evutil_closesocket(fd);
/* do something about the error? */
done:
_bufferevent_decref_and_unlock(bev);
return result;
}
5.3 通过主机名启动连接
常常需要将解析主机名和连接到主机合并成单个操作
int
bufferevent_socket_connect_hostname(struct bufferevent *bev,
struct evdns_base *evdns_base, int family, const char *hostname, int port)
{
char portbuf[10];
struct evutil_addrinfo hint;
int err;
struct bufferevent_private *bev_p =
EVUTIL_UPCAST(bev, struct bufferevent_private, bev);
if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC)
return -1;
if (port < 1 || port > 65535)
return -1;
BEV_LOCK(bev);
bev_p->dns_error = 0;
BEV_UNLOCK(bev);
evutil_snprintf(portbuf, sizeof(portbuf), "%d", port);
memset(&hint, 0, sizeof(hint));
hint.ai_family = family;
hint.ai_protocol = IPPROTO_TCP;
hint.ai_socktype = SOCK_STREAM;
bufferevent_suspend_write(bev, BEV_SUSPEND_LOOKUP);
bufferevent_suspend_read(bev, BEV_SUSPEND_LOOKUP);
bufferevent_incref(bev);
err = evutil_getaddrinfo_async(evdns_base, hostname, portbuf,
&hint, bufferevent_connect_getaddrinfo_cb, bev);
if (err == 0) {
return 0;
} else {
bufferevent_unsuspend_write(bev, BEV_SUSPEND_LOOKUP);
bufferevent_unsuspend_read(bev, BEV_SUSPEND_LOOKUP);
return -1;
}
}
- 这个函数解析名字 hostname,查找其 family 类型的地址(允许的地址族类型 有
AF_INET,AF_INET6和 AF_UNSPEC)。如果名字解析失败,函数将调用事件回调,报告错误事件。如果解析成功,函数将启动连接请求,就像 bufferevent_socket_connect()一样。- dns_base 参数是可选的:如果为 NULL,等待名字查找完成期间调用线程将被阻塞,而这通常不是期望的行为;如果提供 dns_base 参数,libevent 将使用它来异步地查询主机名。关于 DNS 的更多信息,请看第九章。
- 函数告知 libevent,bufferevent 上现存的套接字还没有连接,在名字解析和连接操作成功完成之前,不应该对套接字进行读取或者写入操作。
- 获取最近的错误。返回值0表示没有检测到 DNS 错误
int bufferevent_socket_get_dns_error(struct bufferevent *bev)
{
int rv;
struct bufferevent_private *bev_p =
EVUTIL_UPCAST(bev, struct bufferevent_private, bev);
BEV_LOCK(bev);
rv = bev_p->dns_error;
BEV_LOCK(bev);
return rv;
}
6. 通用的 bufferevent 操作
6.1 释放bufferent
void bufferevent_free(struct bufferevent *bufev)
{
BEV_LOCK(bufev);
bufferevent_setcb(bufev, NULL, NULL, NULL, NULL);
_bufferevent_cancel_all(bufev);
_bufferevent_decref_and_unlock(bufev);
}
- 这个函数释放 bufferevent。bufferevent 内部具有引用计数,所以,如果释放bufferevent时还有未决的延迟回调,则在回调完成之前 bufferevent 不会被删除
- 如果设置了 BEV_OPT_CLOSE_ON_FREE 标志,并且 bufferevent 有一个套接字或者底层bufferevent 作为其传输端口,则释放 bufferevent 将关闭这个传输端口
6.2 操作回调、水位和启用/禁用
- 设置回调
void bufferevent_setcb(struct bufferevent *bufev,//发生了事件的bufferevent
bufferevent_data_cb readcb, //读取足够多数据时的回调
bufferevent_data_cb writecb, //写入足够多数据时的回调
bufferevent_event_cb eventcb, //发生错误时的回调
void *cbarg) //通过它向回调传递数据
{
BEV_LOCK(bufev);
bufev->readcb = readcb;
bufev->writecb = writecb;
bufev->errorcb = eventcb;
bufev->cbarg = cbarg;
BEV_UNLOCK(bufev);
}
- 启用事件
int bufferevent_enable(struct bufferevent *bufev, short event)
{
struct bufferevent_private *bufev_private =
EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
short impl_events = event;
int r = 0;
_bufferevent_incref_and_lock(bufev);
if (bufev_private->read_suspended)
impl_events &= ~EV_READ;
if (bufev_private->write_suspended)
impl_events &= ~EV_WRITE;
bufev->enabled |= event;
if (impl_events && bufev->be_ops->enable(bufev, impl_events) < 0)
r = -1;
_bufferevent_decref_and_unlock(bufev);
return r;
}
- 可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE事件。没有启用读取或者写入事件时,bufferevent 将不会试图进行数据读取或者写入。
- 没有必要在输出缓冲区空时禁用写入事件:bufferevent 将自动停止写入,然后在有数据等待写入时重新开始
- 禁用事件
int bufferevent_disable(struct bufferevent *bufev, short event)
{
int r = 0;
BEV_LOCK(bufev);
bufev->enabled &= ~event;
if (bufev->be_ops->disable(bufev, event) < 0)
r = -1;
BEV_UNLOCK(bufev);
return r;
}
short bufferevent_get_enabled(struct bufferevent *bufev)
{
short r;
BEV_LOCK(bufev);
r = bufev->enabled;
BEV_UNLOCK(bufev);
return r;
}
- 调整水位
void bufferevent_setwatermark(struct bufferevent *bufev, short events,
size_t lowmark, size_t highmark)
{
struct bufferevent_private *bufev_private =
EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
BEV_LOCK(bufev);
if (events & EV_WRITE) {
bufev->wm_write.low = lowmark;
bufev->wm_write.high = highmark;
}
if (events & EV_READ) {
bufev->wm_read.low = lowmark;
bufev->wm_read.high = highmark;
if (highmark) {
/* There is now a new high-water mark for read.
enable the callback if needed, and see if we should
suspend/bufferevent_wm_unsuspend. */
if (bufev_private->read_watermarks_cb == NULL) {
bufev_private->read_watermarks_cb =
evbuffer_add_cb(bufev->input,
bufferevent_inbuf_wm_cb,
bufev);
}
evbuffer_cb_set_flags(bufev->input,
bufev_private->read_watermarks_cb,
EVBUFFER_CB_ENABLED|EVBUFFER_CB_NODEFER);
if (evbuffer_get_length(bufev->input) > highmark)
bufferevent_wm_suspend_read(bufev);
else if (evbuffer_get_length(bufev->input) < highmark)
bufferevent_wm_unsuspend_read(bufev);
} else {
/* There is now no high-water mark for read. */
if (bufev_private->read_watermarks_cb)
evbuffer_cb_clear_flags(bufev->input,
bufev_private->read_watermarks_cb,
EVBUFFER_CB_ENABLED);
bufferevent_wm_unsuspend_read(bufev);
}
}
BEV_UNLOCK(bufev);
}
6.3 操作 bufferevent 中的数据
- 获取输入/输出缓冲区
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev){
return bufev->input;
}
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev){
return bufev->output;
}
- 如果写入操作因为数据量太少而停止(或者读取操作因为太多数据而停止),则向输出缓冲区添加数据(或者从输入缓冲区移除数据)将自动重启操作。
- 向缓冲区写入数据
int bufferevent_write(struct bufferevent *bufev, const void *data,
size_t size)
{
if (evbuffer_add(bufev->output, data, size) == -1)
return (-1);
return 0;
}
int bufferevent_write_buffer(struct bufferevent *bufev,
struct evbuffer *buf)
{
if (evbuffer_add_buffer(bufev->output, buf) == -1)
return (-1);
return 0;
}
- 从输入缓冲区移除数据
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size)
{
return (evbuffer_remove(bufev->input, data, size));
}
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf)
{
return (evbuffer_add_buffer(buf, bufev->input));
}
6.4 读写超时
- 可以要求在一定量的时间已经流逝,而没有成功写入或者读取数据的时候调用一个超时回调
int bufferevent_set_timeouts(struct bufferevent *bufev,
const struct timeval *tv_read,
const struct timeval *tv_write)
{
int r = 0;
BEV_LOCK(bufev);
if (tv_read) {
bufev->timeout_read = *tv_read;
} else {
evutil_timerclear(&bufev->timeout_read);
}
if (tv_write) {
bufev->timeout_write = *tv_write;
} else {
evutil_timerclear(&bufev->timeout_write);
}
if (bufev->be_ops->adj_timeouts)
r = bufev->be_ops->adj_timeouts(bufev);
BEV_UNLOCK(bufev);
return r;
}
- 试图读取数据的时候,如果至少等待了 timeout_read 秒,则读取超时事件将被触发。试图写入数据的时候,如果至少等待了 timeout_write 秒,则写入超时事件将被触发
- 只有在读取或者写入的时候才会计算超时。也就是说,如果 bufferevent 的读取被禁
止,或者输入缓冲区满(达到其高水位),则读取超时被禁止- 读取或者写入超时发生时,相应的读取或者写入操作被禁止,然后超时事件回调被调用,带有标志BEV_EVENT_TIMEOUT | BEV_EVENT_READING或者EV_EVENT_TIMEOUT | BEV_EVENT_WRITING
6.5 对bufferevent 发起清空操作
int bufferevent_flush(struct bufferevent *bufev,
short iotype,
enum bufferevent_flush_mode mode)
{
int r = -1;
BEV_LOCK(bufev);
if (bufev->be_ops->flush)
r = bufev->be_ops->flush(bufev, iotype, mode);
BEV_UNLOCK(bufev);
return r;
}
- 清空 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 类型。
7. 类型特定的 bufferevent 函数
不能支持所有 bufferevent 类型,一下函数仅能用于基于套接字的 bufferevent
- 1.设置优先级
int bufferevent_priority_set(struct bufferevent *bufev, int priority)
{
int r = -1;
BEV_LOCK(bufev);
if (bufev->be_ops != &bufferevent_ops_socket)
goto done;
if (event_priority_set(&bufev->ev_read, priority) == -1)
goto done;
if (event_priority_set(&bufev->ev_write, priority) == -1)
goto done;
r = 0;
done:
BEV_UNLOCK(bufev);
return r;
}
- 2.设置描述符
int bufferevent_setfd(struct bufferevent *bev, evutil_socket_t fd)
{
union bufferevent_ctrl_data d;
int res = -1;
d.fd = fd;
BEV_LOCK(bev);
if (bev->be_ops->ctrl)
res = bev->be_ops->ctrl(bev, BEV_CTRL_SET_FD, &d);
BEV_UNLOCK(bev);
return res;
}
- 3.返回event_base
struct event_base *bufferevent_get_base(struct bufferevent *bufev){
return bufev->ev_base;
}
- 4.返回作为 bufferevent 底层传输端口的另一个 bufferevent
struct bufferevent *bufferevent_get_underlying(struct bufferevent *bev)
{
union bufferevent_ctrl_data d;
int res = -1;
d.ptr = NULL;
BEV_LOCK(bev);
if (bev->be_ops->ctrl)
res = bev->be_ops->ctrl(bev, BEV_CTRL_GET_UNDERLYING, &d);
BEV_UNLOCK(bev);
return (res<0) ? NULL : d.ptr;
}
8.手动锁定和解锁
有时候需要确保对 bufferevent 的一些操作是原子地执行的。为此,libevent 提供了手动锁定和解锁 bufferevent 的函数
- 上锁
void bufferevent_lock(struct bufferevent *bev)
{
_bufferevent_incref_and_lock(bev);
}
void _bufferevent_incref_and_lock(struct bufferevent *bufev)
{
struct bufferevent_private *bufev_private =
BEV_UPCAST(bufev);
BEV_LOCK(bufev);
++bufev_private->refcnt;
}
/** Internal: Grab the lock (if any) on a bufferevent */
#define BEV_LOCK(b) do { \
struct bufferevent_private *locking = BEV_UPCAST(b); \
EVLOCK_LOCK(locking->lock, 0); \
} while (0)
/** Acquire a lock. */
#define EVLOCK_LOCK(lockvar,mode) \
do { \
if (lockvar) \
_evthreadimpl_lock_lock(mode, lockvar); \
} while (0)
int _evthreadimpl_lock_lock(unsigned mode, void *lock)
{
if (_evthread_lock_fns.lock)
return _evthread_lock_fns.lock(mode, lock);
else
return 0;
}
- 解锁
void bufferevent_unlock(struct bufferevent *bev)
{
_bufferevent_decref_and_unlock(bev);
}
...