libevent源码V--bufferevent源码

参考:http://blog.csdn.net/luotuo44/article/details/39344743

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_setcb完成这个工作。该函数相当简单,也就是进行一些赋值操作。

//bufferevent.c文件
void
bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg)
{
	//bufferevent结构体内部有一个锁变量
	BEV_LOCK(bufev);
 
	bufev->readcb = readcb;
	bufev->writecb = writecb;
	bufev->errorcb = eventcb;
 
	bufev->cbarg = cbarg;
	BEV_UNLOCK(bufev);
}

令bufferevent可以工作:

 相信读者也知道,即使调用了bufferevent_socket_new和bufferevent_setcb,这个bufferevent还是不能工作,必须调用bufferevent_enable。为什么会这样的呢?

        如果看过之前的那些博文,相信读者知道,一个event能够工作,不仅仅需要new出来,还要调用event_add函数,把这个event添加到event_base中。在本文前面的代码中,并没有看到event_add函数的调用。所以还需要调用一个函数,把event添加到event_base中。函数bufferevent_enable就是完成这个工作的。

//bufferevent.c文件
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_free,释放了bufferevent
	_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;
 
	//调用对应类型的enbale函数。因为不同类型的bufferevent有不同的enable函数
	if (impl_events && bufev->be_ops->enable(bufev, impl_events) < 0)
		r = -1;
 
	//减少引用并解锁
	_bufferevent_decref_and_unlock(bufev);
	return r;
}

上面代码可以看到,最终会调用对应bufferevent类型的enable函数,对于socket bufferevent,其enable函数是be_socket_enable,代码如下:

//bufferevent.c文件
int
_bufferevent_add_event(struct event *ev, const struct timeval *tv)
{
	if (tv->tv_sec == 0 && tv->tv_usec == 0)
		return event_add(ev, NULL);
	else
		return event_add(ev, tv);
}
 
 
//bufferevent_sock.c文件
#define be_socket_add(ev, t)			\
	_bufferevent_add_event((ev), (t))
 
 
static int
be_socket_enable(struct bufferevent *bufev, short event)
{
	if (event & EV_READ) {
		if (be_socket_add(&bufev->ev_read,&bufev->timeout_read) == -1)
			return -1;
	}
	if (event & EV_WRITE) {
		if (be_socket_add(&bufev->ev_write,&bufev->timeout_write) == -1)
			return -1;
	}
	return 0;
}

如果读者熟悉Libevent的超时事件,那么可以知道Libevent是在event_add函数里面确定一个event的超时的。上面代码也展示了这一点,如果读或者写event设置了超时(即其超时值不为0),那么就会作为参数传给event_add函数。如果读者不熟悉的Libevent的超时事件的话,可以参考《超时event的处理》。

        用户可以调用函数bufferevent_set_timeouts,设置读或者写事件的超时。代码如下:

//bufferevent.c文件
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;
}
 
 
//bufferevent_sock.c文件
static int
be_socket_adj_timeouts(struct bufferevent *bufev)
{
	int r = 0;
	//用户监听了读事件
	if (event_pending(&bufev->ev_read, EV_READ, NULL))
		if (be_socket_add(&bufev->ev_read, &bufev->timeout_read) < 0)
			r = -1;
 
	//用户监听了写事件
	if (event_pending(&bufev->ev_write, EV_WRITE, NULL)) {
		if (be_socket_add(&bufev->ev_write, &bufev->timeout_write) < 0)
			r = -1;
	}
	return r;
}

从上面代码可以看到:用户不仅仅可以设置超时值,还可以修改超时值,也是通过这个函数进行修的。当然也是可以删除超时的,直接把超时参数设置成NULL即可。

        至此,已经完成了bufferevent的初始化工作,只需调用event_base_dispatch函数,启动发动机就可以工作了。

--------------------- 
作者:luotuo44 
来源:CSDN 
原文:https://blog.csdn.net/luotuo44/article/details/39344743 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值