zoukankan      html  css  js  c++  java
  • 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完成。也主要是由此处实现完成的。
    
  • 相关阅读:
    二维莫队的一个细节
    错失AK良机的测试48T3 Walk
    枚举二进制子集
    又是一次值得纪念的考试
    测试46
    值得纪念的测试43
    点分治模板理解
    牛客多校第三场 G Removing Stones(分治+线段树)
    牛客多校第三场 F Planting Trees
    HDU6621 K-th Closest Distance HDU2019多校训练第四场 1008(主席树+二分)
  • 原文地址:https://www.cnblogs.com/persistentsnail/p/3294851.html
Copyright © 2011-2022 走看看