转载请注明出处:http://blog.csdn.net/luotuo44/article/details/39344743
和之前的《Libevent工作流程探究》一样,这里也是用一个例子来探究bufferevent的工作流程。具体的例子可以参考《Libevent使用例子,从简单到复杂》,这里就不列出了。其实要做的例子也就是bufferevent_socket_new、bufferevent_setcb、bufferevent_enable这几个函数。
因为本文会用到《 Libevent工作流程探究》中提到的说法,比如将一个event插入到event_base中。所以读者最好先读一下那篇博文。此外,因为bufferevent结构体本身会使用evbuffer结构体和还会调用相应的一些操作,所以读者还应该先阅读《 evbuffer结构与基本操作》和《 更多evbuffer操作函数》。
bufferevent结构体:
bufferevent其实也就是在event_base的基础上再进行一层封装,其本质还是离不开event和event_base,从bufferevent的结构体就可以看到这一点。
bufferevent结构体中有两个event,分别用来监听同一个fd的可读事件和可写事件。为什么不用一个event同时监听可读和可写呢?这是因为监听可写是困难的,下面会说到原因。读者也可以自问一下,自己之前有没有试过用最原始的event监听一个fd的可写。
由于socket 是全双工的,所以在bufferevent结构体中,也有两个evbuffer成员,分别是读缓冲区和写缓冲区。 bufferevent结构体定义如下:
//bufferevent_struct.h文件
struct bufferevent {
struct event_base *ev_base;
//操作结构体,成员有一些函数指针。类似struct eventop结构体
const struct bufferevent_ops *be_ops;
struct event ev_read;//读事件event
struct event ev_write;//写事件event
struct evbuffer *input;//读缓冲区
struct evbuffer *output; //写缓冲区
struct event_watermark wm_read;//读水位
struct event_watermark wm_write;//写水位
bufferevent_data_cb readcb;//可读时的回调函数指针
bufferevent_data_cb writecb;//可写时的回调函数指针
bufferevent_event_cb errorcb;//错误发生时的回调函数指针
void *cbarg;//回调函数的参数
struct timeval timeout_read;//读事件event的超时值
struct timeval timeout_write;//写事件event的超时值
/** Events that are currently enabled: currently EV_READ and EV_WRITE
are supported. */
short enabled;
};
如果看过Libevent的参考手册的话,应该还会知道bufferevent除了用于socket外,还可以用于socketpair 和 filter。如果用面向对象的思维,应从这个三个应用中抽出相同的部分作为父类,然后派生出三个子类。
Libevent虽然是用C语言写的,不过它还是提取出一些公共部分,然后定义一个bufferevent_private结构体,用于保存这些公共部分成员。从集合的角度来说,bufferevent_private应该是bufferevent的一个子集,即一部分。但在Libevent中,bufferevent确实bufferevent_private的一个成员。下面是bufferevent_private结构体。
//bufferevent-internal.h文件
struct bufferevent_private {
struct bufferevent bev;
//设置input evbuffer的高水位时,需要一个evbuffer回调函数配合工作
struct evbuffer_cb_entry *read_watermarks_cb;
/** If set, we should free the lock when we free the bufferevent. */
//锁是Libevent自动分配的,还是用户分配的
unsigned own_lock : 1;
...
//这个socket是否处理正在连接服务器状态
unsigned connecting : 1;
//标志连接被拒绝
unsigned connection_refused : 1;
//标志是什么原因把 读 挂起来
bufferevent_suspend_flags read_suspended;
//标志是什么原因把 写 挂起来
bufferevent_suspend_flags write_suspended;
enum bufferevent_options options;
int refcnt;// bufferevent的引用计数
//锁变量
void *lock;
};
新建一个bufferevent:
函数bufferevent_socket_new可以完成这个工作。
//bufferevent-internal.h文件
struct bufferevent_ops {
const char *type;//类型名称
off_t mem_offset;//成员bev的偏移量
//启动。将event加入到event_base中
int (*enable)(struct bufferevent *, short);
//关闭。将event从event_base中删除
int (*disable)(struct bufferevent *, short);
//销毁
void (*destruct)(struct bufferevent *);
//调整event的超时值
int (*adj_timeouts)(struct bufferevent *);
/** Called to flush data. */
int (*flush)(struct bufferevent *, short, enum bufferevent_flush_mode);
//获取成员的值。具体看实现
int (*ctrl)(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *);
};
//bufferevent_sock.c文件
const struct bufferevent_ops bufferevent_ops_socket = {
"socket",
evutil_offsetof(struct bufferevent_private, bev),
be_socket_enable,
be_socket_disable,
be_socket_destruct,
be_socket_adj_timeouts,
be_socket_flush,
be_socket_ctrl,
};
//由于有几个不同类型的bufferevent,而且它们的enable、disable等操作是不同的。所以
//需要的一些函数指针指明某个类型的bufferevent应该使用哪些操作函数。结构体bufferevent_ops_socket
//就应运而生。对于socket,其操作函数如上。
//bufferevent_sock.c文件
struct bufferevent *
bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
int options)
{
struct bufferevent_private *bufev_p;
struct bufferevent *bufev;
...//win32
//结构体内存清零,所有成员都为0
if ((bufev_p = mm_calloc(1, sizeof(struct bufferevent_private)))== NULL)
return NULL;
//如果options中需要线程安全,那么就会申请锁
//会新建一个输入和输出缓存区
if (bufferevent_init_common(bufev_p, base, &bufferevent_ops_socket,
options) < 0) {
mm_free(bufev_p);
return NULL;
}
bufev = &bufev_p->bev;
//设置将evbuffer的数据向fd传
evbuffer_set_flags(bufev->output, EVBUFFER_FLAG_DRAINS_TO_FD);
//将fd与event相关联。同一个fd关联两个event
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的回调函数,使得外界给写缓冲区添加数据时,能触发
//写操作,这个回调对于写事件的监听是很重要的
evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev);
//冻结读缓冲区的尾部,未解冻之前不能往读缓冲区追加数据
//也就是说不能从socket fd中读取数据
evbuffer_freeze(bufev->input, 0);
//冻结写缓冲区的头部,未解冻之前不能把写缓冲区的头部数据删除
//也就是说不能把数据写到socket fd
evbuffer_freeze(bufev->output, 1);
return bufev;
}
留意函数里面的evbuffer_add_cb调用,后面会说到。
函数在最后面会冻结两个缓冲区。其实,虽然这里冻结了,但实际上Libevent在读数据或者写数据之前会解冻的读完或者写完数据后,又会马上冻结。这主要防止数据被意外修改。用户一般不会直接调用evbuffer_freeze或者evbuffer_unfreeze函数。一切的冻结和解冻操作都由Libevent内部完成。还有一点要注意,因为这里只是把写缓冲区的头部冻结了。所以还是可以往写缓冲区的尾部追加数据。同样,此时也是可以从读缓冲区读取数据。这个是必须的。因为在Libevent内部不解冻的时候,用户需要从读缓冲区中获取数据(这相当于从socket fd中读取数据),用户也需要把数据写到写缓冲区中(这相当于把数据写入到socket fd中)。
在bufferevent_socket_new函数里面会调用函数bufferevent_init_common完成公有部分的初始化。
//bufferevent.c文件
int
bufferevent_init_common(struct bufferevent_private *bufev_private,
struct event_base *base,
const struct bufferevent_ops *ops,
enum bufferevent_options options)
{
s