libevent的bufferevent

libevent的bufferevent

	libevent的bufferevent是在event的基础上自己维护了一个buffer,这意味着你的程序不再需要自己管理一个buffer。而且在windows上的异步IO不是“select()-like"接口,而是IOCP API,在一个socket已经准备好读写时并不会通知你的程序去从内核复制到用户内存(或者从内核复制到内核),而是在将数据从内核已经复制到用户内存(或反之)再通知你的程序。bufferevent API的使用刚好能满足这种情况,或者说在不支持IOCP的linux上能够模拟出这种功能。看下bufferevent的基本用法:

/* echo server */

/* For sockaddr_in */
#include 
/* For socket functions */
#include 
/* For fcntl */
#include 

#include 
#include 
#include 

#include 
#include 
#include 
#include 

#define MAXLINE 32
void readcb(struct bufferevent *bev, void *ctx)
{
	char *line = NULL;
	struct evbuffer *input, *output;
	size_t n;
	input = bufferevent_get_input(bev);
	output = bufferevent_get_output(bev);
	while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF)) != NULL)
	{
		evbuffer_add(output, line, strlen(line) + 1);
		evbuffer_add(output, "\n", 1);
		free(line);
	}
	
	char buf[MAXLINE];
	if (evbuffer_get_length(input) >= MAXLINE)
	{
		while (evbuffer_get_length(input))
		{
			int n = evbuffer_remove(input, buf, sizeof(buf));
			evbuffer_add(output, buf, n);
		}
		evbuffer_add(output, "\n", 1);
	}
}

void errorcb(struct bufferevent *bev, short error, void *ctx)
{
    if (error & BEV_EVENT_EOF) {
        /* connection has been closed, do any clean up here */
        /* ... */
    } else if (error & BEV_EVENT_ERROR) {
        /* check errno to see what error occurred */
        /* ... */
    } else if (error & BEV_EVENT_TIMEOUT) {
        /* must be a timeout event handle, handle it */
        /* ... */
    }
    bufferevent_free(bev);
}

void do_accept(evutil_socket_t listener, short event, void *arg)
{
	struct event_base *base = (struct event_base *)arg;
	struct sockaddr_in sin;
	socklen_t slen = sizeof(sin);
	int fd = accept(listener, (struct sockaddr *)&sin, &slen);
	if (fd < 0)
	{
		/* EAGIN? */
		perror("accept");
	}
	else 
	{
		struct bufferevent *bev;
		evutil_make_socket_nonblocking(fd);
		bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
		bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
		bufferevent_setwatermark(bev, EV_READ, 0, 102400);
		bufferevent_enable(bev, EV_READ | EV_WRITE);
	}
}

int main(int argc, char *argv[])
{
	struct sockaddr_in sin;
	evutil_socket_t listener;
	struct event_base *base;
	struct event * listener_event;

	base = event_base_new();
	if (!base)
		return -1; /* XXXerr */
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	sin.sin_port = htons(16998);

	listener = socket(AF_INET, SOCK_STREAM, 0);
	evutil_make_socket_nonblocking(listener);

	if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
		perror("bind failed:");
		return -1;
	}
	if (listen(listener, 16) < 0)
	{
		perror("listen failed:");
		return -1;
	}
	listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void *)base);
	event_add(listener_event, NULL);
	event_base_dispatch(base);
}

	bufferevent相关文档比较浅显,如果要掌握相关函数的具体行为,要研究源码实现细节了。
1.bufferevent_socket_new
	调用evbuffer_new初始化input,output(都是evbuffer类型)
	调用event_assign初始化ev_read,ev_write(都是event类型),初始化读写事件回调分别为bufferevent_readcb,  bufferevent_writecb
	调evbuffer_add_cb给output添加了一个callback bufferevent_socket_outbuf_cb
	结论:bufferevent主要是由读写事件(ev_read,ev_write),读写缓存(input,output)构成

2.bufferevent_enable
	其实是调用了event_add将相应读写事件加入事件监听队列poll。正如文档所说,如果相应事件不置为true,bufferevent是不会读写数据的。

3.bufferevent_socket_outbuf_cb
	bufferevent初始化output时,会给output添加这个回调函数,这个回调函数又会在条件允许的情况下把写事件添加入监听队列(event_add ev_write),一旦socket可写,events就会回调bufferevent_writecb,bufferevent_writecb会把写缓存里的(output)数据都发送出去。

4.evbuffer_add
	看evbuffer的这个添加数据的操作,跟踪函数调用很快发现主要是做了两件事:一是在把数据拷贝到缓存,缓存不足时还要做扩容操作;二是拷贝完数据后,要遍历evbuffer的回调函数队列,根据条件应逐一调用符合条件的回调函数。结合bufferevent的output来看,在bufferevent初始化时会给output注册一个回调函数bufferevent_socket_outbuf_cb,所以任何对output的evbuffer_add操作最后都会触发bufferevent_writecb, write数据。文档说,默认情况下bufferevent写是enable的,读却不是,即开始就是可写出数据的。其实能否读写的本质是,bufferevent的ev_read,ev_write是否通过event_add添加进了events的监听队列中去poll。再看bufferevent_write的实现,其实就是对output调了evbuffer_add。

5.bufferevent_readcb,bufferevent_writecb
	当bufferevent的socket变为可读写时分别调用的回调函数。这两个函数里read,write,并把数据写入或者写出buffer。还有一系列callback,称作用户定义回调,由函数bufferevent_setcb设置,其实是在bufferevent_readcb, bufferevent_writecb执行过程或者执行完成时回调的。比如IOCP的机制:当IO中可读写时,将数据从内核拷贝到用户buffer,并通知用户IO完成。也主要是由此处实现完成的。
原文地址:https://www.cnblogs.com/persistentsnail/p/3294851.html