Libevent: 缓冲I/O事件bufferevent的封装

简介

I/O Buffers 是 libevent在常规的事件回调处理机制中封装的机制,被封装的事件称为缓冲事件(bufferevent), 该事件将自动的管理内部的I\O缓冲区。当buffer中的数据量到达一定的水位(由用户设定)时, 通过callback通知用户调用bufferevent_read从buffer中取数据或是bufferevent_write写入数据
到buffer中

I/O Buffers
libevent provides an abstraction on top of the regular event callbacks. This abstraction is called a buffered event. A buffered event provides input and output buffers that get filled and drained automatically. The user of a buffered event no longer deals directly with the I/O, but instead is reading from input and writing to output buffers.
Once initialized via bufferevent_new(), the bufferevent structure can be used repeatedly with bufferevent_enable() and bufferevent_disable(). Instead of reading and writing directly to a socket, you would call bufferevent_read() and bufferevent_write().

从基础接口入手

libevent Documentation中提及了几个常用的接口。

bufferevent_new
bufferevent_read、bufferevent_write
bufferevent_enable、bufferevent_disable

libevent的命名还是挺符合人类直觉的,相似的名称都具有相似的行为。
如 event_new <=> bufferevent_new、
event_base_set <=> bufferevent_base_set。

结合简介以及文档,不难猜测Libevent是如何进行封装的,无非是在event的基础上,管理buffer。

下面从结构体进一步验证猜想。

struct bufferevent {
	struct event_base *ev_base;
	/*bufferevent底层读写event, 将设置为内部callback
	   bufferevent_readcb、bufferevent_writecb
	*/
	struct event ev_read;
	struct event ev_write;

    /*I/O缓冲区*/
	struct evbuffer *input;
	struct evbuffer *output;
  
    /*水位*/
	struct event_watermark wm_read;
	struct event_watermark wm_write;
  
    /*用户定义的回调*/
	evbuffercb readcb;         
	evbuffercb writecb;        
	everrorcb errorcb;
	void *cbarg;

	int timeout_read;	/* in seconds */
	int timeout_write;	/* in seconds */

	short enabled;	/* events that are currently enabled */
};

现在已经可以确认,bufferevent就是将对文件描述符(fd)的读写分别用读写event来控制,并同时通过读写event管理input/out buffer,而buffer涉及水位watermark的管理。

那么实际上如何管理呢?
继续猜测:
当ev_read为active时, evread_cb 将fd中的数据载入到input buffer,直到高水位时,停止载入。若buffer中的数据量超过低水位,则通过readcb通知用户,来拿数据。

当ev_write为active时, evwrite_cb将output buffer中的数据载入到fd中。

因而,可以推出bufferevent的核心处理在于evread_cb 和 evwrite_cb

bufferevent核心机制

从上面我提到了bufferevent的核心机制应该在evread_cb 和 evwrite_cb?
那么,它们应该在哪里呢? 什么时候被设置呢?

既然bufferevent是通过常规event封装的,那么必然涉及常规事件的初始化。不妨从在bufferevent初始化或是使用的途中寻找有没有涉及到event_set的调用

bufferevent_new中找到了相关的代码:

struct bufferevent *
bufferevent_new(int fd, evbuffercb readcb, evbuffercb writecb,
    everrorcb errorcb, void *cbarg)
{
	struct bufferevent *bufev;
    
	// 注意: calloc可以说等价于malloc + memset(dest, 0, sz)
	if ((bufev = calloc(1, sizeof(struct bufferevent))) == NULL)
		return (NULL);
    
    /* 初始化buffer */
	if ((bufev->input = evbuffer_new()) == NULL) {
		free(bufev);
		return (NULL);
	}

	if ((bufev->output = evbuffer_new()) == NULL) {
		evbuffer_free(bufev->input);
		free(bufev);
		return (NULL);
	}
    
    /* 设置event_cb */
	event_set(&bufev->ev_read, fd, EV_READ, bufferevent_readcb, bufev);
	event_set(&bufev->ev_write, fd, EV_WRITE, bufferevent_writecb, bufev);

	bufferevent_setcb(bufev, readcb, writecb, errorcb, cbarg);

	/*
	 * Set to EV_WRITE so that using bufferevent_write is going to
	 * trigger a callback.  Reading needs to be explicitly enabled
	 * because otherwise no data will be available.
	 */
	bufev->enabled = EV_WRITE; // 启用读缓冲事件

	return (bufev);
}
event_set(&bufev->ev_read, fd, EV_READ, bufferevent_readcb, bufev);
event_set(&bufev->ev_write, fd, EV_WRITE, bufferevent_writecb, bufev);

因此,现在我们将关注点转移到bufferevent_readcbbufferevent_writecb的实现上。


注意:

这里有一段代码bufev->enabled = EV_WRITE;,可能现在还不是很清楚这是干嘛的, 但我们的现在应该先理清整体思路,不妨在此处作个标记,待整理完核心部分后,再回头研究。

bufferevent_readcb 的实现

废话不说,直接贴源码

tatic void
bufferevent_readcb(int fd, short event, void *arg)
{
	struct bufferevent *bufev = arg;
	int res = 0;
	short what = EVBUFFER_READ;
	size_t len;
	int howmuch = -1;

	// bufferevent超时,调用用户定义的err callback处理
	if (event == EV_TIMEOUT) {
		what |= EVBUFFER_TIMEOUT;
		goto error;
	}

	/*
	 * If we have a high watermark configured then we don't want to
	 * read more data than would make us reach the watermark.
	 */
	if (bufev->wm_read.high != 0) {
		howmuch = bufev->wm_read.high - EVBUFFER_LENGTH(bufev->input);
		/* we might have lowered the watermark, stop reading */
		/* 输入缓冲区内容高于限制的水位,需要停止从fd中读取数据 */
		if (howmuch <= 0) {
			struct evbuffer *buf = bufev->input;
			event_del(&bufev->ev_read);
			evbuffer_setcb(buf,
			    bufferevent_read_pressure_cb, bufev);
			return;
		}
	}

    // 从fd载入数据到input buffer
	res = evbuffer_read(bufev->input, fd, howmuch);
	if (res == -1) {
		if (errno == EAGAIN || errno == EINTR)
			goto reschedule;
		/* error case */
		what |= EVBUFFER_ERROR;
	} else if (res == 0) {
		/* eof case */
		what |= EVBUFFER_EOF;
	}

	if (res <= 0)
		goto error;

    // 由于没有设置EV_为持续事件需要重新添加到base的关注中
	bufferevent_add(&bufev->ev_read, bufev->timeout_read);

	/* See if this callbacks meets the water marks */
	len = EVBUFFER_LENGTH(bufev->input);

	/* input buffer中的数据量少于低水位 */
	if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
		return;
	/* 高于限定的水位,停止载入数据 */
	if (bufev->wm_read.high != 0 && len >= bufev->wm_read.high) {
		struct evbuffer *buf = bufev->input;
		event_del(&bufev->ev_read);

		/* Now schedule a callback for us when the buffer changes */
		evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
	}
    
	// input buffer len > low watermark, 回调readcb
	/* Invoke the user callback - must always be called last */
	if (bufev->readcb != NULL)
		(*bufev->readcb)(bufev, bufev->cbarg);
	return;

 reschedule:
	bufferevent_add(&bufev->ev_read, bufev->timeout_read);
	return;

 error:
	(*bufev->errorcb)(bufev, what, bufev->cbarg);
}

此处,我们可以借用蓄水池和水龙头来描述bufferevent_readcb的主要行为。

这个水龙头(bufferevent)比较智能,当水龙头中的水管充水时(可读事件active),水龙头根据蓄水池(input buffer)的水位决定是否打开放水。
当水位越高规定的上限时,水龙头不进水,否则水龙头放水到蓄水池。
当水位较合适时(下限 < 水位 < 上限), 通知用户来取水(bufev->readcb)

其他细节:

  • bufferevent_readcb是如何停止进水呢? 很简单, 只要水管没水进来。
    实际做法就是将读event移出base的关注。bufferevent_enable/bufferevent_enable也是同样的原理。

    但是,数据被取走时,当水平面下降,总得进水吧? 不然,buffer早晚干涸。
    此处就是引入buffer设置callback —— bufferevent_read_pressure_cb的原因。
    每当buffer中的水位限制发生改变或是水位发生改变,如果buffer设置了callback,总会尝试调用callback通知base恢复对读event的关注。

  • 诡异的"return"
    将注意力拉回到开头对高水位的判断。
    在数据量不少于高水位时,设置了bufferevent_read_pressure_cb,却不通知用户来取,直接return了。由于改变水位的操作一般而言都放在随后的bufev->readcb,这样处理可能导致数据一直停留在buffer中。

    if (bufev->wm_read.high != 0) {
    	howmuch = bufev->wm_read.high - EVBUFFER_LENGTH(bufev->input);
    	/* we might have lowered the watermark, stop reading */
    	/* 输入缓冲区内容高于限制的水位,需要停止从fd中读取数据 */
    	if (howmuch <= 0) {
    		struct evbuffer *buf = bufev->input;
    		event_del(&bufev->ev_read);
    		evbuffer_setcb(buf,
    		    bufferevent_read_pressure_cb, bufev);
    		return;
    	}
    }
    

    然而,我们注意到随后的代码段中,从fd灌水到buffer时,即使水位越过了高水位也会通知用户来取。因此,可以断定,只要在某次蓄水之后,用户未能取走数据使得水位降低到高水位之下时,数据可能将被抛弃在buffer中。只能通过额外的手段来取走了。

    /* 高于限定的水位,停止载入数据 */
    if (bufev->wm_read.high != 0 && len >= bufev->wm_read.high) {
    	struct evbuffer *buf = bufev->input;
    	event_del(&bufev->ev_read);
    
    	/* Now schedule a callback for us when the buffer changes */
    	evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
    }
    
    // input buffer len > low watermark, 回调readcb
    /* Invoke the user callback - must always be called last */
    if (bufev->readcb != NULL)
    	(*bufev->readcb)(bufev, bufev->cbarg);
    return;
    

bufferevent_writecb的实现

static void
bufferevent_writecb(int fd, short event, void *arg)
{
	struct bufferevent *bufev = arg;
	int res = 0;
	short what = EVBUFFER_WRITE;

	if (event == EV_TIMEOUT) {
		what |= EVBUFFER_TIMEOUT;
		goto error;
	}
    
	// 只要output buffer中有数据就往fd中输出
	if (EVBUFFER_LENGTH(bufev->output)) {
	    res = evbuffer_write(bufev->output, fd);
	    if (res == -1) {
#ifndef WIN32
/*todo. evbuffer uses WriteFile when WIN32 is set. WIN32 system calls do not
 *set errno. thus this error checking is not portable*/
		    if (errno == EAGAIN ||
			errno == EINTR ||
			errno == EINPROGRESS)
			    goto reschedule;
		    /* error case */
		    what |= EVBUFFER_ERROR;

#else
				goto reschedule;
#endif

	    } else if (res == 0) {
		    /* eof case */
		    what |= EVBUFFER_EOF;
	    }
	    if (res <= 0)
		    goto error;
	}
    
	// 只要output buffer中数据未读完就继续关注读事件
	if (EVBUFFER_LENGTH(bufev->output) != 0)
		bufferevent_add(&bufev->ev_write, bufev->timeout_write);

	/*
	 * Invoke the user callback if our buffer is drained or below the
	 * low watermark.
	 */
	// 只要output buffer枯竭或是低于写事件的低水位,则从调用
	// write_cb索要数据
	if (bufev->writecb != NULL &&
	    EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
		(*bufev->writecb)(bufev, bufev->cbarg);

	return;

 reschedule:
	if (EVBUFFER_LENGTH(bufev->output) != 0)
		bufferevent_add(&bufev->ev_write, bufev->timeout_write);
	return;

 error:
	(*bufev->errorcb)(bufev, what, bufev->cbarg);
}

bufferevent_writecb中实现就较简单了,只要有数据就一直往fd中写。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值