精华内容
下载资源
问答
  • libevent中文帮助文档
    2019-09-26 14:26:56

    libevent

    libevent实际上就是对底层select/poll/epoll等进行了封装,每个event_base都有一种方法,该方法是select、poll、epoll、kqueue、devpoll、evport、win32。

    event_base

    使用libevent函数之前需要分配一个或多个event_base,每个event_base持有一个事件集合,以检测以确定哪个事件是激活的,它相当于是一个底座,只要向底座上插入事件,然后不断的监控事件,等待事件发生调用回调函数即可
    event_base有两种:(1)默认的;(2)可配置的,指定使用的后台方法等;

    event_loop

    一旦创建好事件根基event_base,并且在根基上安插好事件之后,需要对事件循环监控(换句话说就是等待事件的到来,触发事件的回调函数),有两种方式可以达到上面描述的功能,即:event_base_dispatch和event_base_loop。

    event

    event是对事件的抽象,它是libevent基本操作单元。每个事件代表一组条件的集合,这些条件包括:
    (1)文件描述符已经就绪、可以读取或者写入 
    (2)文件描述符变为就绪状态、可以读取或者写入(仅对于边沿触发IO)
    (3)超时事件
    (4)发生某信号 
    (5)用户触发事件
    创建event,关联到event_base后,事件进入已初始化状态,添加到event_base后,进入未决状态,此状态下,如果事件触发,则进入激活状态,事件回调将被执行。

    bufferevent

    上面提到的event_base、event主要是用作对事件处理(监听、分发等)的抽象,而bufferevent则是对数据缓冲/事件的抽象
    (1)数据缓冲
    bufferevent由一个底层的传输端口(如套接字)一个读取缓冲区一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。前者是IO就绪后,调用事件回调,通过原生接口send/recv从IO缓冲区写入/读取数据。后者是IO就绪后,首先由bufferevent通过原生接口从IO缓冲区写入读取数据后,再回调用事件回调,相当于bufferevent在中间封装了一层
    bufferevent有一个输入缓冲区、一个输出缓冲区,它们的类型都是evbuffer。有数据要写入到bufferevent时,添加数据到输出缓冲区。bufferevent 中有数据供读取的时候,从输入缓冲区抽取(drain)数据。
    每个bufferevent有两个数据相关的回调:一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用;通过调整bufferevent的读取和写入"水位(watermarks)"可以覆盖这些函数的默认行为。

    (2)事件
    bufferevent是对套接字的封装,具有相关的事件状态:链路建立、链路错误等。因此,它也是对事件的抽象,可以用它代替event,由event_base进行分发

    evbuffer

    对bufferevent的缓冲区抽象,提供缓冲区读写等能力。

    LibEvent集成步骤

    (1)创建一个event_base、监听socket,设置为非阻塞,完成bind、listen;
    (2)创建一个event,与event_base、监听socket关联;事件类型为EV_READ|EV_PERSIST,回调为do_accept
    (3)调用event_add向event_base中添加event,调用event_base_dispatch启动事件循环;
    (4)事件激活后,调用事件回调。然后通过两种方式进行读写:send/recv等原生接口bufferevent

    使用bufferevent方式进行数据读写

    (1)创建基于套接字的bufferevent,与event_base、socket关联。即此bufferevent操作的数据是对应fd的缓冲区数据,event_base可以分发bufferevent的事件;

    (2)[调用bufferevent_socket_connect,如果bufferevent还未设置socket,将为其分配一个新的流套接字,并且设置为非阻塞的;如果已经设置socket,将告知libevent套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作];

    (3)调用bufferevent_setcb()设置事件回调;

    (4)调用bufferevent_enable/bufferevent_disable启用/禁用EV_READ、EV_WRITE、EV_READ|EV_WRITE事件。读取或者写入事件被禁用时,bufferevent将不会试图进行数据读取或者写入;

    (5)调用bufferevent_setwatermark设置读取/写入的高低水位;

    (6)在事件回调中通过bufferevent_get_input、bufferevent_get_output获取bufferevent的输入/输出缓冲区evbuffer,然后通过相关接口(evbuffer_readln、evbuffer_add、evbuffer_remove等)对evbuffer进行操作,实现数据读取与写入;也可以直接使用bufferevent_write/bufferevent_write_buffer/bufferevent_read/bufferevent_read_buffer等接口对bufferevent进行操作,实现数据读取与写入,其实质也是对evbuffer进行操作

    更多相关内容
  • Libevent 编程中文帮助文档 版本:V1.0 日期:2016-11-15 作者:周勇 本文 档是 2009-2012 年由 Nick-Mathewson 基于 Attribution-Noncommercial-Share Alike 许可协议 3.0 创建,未来版本将会使用约束性更低的...
  • LibEvent中文帮助文档

    万次阅读 多人点赞 2016-12-02 09:44:13
    LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
      LibEvent

    快速可移植非阻塞式网络编程

      

    修订历史

    版本

    日期

    作者

    备注

    V1.0

    2016-11-15

    周勇

    Libevent编程中文帮助文档

     

    文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.

    此外,本文档的源代码示例也是基于BSD"3条款""修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:

    英文:http://libevent.org/

    中文:http://download.csdn.net/detail/zhouyongku/9700438

    请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.

    目录:

    1.关于本文档

    2.示例代码注意事项

    3.一个小的异步IO例子

    3.1怎样才能更方便?(Windows下怎么弄)

    3.2这一切效率如何,当真?

    4.正文前页

    4.1从1000英尺看LibEvent

    4.2库

    4.3头文件

    4.4如果需要使用老版本libevent

    4.4.1版本状态告知

    5.设置LibEvent库

    5.1LibEvent日志消息

    5.2处理致命错误

    5.3内存管理

    5.4线程和锁

    5.5调试锁的使用

    5.6调试事件使用

    5.7检查LibEvent的版本信息

    5.8释放LibEvent全局结构体

    6.创建Event_base

    6.1创建默认的Event_base

    6.2创建复杂的Event_base

    6.3检查Event_base的后台方法

    6.4重新分配一个Event_base

    6.5设置Event_base优先级

    6.6Fork()之后重新初始化一个Event_base

    6.7废弃的Event_base函数

    7.事件循环

    7.1运行循环

    7.2停止循环

    7.3检查事件

    7.4检查内部时间缓存

    7.5转存Event_base状态

    7.6每个event_base上运行一个event

    7.7废弃的事件回调函数

    8.处理事件

    8.1构造事件对象

    8.1.1事件标志

    8.1.2关于事件持久性

     8.1.3创建一个用本身作为回调函数参数的event

    8.1.4超时事件

    8.1.5构造信号事件

    8.1.6不用堆分配来设置事件

    8.2使事件未决和非未决

    8.3事件优先级

    8.4检查事件状态

    8.5查找当前运行事件

    8.6配置一次性事件

    8.7手动激活事件

    8.8优化通用超时

    8.9从已清除的内存中识别事件

    8.10废弃的事件操作函数

    9.辅助类型和函数

    9.1基本类型

    9.1.1Evuitl_socket_t

    9.1.2标准整数类型

    9.1.3各种兼容性类型

    9.2定时器可移植函数

    9.3套接字API兼容性

    9.4可移植的字符串操作函数

    9.5区域无关的字符串操作函数

    9.6IPv6辅助和兼容性函数

    9.7结构体可移植函数

    9.8安全随机数发生器

    10.Bufferevent概念和入门

    10.1Bufferevent和Evbuffer

    10.2回调和水位

    10.3延迟回调

    10.4Bufferevent的选项标志

    10.5与套接字的Bufferevent一起工作

    10.5.1创建基于套接字的Eventbuffer

    10.5.2在套接字的Bufferevent上启动连接

    10.5.3通过主机名启动连接

    10.6通用Bufferevent操作

    10.6.1释放Bufferevent

    10.6.2操作回调、水位、启用、禁用

    10.6.3操作Bufferevent中的数据

    10.6.4读写超时

    10.6.5对Bufferevent发起清空操作

    10.7类型特定的Bufferevent函数

    10.8手动锁定和解锁

    10.9已废弃的Bufferevent功能

    11.高级话题

    11.1成对的Bufferevent

    11.2过滤Bufferevent

    11.3限制最大单个读写大小

    11.4Bufferevent和速率限制

    11.4.1速率限制模型

    11.4.2为Bufferevent设置速率限制

    11.4.3为一组Eventbuffer设置速率限制

    11.4.4检查当前速率限制

    11.4.5手动调整速率限制

    11.4.6设置速率限制组的最小可能共享

    11.4.7  速率限制实现的限制

    11.5Bufferevent和SSL

    11.5.1创建和使用基于SSL的Bufferevent

    11.5.2线程和OpenSSL的一些说明

    12.Evbuffer IO实用功能

    12.1创建或释放一个Bvbuffer

    12.2Evbuffer和线程安全

    12.3检查Evbuffer

    12.4向Evbuffer添加数据:基础

    12.5将数据从一个Evbuffer移动到另一个

    12.6添加数据到Evbuffer的前面

    12.7重新排列Evbuffer的内部布局

    12.8从evbuffer移除数据

    12.9从Evbuffer中复制出数据

    12.10面向行的输入

    12.11在Evbuffer中搜索

    12.12检测数据而不复制

    12.13直接向Evbuffer添加数据

    12.14使用Evbuffer的网络IO

    12.15Evbuffer和回调

    12.16为基于evbuffer的IO避免数据复制

    12.17增加一个文件到Evbuffer

    12.18细粒度控制文件段

    12.19添加一个Evbuffer引用到另一个Evbuffer

    12.20让Evbuffer只能添加和删除

    12.21废弃的Evbuffer函数

    13.连接监听器:接受一个TCP连接

    13.1创建或释放一个evconnlistener

    13.1.1可识别的标志

    13.1.2连接监听器回调

    13.2启用和禁用evconnlistener

    13.3调整evconnlistener的回调函数

    13.4检查evconnlistener

    13.5检查错误

    13.6示例程序:一个echo服务器

    14.使用LibEvent的DNS:高和低层功能

    14.1正文前页:可移植的阻塞式名称解析

    14.2使用evdns_getaddrinfo()进行非阻塞名字解析

    14.3创建和配置evdns_base

    14.3.1使用系统配置初始化evdns

    14.3.2手动配置evdns

    14.3.3库端配置

    15.底层DNS接口

    15.1挂起DNS客户端操作,更换名字服务器

    16.DNS服务器接口

    16.1创建和关闭DNS服务器

    16.4DNS服务器示例

    17.废弃的DNS接口

    18.LibEvent编程示例

    18.1Event客户端服务器示例

    18.1.1客户端

    18.1.2服务器端

    18.1.3编译源码

    18.1.4脚本文件

    18.1.4运行测试

    18.2BufferEvent客户端服务器示例

    18.2.1客户端

    18.2.2服务器端

    18.2.3编译源码

    18.2.4脚本文件

    18.2.4运行测试

     

     

     

    
    
    
    展开全文
  • C语言实现的开源网络库 LibEvent2.0.22的帮助文档中文翻译版本 日期 作者 备注 2016-11-15 周勇 Libevent编程中文帮助文档V1.0
  • LibEvent中文帮助文档_zhouyongyi
  • LibEvent2.0.22的帮助文档中文翻译版本 日期 作者 备注 2016-11-15 周勇 Libevent编程中文帮助文档V1.0
  • LibEvent的说明帮助文档中文
  • LibEvent中文帮助文档--第1、2、3、4章

    万次阅读 2016-12-02 10:20:34
    LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
    

     

     LibEvent中文帮助文档--第1、2、3、4章

    返回主目录

    Libevent

    快速可移植非阻塞式网络编程

     

     

    修订历史

    版本

    日期

    作者

    备注

    V1.0

    2016-11-15

    周勇

    Libevent编程中文帮助文档

     

    文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.

    此外,本文档的源代码示例也是基于BSD"3条款""修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:

    英文:http://libevent.org/

    中文:http://blog.csdn.net/zhouyongku/article/details/53431597

    请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.

    返回主目录

    1.关于本文档

    为了更好掌握Libevent(2.0)进行快速可移植的异步IO网络编程,你需要具备:

    • C语言基本知识

    • C语言网络开发函数调用(socket(),connect()).

    2.示例代码注意事项

    本文档描述的源码示例需要运行在LinuxFreeBSDOpenBSDNetBSDMacOSXSolarisAndroid这些操作系统中,Windows环境下编译可能会有一些不兼容的情况发生.

    3.一个小的异步IO例子

    许多初学者往往都是使用阻塞式IO调用进行编程.当你调用一个同步IO的时候,除非操作系统已经完成了操作或者时间长到你的网络堆栈放弃的时候,否则系统是不会返回完成的.举个例子,当你调用"connect"做一个TCP连接的时候,你的操作系统必须排队处理来自发送到服务器的SYN,除非等到SYN_ACK包从对面接收到,或者是超时,否则操作是不会返回给你的应用程序.

     

    TCP三次握手

    第一次握手:建立连接时,客户端发送syn(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers).第二次握手:服务器收到syn,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN(syn=k),SYN+ACK,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手.

    这里有一个很简单的阻塞式网络调用的例子,它打开一个连接到www.google.com,发送它简单的HTTP请求,并打印输出到stdout.

    示例:一个简单的阻塞式HTTP客户端

    /* For sockaddr_in*/
    #include <netinet/in.h>
    /* For socket functions*/
    #include <sys/socket.h>
    /* For gethostbyname*/
    #include <netdb.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    int main(int c, char** v)
    {
    const char query[] =
    "GET / HTTP/1.0\r\n"
    "Host: www.google.com\r\n"
    "\r\n";
    const char hostname[] = "www.google.com";
    struct sockaddr_in sin;
    struct hostent* h;
    const char* cp;
    int fd;
    ssize_t n_written, remaining;
    char buf[1024];
    /* Look up the IP address for the hostname. Watch out; this isn’t
    threadsafe on most platforms.*/
    h = gethostbyname(hostname);
    if (!h) {
    fprintf(stderr, "Couldn’t lookup %s: %s", hostname, 	hstrerror(h_errno));
    	return 1;
    }
    if (h->h_addrtype != AF_INET) {
    	fprintf(stderr, "No ipv6 support, sorry.");
    	return 1;
    }
    /* Allocate a new socket*/
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
    	perror("socket");
    	return 1;
    }
    /* Connect to the remote host*/
    sin.sin_family = AF_INET;
    sin.sin_port = htons(80);
    sin.sin_addr =
    * (struct in_addr * )h->h_addr;
    if (connect(fd, (struct sockaddr * ) &sin, sizeof(sin))) {
    	perror("connect");
    	close(fd);
    	return 1;
    }
    /* Write the query.*/
    /* XXX Can send succeed partially?*/
    cp = query;
    remaining = strlen(query);
    while (remaining) {
    	n_written = send(fd, cp, remaining, 0);
    	if (n_written <= 0) {
    	perror("send");
    	return 1;
    }
    remaining -= n_written;
    cp += n_written;
    }
    /* Get an answer back.*/
    while (1) {
    	ssize_t result = recv(fd, buf, sizeof(buf), 0);
    	if (result == 0) {
    	break;
    } else if (result < 0) {
    	perror("recv");
    	close(fd);
    	return 1;
    }
    fwrite(buf, 1, result, stdout);
    }
    close(fd);
    return 0;
    }

    我们可以看出,上面的代码均是阻塞式操作:调用gethostbyname的去解析www.google.com的时候不等到它成功或者失败不会返回;调用connect的时候不等到它连接成功不会返回;调用recv的时候不等到它接受到数据或关连接关闭的时候不会返回;同样,调用send的时候至少等到待把输出区间待发送数据刷新到内核的写缓冲区之后才会返回.

     

     

    现在看起来阻塞式IO还没有让人多厌烦,因为当你的程序在处理网络IO的同时不会处理其它业务,那么阻塞式IO能满足你的编程需求,但是想一下,当你想写一个程序支持多个连接,比如说需要同时从两个连接中读取数据,那么这个时候你就不知道到底先从哪个连接读取数据.

    一个糟糕的例子:

    /* This won’t work.*/
    char buf[1024];
    int i, n;
    while (i_still_want_to_read()) 
    {
    for (i=0; i<n_sockets; ++i) {
    n = recv(fd[i], buf, sizeof(buf), 0);
    if (n==0)
    handle_close(fd[i]);
    else if (n<0)
    handle_error(fd[i], errno);
    else
    handle_input(fd[i], buf, n);
    }
    }

    因为如果数据到达fd[2]首先,程序甚至不会尝试从fd[2]读取数据,直到读取fd[0]fd[1]得到一些完成数据的读取.

     

    有时候人们使用多线程或多进程服务来解决这个问题.最简单的方法就是让单独的每个进程或线程来处理它们各自的连接.由于每个连接都有自己的处理过程所以等待一个连接过程的阻塞IO方法的调用将不会影响到其它任何别的连接的处理过程.

     

    这里有一个别的程序示例.这是一个很小的服务器,在端口40713上侦听TCP连接,每次一条一条地将数据读出来,当数据读出来的时候立刻进行R13加密,一条一条地写进输出缓冲区,在这个过程中它调用Unix fork()来创建一个新的过程来处理服务器接受到的连接.

    示例:Forking ROT13 服务器

    * For sockaddr_in*/
    #include <netinet/in.h>
    /* For socket functions*/
    #include <sys/socket.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #define MAX_LINE 16384
    char rot13_char(char c)
    {
    	/* We don’t want to use isalpha here; setting the locale would 
    change which characters are considered alphabetical.*/
    if ((c >= ’a’ && c <= ’m’) || (c >= ’A’ && c <= ’M’))
    return c + 13;
    else if ((c >= ’n’ && c <= ’z’) || (c >= ’N’ && c <= ’Z’))
    return c - 13;
    else
    return c;
    }
    void child(int fd)
    {
    char outbuf[MAX_LINE+1];
    size_t outbuf_used = 0;
    ssize_t result;
    while (1) {
    char ch;
    result = recv(fd, &ch, 1, 0);
    if (result == 0) {
    break;
    } else if (result == -1) {
    perror("read");
    break;
    }
    /* We do this test to keep the user from overflowing the buffer.*/
    if (outbuf_used < sizeof(outbuf)) {
    outbuf[outbuf_used++] = rot13_char(ch);
    }
    if (ch == ’\n’) {
    send(fd, outbuf, outbuf_used, 0);
    outbuf_used = 0;
    continue;
    }
    }
    }
    void run(void)
    {
    int listener;
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    sin.sin_port = htons(40713);
    listener = socket(AF_INET, SOCK_STREAM, 0);
    #ifndef WIN32
    {
    	int one = 1;
    	setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one,    
    	sizeof(one));
    }
    #endif
    	if (bind(listener, (struct sockaddr * )&sin, sizeof(sin)) < 0) {
    	perror("bind");
    	return;
    }
    if (listen(listener, 16)<0) 
    {
    	perror("listen");
    	return;
    }
    while (1) 
    {
    	struct sockaddr_storage ss;
    	socklen_t slen = sizeof(ss);
    	int fd = accept(listener, (struct sockaddr * )&ss, &slen);
    	if (fd < 0) 
    	{
    		perror("accept");
    	} 
    	else 
    	{
    		if (fork() == 0) 
    		{
    			child(fd);
    			exit(0);
    		}
    	}
    }
    }
    int main(int c, char** v)
    {
    run();
    return 0;
    }

    那么,我们有了一个完美的解决方案来处理多个即时连接,我就可以停这本书去做别的事了么?不完全是.首先,进程创建(甚至线程创建)的代价在一些平台上可能会非常昂贵.在实际中,你可能更想使用一个线程池,而不是创建新的进程.但更重要的是,线程在规模上并不尽如人意.如果您的程序同事需要处理成千上万的连接,可能会效率极低因为cpu同时运行的线程只有屈指可数的几个.

     

    但是如果连多线程都不能解决多个连接中的问题的话,那么还有什么别的方法?在Unix编程中,你就需要将你的socket设置成为非阻塞模式.Unix的调用方法如下:

    fcntl(fd, F_SETFL, O_NONBLOCK);

    这里fd代表的是socket的文件描述符.一旦你设置fd(套接字)为非阻塞模式,从那以后,无论什么时候,你调用fd函数都将立即完成操作或返回表示 "现在我无法取得任何进展,再试一次"的错误码.所以我们的两个套接字的例子可能会天真地写成下面的代码:

     

    糟糕的例子:忙查询所有套接字

    /* This will work, but the performance will be unforgivably bad.*/
    int i, n;
    char buf[1024];
    for (i=0; i < n_sockets; ++i)
    	fcntl(fd[i], F_SETFL, O_NONBLOCK);
    while (i_still_want_to_read())
    {
    	for (i=0; i < n_sockets; ++i) 
    	{
    		n = recv(fd[i], buf, sizeof(buf), 0);
    		if (n == 0) 
    		{
    			handle_close(fd[i]);
    		} 
    		else if (n < 0)
    		{
    			if (errno == EAGAIN) 
    			; 
    		/* The kernel didn’t have any data for us to read.*/
    			else handle_error(fd[i], errno);
    		} 
    		else 
    		{
    			handle_input(fd[i], buf, n);
    		}
    	}
    }

    现在我们正在使用非阻塞套接字,上面的代码会有效,但只有很少.性能将会很糟糕,有两个原因:首先,当任何连接中都没有数据的时候,该循环将继续,并且消耗完你的CPU;其次,如果你想处理一个或两个以上连接,无论有没有数据你都需要为每个连接调用系统内核.所以我们需要一种方法告诉内核:你必须等到其中一个socket已经准备好给我数据才通知我,并且告诉我是哪一个socket.解决这个问题人们常用的是select()方法.Select()方法的调用需要三套fd(数组),一个作为写,一个作为读,一个作为异常.该函数等待socket集合,并且修改这个集合以知道哪些socket可以使用了.

    下面是一个select()例子:

    示例:使用select()

    /* If you only have a couple dozen fds, this version won’t be awful*/
    fd_set readset;
    int i, n;
    char buf[1024];
    while (i_still_want_to_read())
    {
    	int maxfd = -1;
    	FD_ZERO(&readset);
    	/* Add all of the interesting fds to readset
    	*/
    	for (i=0; i < n_sockets; ++i) 
    	{
    		if (fd[i]>maxfd) maxfd = fd[i];
    		FD_SET(fd[i], &readset);
    	}
    	/* Wait until one or more fds are ready to read*/
    	select(maxfd+1, &readset, NULL, NULL, NULL);
    	/* Process all of the fds that are still set in readset*/
    	for (i=0; i < n_sockets; ++i) 
    	{
    		if (FD_ISSET(fd[i], &readset)) 
    		{
    			n = recv(fd[i], buf, sizeof(buf), 0);
    			if (n == 0) 
    			{
    				handle_close(fd[i]);
    			}
    			else if (n < 0)
    			{
    				if (errno == EAGAIN)
    				; 
    /* The kernel didn’t have any data for us to read.*/
    				else
    				handle_error(fd[i], errno);
    			} 
    			else
    			{
    				handle_input(fd[i], buf, n);
    			}
    		}
    	}
    }

    这是我们ROT13服务器重新实现,使用select().

    示例:基于select()ROT13服务器

    /* For sockaddr_in*/
    #include <netinet/in.h>
    /* For socket functions*/
    #include <sys/socket.h>
    /* For fcntl*/
    #include <fcntl.h>
    /* for select*/
    #include <sys/select.h>
    #include <assert.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #define MAX_LINE 16384
    
    char rot13_char(char c)
    {
    	/* We don’t want to use isalpha here; setting the locale 
        change which characters are considered alphabetical.*/
    	if ((c >= ’a’ && c <= ’m’) || (c >= ’A’ && c <= ’M’))
    	return c + 13;
    	else if ((c >= ’n’ && c <= ’z’) || (c >= ’N’ && c <= ’Z’))
    	return c - 13;
    	else
    	return c;
    }
    struct fd_state 
    {
    	char buffer[MAX_LINE];
    	size_t buffer_used;
    	int writing;
    	size_t n_written;
    	size_t write_upto;
    };
    struct fd_state*alloc_fd_state(void)
    {
    	struct fd_state
    	* state = malloc(sizeof(struct fd_state));
    	if (!state)
    	return NULL;
    	state->buffer_used = state->n_written = state->writing =
    	state->write_upto = 0;
    	return state;
    }
    void free_fd_state(struct fd_state* state)
    {
    	free(state);
    }
    void make_nonblocking(int fd)
    {
    	fcntl(fd, F_SETFL, O_NONBLOCK);
    }
    int do_read(int fd, struct fd_state* state)
    {
    	char buf[1024];
    	int i;
    	ssize_t result;
    	while (1) 
    	{
    		result = recv(fd, buf, sizeof(buf), 0);
    		if (result <= 0)
    		break;
    		for (i=0; i < result; ++i) 
    		{
    			if (state->buffer_used < sizeof(state->buffer))
    			state->buffer[state->buffer_used++] = rot13_char(buf[i]);
    			if (buf[i] == ’\n’) 
    			{
    				state->writing = 1;
    				state->write_upto = state->buffer_used;
    			}
    		}
    	}
    	if (result == 0) 
    	{
    		return 1;
    	} 
    	else if (result < 0) 
    	{
    		if (errno == EAGAIN)
    			return 0;
    		return -1;
    	}
    	return 0;
    }
    int do_write(int fd, struct fd_state * state)
    {
    	while (state->n_written < state->write_upto) 
    	{
    		ssize_t result = send(fd, state->buffer + state->n_written,
    		state->write_upto - state->n_written, 0);
    		if (result < 0) 
    		{
    			if (errno == EAGAIN)
    				return 0;
    			return -1;
    		}
    		assert(result != 0);
    		state->n_written += result;
    	}
    	if (state->n_written == state->buffer_used)
    	state->n_written = state->write_upto = state->buffer_used = 0;
    	state->writing = 0;
    	return 0;
    }
    void run(void)
    {
    	int listener;
    	struct fd_state
    	* state[FD_SETSIZE];
    	struct sockaddr_in sin;
    	int i, maxfd;
    	fd_set readset, writeset, exset;
    	sin.sin_family = AF_INET;
    	sin.sin_addr.s_addr = 0;
    	sin.sin_port = htons(40713);
    	for (i = 0; i < FD_SETSIZE; ++i)
    	state[i] = NULL;
    	listener = socket(AF_INET, SOCK_STREAM, 0);
    	make_nonblocking(listener);
    #ifndef WIN32
    	{
    		int one = 1;
    		setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    	}
    #endif
    	if (bind(listener, (struct sockaddr * )&sin, sizeof(sin)) < 0) {
    		perror("bind");
    		return;
    	}
    	if (listen(listener, 16)<0) 
    	{
    		perror("listen");
    		return;
    	}
    	FD_ZERO(&readset);
    	FD_ZERO(&writeset);
    	FD_ZERO(&exset);
    	while (1) 
    	{
    		maxfd = listener;
    		FD_ZERO(&readset);
    		FD_ZERO(&writeset);
    		FD_ZERO(&exset);
    		FD_SET(listener, &readset);
    		for (i=0; i < FD_SETSIZE; ++i) 
    		{
    			if (state[i]) 
    			{
    				if (i > maxfd)
    				maxfd = i;
    				FD_SET(i, &readset);
    				if (state[i]->writing) 
    					{
    						FD_SET(i, &writeset);
    					}
    				}
    		}
    		
    		if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) 
    		{
    			perror("select");
    			return;
    		}
    		if (FD_ISSET(listener, &readset)) 
    		{
    			struct sockaddr_storage ss;
    			socklen_t slen = sizeof(ss);
    			int fd = accept(listener, (struct sockaddr * )&ss, &slen);
    			if (fd < 0) 
    			{
    				perror("accept");
    			} 
    			else if (fd > FD_SETSIZE) 
    			{
    				close(fd);
    			} 
    			else 
    			{
    				make_nonblocking(fd);
    				state[fd] = alloc_fd_state();
    				assert(state[fd]);/* XXX */
    			}
    		}
    		for (i=0; i < maxfd+1; ++i) 
    		{
    			int r = 0;
    			if (i == listener)
    			continue;
    			if (FD_ISSET(i, &readset)) 
    			{
    				r = do_read(i, state[i]);
    			}
    			if (r == 0 && FD_ISSET(i, &writeset)) 
    			{
    				r = do_write(i, state[i]);
    			}
    			if (r) 
    			{
    				free_fd_state(state[i]);
    				state[i] = NULL;
    				close(i);
    			}
    		}
    	}
    }
    int main(int c, char** v)
    {
    	setvbuf(stdout, NULL, _IONBF, 0);
    	run();
    	return 0;
    }

    这还没完,因为生成和读select()的二进制流花费的时间与需要的最大fd成正比,而当socket的数量非常大的时候,select()的花费将会更恐怖.

     

    不同的操作系统提供了不同的替代select()功能的函数,例如poll()eopll()kqueqe()evports/dev/poll.这些都比select()具有更好的性能,除了poll()之外增加一个套接字、删除一个套接字以及通知套接字已经为IO准备好了这些动作的时间花费都是O(1).

     

    不幸的是,这些都没有一个有效的接口统一标准.Linuxeopll(),BSD(包含Darwin)kqueue(),Solarisevports/dev/poll,除此之外这些操作系统没有别的接口了.所以如果你想要写一个可移植的高效异步处理应用程序,你需要用抽象的方法封装这些所有的接口,并且提供其中最有效的方法.

     

    这些都是LibEventAPI最底层工作的API,提供了可代替select()的各种方法的统一接口,在运行的计算机上使用可用的最有效的版本.

     

    下面有另一个版本的ROT13异步服务器,这一次,我们将使用LibEvent2来代替select(),注意fd_sets已经变为:使用通过select()poll()epoll()kqueue()等一系列函数实现的event_base结构来聚合和分离事件.

     

    示例:一个给予LibEvent实现的低级ROT13

    /*For sockaddr_in*/ 
    #include <netinet/in.h>
    /*For socket functions*/ 
    #include <sys/socket.h>
    /*For fcntl*/ 
    #include <fcntl.h>
    #include <event2/event.h>
    #include <assert.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #define MAX_LINE 16384
    void do_read(evutil_socket_t fd, short events, void*arg);
    void do_write(evutil_socket_t fd, short events, void*arg);
    char rot13_char(char c)
    {
       	/*We don 't want to use isalpha here; setting the locale would
         change*which characters are considered alphabetical.*/ 
    	if ((c >= a ' && c<= 'm ') || (c >= 'A ' && c <= 'M '))return c + 13; 
      	else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))return c - 13; 
      	else
      	return c; 
    }
    
    struct fd_state
    {
      	char buffer[MAX_LINE]; 
    	size_t buffer_used; 
    	size_t n_written; 
    	size_t
        	write_upto; 
    	struct event*read_event; 
    	struct event*write_event; 
    }; 
    struct fd_state*alloc_fd_state(struct event_base*base, evutil_socket_t fd)
    {
      	struct fd_state*state = malloc(sizeof(struct fd_state)); 
    	if (!state)	return NULL; 
    	state->read_event = event_new(base, fd, EV_READ | EV_PERSIST, do_read,state); 
    	if (!state->read_event)
      	{
        		free(state); return NULL; 
      	} 
    	state->write_event = event_new(base, fd, EV_WRITE | EV_PERSIST, do_write,state); 
    	if (!state->write_event)
      	{
        		event_free(state->read_event); free(state); return NULL; 
      	}
      	state->buffer_used = state->n_written = state->write_upto = 0; 
    	assert(state->write_event); 
    	return state; 
    }
    
    void free_fd_state(struct fd_state*state)
    {
      	event_free(state->read_event); 
    	event_free(state->write_event); 
    	free(state); 
    } 
    void do_read(evutil_socket_t fd, short events, void*arg)
    {
      	struct fd_state*state = arg; char buf[1024]; int i; ssize_t result; 
      	while (1)
      	{
        		assert(state->write_event); 
    		result = recv(fd, buf, sizeof(buf), 0); 
    		if(result <= 0)break; 
    		for (i = 0; i < result; ++i)
        		{
          			if (state->buffer_used < sizeof(state->buffer))
    			state->buffer[state->buffer_used++] = rot13_char(buf[i]); 
    	 		if (buf[i] == '\n')
          			{
            				assert(state->write_event); 				
    				event_add(state->write_event, NULL); 
    				state->write_upto = state->buffer_used; 
          			} 
        		}
      	}
      	if (result == 0)
      	{
        		free_fd_state(state); 
      	}
      	else if (result < 0)
      	{
        		if (errno == EAGAIN) // XXXX use evutil macro
        			return ; 
    		perror("recv"); 
    		free_fd_state(state); 
      	}
    }
    
    void do_write(evutil_socket_t fd, short events, void*arg)
    {
      	struct fd_state*state = arg; 
      	while (state->n_written < state->write_upto)
      	{
        		ssize_t result = send(fd, state->buffer + state->n_written, state
          		->write_upto - state->n_written, 0); 
    	if (result < 0)
        {
          	if (errno == EAGAIN) // XXX use evutil macro
          		return ; 
    	free_fd_state(state); 
    	return ; 
        } 
    	assert(result != 0); 
    	state->n_written += result; 
      }
      if (state->n_written == state->buffer_used)
    	state->n_written = state->write_upto = state->buffer_used = 1; 		event_del(state->write_event); 
    }
    
    void do_accept(evutil_socket_t listener, short event, void*arg)
    {
      	struct event_base*base = arg; 
    	struct sockaddr_storage ss; 
    	socklen_t slen =sizeof(ss); 
    	int fd = accept(listener, (struct sockaddr*) &ss, &slen); 
    	if (fd < 0)
      	{
        		// XXXX eagain??
        		perror("accept"); 
      	} 
     	else if (fd > FD_SETSIZE)
      	{
        		close(fd);  // XXX replace all closes with EVUTIL_CLOSESOCKET
        	}
     	else
     	{
        		struct fd_state*state; 
    		evutil_make_socket_nonblocking(fd); 
    		state =alloc_fd_state(base, fd); 
    		assert(state);  /*XXX err */ 
    		assert(state->write_event); 
    		event_add(state->read_event, NULL); 
      	}
    }
    
    void run(void)
    {
      	evutil_socket_t listener; 
    	struct sockaddr_in sin; 
    	struct event_base*base;
        	struct event*listener_event; 
    	base = event_base_new(); 
    	if (!base)return ;  /*XXXerr */ 
    	sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0;
        	sin.sin_port = htons(40713); 
    	listener = socket(AF_INET, SOCK_STREAM, 0);
        	evutil_make_socket_nonblocking(listener); 
      	#ifndef WIN32
        	{
          		int one = 1; 
    		setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); 
        	} 
      	#endif 
      	if (bind(listener, (struct sockaddr*) &sin, sizeof(sin)) < 0)
      	{
        		perror("bind"); return ; 
      	} 
    	if (listen(listener, 16) < 0)
      	{
        		perror("listen"); 
    		return ; 
      	}
      	listener_event = event_new(base, listener, EV_READ | EV_PERSIST, do_accept, 
        	(void*)base);  /*XXX check it*/ 
    	event_add(listener_event, NULL);
        	event_base_dispatch(base); 
    }
    
    int main(int c, char**v)
    {
      	setvbuf(stdout, NULL, _IONBF, 0); 
    	run(); 
    	return 0; 
    }

    代码中需要注意:socket的类型定义我们使用evutil_socket_t类型来代替int,使用evutil_make_socket_noblocking来代替fcntl(O_NOBLOCK)生成一个非阻塞式socket.这样使我们的代码能够很好的兼容win32网络API.

    3.1怎样才能更方便?(Windows下怎么弄)

    你可能也注意到了,随着我们的代码的效率越来越高,代码的也越来越复杂.回到之前,我们不必亲自管理每个连接的缓冲区,而只需要为每个过程分配栈内存.我们也不必去监控socket是可以读还是可写,这些都是自动隐含在我们的代码中的.我们也不需要一个结构体去跟踪每个操作有多少完成了,只需要使用循环和栈变量就够了.

     

    此外,如果你深入Windows网络编程,你可能意识到在上面的例子中LibEvent可能并没有表现出最优的性能.Windows下要进行高速的异步IO不会使用select()接口,而是使用IOCP(完成端口) API.与其他快速的网络API不同,socket为操作准备好的时候,程序在即将执行操作之前IOCP是不会通知的.相反,程序会告诉Windows网络栈开始网络操作,IOCP会通知程序操作完成.

     

    幸运的是,LibEvent2bufferevents接口解决了这两个问题:首先使程序更加简单,其次提供了在WindowsLinux上高效运行的接口.

     

    下面是我们用bufferevents编写的最后一个ROT13服务器.

     

    示例:使用LibEvent编写的简单ROT13服务器

    /* For sockaddr_in*/
    #include <netinet/in.h>
    /* For socket functions*/
    #include <sys/socket.h>
    /* For fcntl*/
    #include <fcntl.h>
    #include <event2/event.h>
    #include <event2/buffer.h>
    #include <event2/bufferevent.h>
    #include <assert.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #define MAX_LINE 16384
    void do_read(evutil_socket_t fd, short events, void* arg);
    void do_write(evutil_socket_t fd, short events, void* arg);
    char rot13_char(char c)
    {
    	/* We don’t want to use isalpha here; setting the locale would change
    	which characters are considered alphabetical.*/
    	if ((c >= ’a’ && c <= ’m’) || (c >= ’A’ && c <= ’M’))
    		return c + 13;
    	else if ((c >= ’n’ && c <= ’z’) || (c >= ’N’ && c <= ’Z’))
    		return c - 13;
    	else
    		return c;
    }
    void readcb(struct bufferevent* bev, void * ctx)
    {
    	struct evbuffer* input, * output;
    	char* line;
    	size_t n;
    	int i;
    	input = bufferevent_get_input(bev);
    	output = bufferevent_get_output(bev);
    	while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) 
    	{
    		for (i = 0; i < n; ++i)
    			line[i] = rot13_char(line[i]);
    		evbuffer_add(output, line, n);
    		evbuffer_add(output, "\n", 1);
    		free(line);
    	}
    	if (evbuffer_get_length(input) >= MAX_LINE) 
    	{
    		/* Too long; just process what there is and go on so
    		that the buffer doesn’t grow infinitely long.*/
    		char buf[1024];
    		while (evbuffer_get_length(input)) 
    		{
    			int n = evbuffer_remove(input, buf, sizeof(buf));
    			for (i = 0; i < n; ++i)
    			buf[i] = rot13_char(buf[i]);
    			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 = arg;
    	struct sockaddr_storage ss;
    	socklen_t slen = sizeof(ss);
    	int fd = accept(listener, (struct sockaddr * )&ss, &slen);
    	if (fd < 0) 
    	{
    		perror("accept");
    	} 
    	else if (fd > FD_SETSIZE) 
    	{
    		close(fd);
    	} 
    	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, MAX_LINE);
    		bufferevent_enable(bev, EV_READ|EV_WRITE);
    	}
    }
    
    void run(void)
    {
    	evutil_socket_t listener;
    	struct sockaddr_in sin;
    	struct event_base * base;
    	struct event* listener_event;
    	base = event_base_new();
    	if (!base)	return; /*XXXerr*/
    	sin.sin_family = AF_INET;
    	sin.sin_addr.s_addr = 0;
    	sin.sin_port = htons(40713);
    	listener = socket(AF_INET, SOCK_STREAM, 0);
    	evutil_make_socket_nonblocking(listener);
    	#ifndef WIN32
    	{
    		int one = 1;
    		setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    	}
    	#endif
    	if (bind(listener, (struct sockaddr * )&sin, sizeof(sin)) < 0) 
    	{
    		perror("bind");
    		return;
    	}
    	if (listen(listener, 16)<0) 
    	{
    		perror("listen");
    		return;
    	}
    	listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, 
    	(void * )base);
    	/* XXX check it*/
    	event_add(listener_event, NULL);
    	event_base_dispatch(base);
    }
    
    int main(int c, char** v)
    {
    	setvbuf(stdout, NULL, _IONBF, 0);
    	run();
    	return 0;
    }

    3.2这一切效率如何,当真?

    在这里写了一个高效的代码.libevent页面上的基准是过时了

    • 这些文件是版权(c)2009 - 2012年由尼克·马修森和可用创意下议院Attribution-Noncommercial-Share都许可,3.0版.未来版本

    • 可能会用更少的限制性许可协议.

    • 此外,这些文档的源代码示例都是基于"3条款""修改的"BSD许可,请参考license_bsd文件全部条款.

    • 本文档的最新版本,请参阅【http://libevent.org/】

    • 本文档对应的最新版本代码,请安装git然后执行【git clone git://github.com/nmathewson/libevent-book.git】

    4.正文前页

    4.11000英尺看LibEvent

    LibEvent是用于编写高速可移植的非阻塞IO,它的目标是:

    • 可移植性:使用LibEvent编写的程序应该在LibEvent支持跨越的所有平台上工作,即使没有更好的方法来处理非阻塞式IO:LibEvent也应该支持一般的方法使程序可以运行在某些限制的环境中.

    • 速度:LibEvent试图在每一个平台实现最快的非阻塞式IO,而不会引入太多的额外开销.

    • 可扩展性:LibEvent设计为即使在成千上万的socket情况下也能良好工作.

    • 方便:无论在什么情况下,用LibEvent来编写程序最自然的方式都应该是稳定可靠的.

       

      LibEvent由下列组件构成:

    • evutil:用于抽象出不同平台网络实现的通用功能.

    • event and event_base:libevent的核心,为各种平台特定的、基于事件的非阻塞 IO后端提供抽象 API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基本的超时功能,检测 OS信号.

    • eufferevent: libevent基于事件的核心提供使用更方便的封装.除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时 IO已经真正发生.(bufferevent接口有多个后端,可以采用系统能够提供的更快的非阻塞 IO方式 , Windows中的 IOCP)

    • evbuffer: bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数.

    • evhttp:一个简单的 HTTP客户端/服务器实现.

    • evdns:一个简单的 DNS客户端/服务器实现.

    • evrpc:一个简单的 RPC实现.

       

    4.2

    创建 libevent,默认安装下列库:

    • libevent_core:所有核心的事件和缓冲功能,包含了所有的 event_baseevbufferbufferevent和工具函数.

    • libevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括 HTTPDNS RPC.

    • libevent:这个库因为历史原因而存在,它包含 libevent_core libevent_extra的内容 .不应该使用这个库未来版本的 libevent可能去掉这个库.

       

      某些平台上可能安装下列库:

    • libevent_pthreads:添加基于 pthread可移植线程库的线程和锁定实现.它独立于

    • libevent_core,这样程序使用 libevent时就不需要链接到 pthread,除非是以多线程方式使用 libevent.

    • libevent_openssl:这个库为使用 bufferevent OpenSSL进行加密的通信提供支持 .它独立于 libevent_core,这样程序使用 libevent时就不需要链接到 OpenSSL,除非是进行加密通信.

       

    4.3头文件

    libevent公用头文件都安装在 event2目录中,分为三类:

    • API头文件:定义 libevent公用接口.这类头文件没有特定后缀.

    • 兼容头文件:为已废弃的函数提供兼容的头部包含定义.不应该使用这类头文件,除非是在移植使用较老版本 libevent的程序时.

    • 结构头文件:这类头文件以相对不稳定的布局定义各种结构体.这些结构体中的一些是为了提供快速访问而暴露;一些是因为历史原因而暴露.直接依赖这类头文件中的任何结构体都会破坏程序对其他版本 libevent的二进制兼容性,有时候是以非常难以调试的方式出现.这类头文件具有后缀"_struct.h".(还存在不在 event2目录中的较老版本 libevent的头文件,请参考下节:如果需要使用老版本 libevent)

       

    4.4如果需要使用老版本libevent

    libevent 2.0以更合理的、不易出错的方式修正了 API.如果可能,编写新程序时应该使用libevent 2.0.但是有时候可能需要使用较老的 API,例如在升级已存的应用时,或者支持因为某些原因不能安装2.0或者更新版本 libevent的环境时.较老版本的 libevent头文件较少,也不安装在 event2目录中.

    老版本头文件

    当版本前头文件

    event.h

    event2/event*.h,event2/buffer*.h,

    event2/bufferevent*.h,event2/tag*.h

    evdns.h

    event2/dns*.h

    evhttp.h

    event2/http*/

    evrpc.h

    event2/rpc*.h

    evutil.h

    event2/util*.h

     

    2.0以及以后版本的 libevent,老的头文件仍然会作为新头文件的封装而存在.

     

    其他关于使用较老版本的提示:

    • 1.4版之前只有一个库 libevent,它包含现在分散到 libevent_core libevent_extra中的所有功能.

    • 2.0版之前不支持锁定:只有确定不同时在多个线程中使用同一个结构体时,libevent才是线程安全的.

      下面的节还将讨论特定代码区域可能遇到的已经废弃的 API.

    4.4.1版本状态告知

    之前的LibEvent版本1.4.7应该是已经完全放弃了.版本1.3e之前的也应该是满是bug.(另外,请不要向LibEvent维护者发送任何在1.4x或更早版本的新的特色,这些版本都已经发行了realease版本.如果你在1.3x或更早的版本发现了一个bug,在你提交反馈报告之前请确认在最新版本也出现了这些bug.所以后续版本的发行都是有原因的.)

     

    <<下一章>>

    
    
    
    展开全文
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
    
    LibEvent中文帮助文档--第12章【Evbuffer IO实用功能】

       返回主目录

    Libevent

    快速可移植非阻塞式网络编程

     

     

    修订历史

    版本

    日期

    作者

    备注

    V1.0

    2016-11-15

    周勇

    Libevent编程中文帮助文档

     

    文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.

    此外,本文档的源代码示例也是基于BSD"3条款""修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:

    英文:http://libevent.org/

    中文:http://blog.csdn.net/zhouyongku/article/details/53431750

    请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.

    <<上一章>>

    12.Evbuffer IO实用功能

    LibEvent的bufferevent实现了向后添加数据和前面移除数据的优化字节序列.evbuffer用于处理完了IO的缓冲部分.它不提供调度IO或当IO就绪时触发IO的功能:这就是bufferevent所做的事.

     

    除了特定说明外,本章的函数定义在<event2/buffer.h>中.

     

    12.1创建或释放一个Bvbuffer

     

    接口

    struct evbuffer* evbuffer_new(void);
    void evbuffer_free(struct evbuffer* buf);

    这些函数应该相当清晰:evbuffer_new()分配了一个空的evbuffer,evbuffer_free()删除一个或全部的内容.

     

    这些函数自从LibEvent0.8就存在.

     

    12.2Evbuffer和线程安全

     

    接口

    int evbuffer_enable_locking(struct evbuffer* buf, void * lock);
    void evbuffer_lock(struct evbuffer* buf);
    void evbuffer_unlock(struct evbuffer* buf);

    默认情况下多线程同时访问一个evbuffer是不安全的,如果非得这样,可以为evbuffer调用evbuffer_enable_locking()默认情况下,在多个线程中同时访问evbuffer 是不安全的.如果需要这样的访问,可以调用evbuffer_enable_locking() . 如 果 lock参 数 为 NULL , libevent会 使 用evthread_set_lock_creation_callback提供的锁创建函数创建一个锁.否则, libevent将 lock参数用作锁.

     

    evbuffer_lock()和evbuffer_unlock()函数分别请求和释放evbuffer 上的锁.可以使用这两个函数让一系列操作是原子的.如果evbuffer 没有启用锁,这两个函数不做任何操作.(注意:对于单个操作,不需要调用evbuffer_lock()和evbuffer_unlock():如果evbuffer启用了锁,单个操作就已经是原子的.只有在需要多个操作连续执行,不让其线程介入的时候,才需要手动锁定evbuffer)

     

    这些函数都在2.0.1-alpha版本中引入.

     

    12.3检查Evbuffer

     

    接口

    size_t evbuffer_get_length(const struct evbuffer* buf);

    这个函数返回 evbuffer存储的字节数,它在2.0.1-alpha版本中引入.

     

    接口

    size_t evbuffer_get_contiguous_space(const struct evbuffer* buf);

    这个函数返回连续地存储在 evbuffer前面的字节数. evbuffer中的数据可能存储在多个分隔开的内存块中,这个函数返回当前第一个块中的字节数.

     

    这个函数在2.0.1-alpha版本引入.

     

    12.4向Evbuffer添加数据:基础

     

    接口

    int evbuffer_add(struct evbuffer* buf, const void * data, size_t datlen);

    这个函数添加 data处的 datalen 字节到 buf 的末尾,成功时返回0,失败时返回-1.

     

    接口

    int evbuffer_add_printf(struct evbuffer* buf, const char * fmt, ...)
    int evbuffer_add_vprintf(struct evbuffer* buf, const char * fmt, va_list ap);

    这些函数添加格式化的数据到 buf末尾.格式参数和其他参数的处理分别与C 库函数printf和vprintf 相同.函数返回添加的字节数.

     

    接口

    int evbuffer_expand(struct evbuffer* buf, size_t datlen);

    这个函数修改缓冲区的最后一块,或者添加一个新的块,使得缓冲区足以容纳datlen 字节,而不需要更多的内存分配.

     

    示例

    /* Here are two ways to add "Hello world 2.0.1" to a buffer.*/
    /* Directly:*/
    evbuffer_add(buf, "Hello world 2.0.1", 17);
    /* Via printf:*/
    evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1);

    evbuffer_add()和evbuffer_add_printf()函数在libevent 0.8版本引入;evbuffer_expand()首次出现在0.9版本,而evbuffer_add_printf()首次出现在1.1版本.

     

    12.5将数据从一个Evbuffer移动到另一个

    为提高效率,libevent具有将数据从一个 evbuffer移动到另一个的优化函数.

     

    接口

    int evbuffer_add_buffer(struct evbuffer* dst, struct evbuffer * src);
    int evbuffer_remove_buffer(	struct evbuffer* src, 
    				struct evbuffer * dst,
    				size_t datlen);

    evbuffer_add_buffer()将src 中的所有数据移动到dst 末尾,成功时返回0,失败时返回-1.

     

    evbuffer_remove_buffer()函数从src 中移动datlen 字节到dst 末尾,尽量少进行复制.如果字节数小于datlen,所有字节被移动.函数返回移动的字节数.

     

    evbuffer_add_buffer()在0.8版本引入;evbuffer_remove_buffer()是2.0.1-alpha版本新增加的.

     

    12.6添加数据到Evbuffer的前面

     

    接口

    int evbuffer_prepend(struct evbuffer* buf, const void * data, size_t size);
    int evbuffer_prepend_buffer(struct evbuffer* dst, struct evbuffer *src);

    除了将数据移动到目标缓冲区前面之外,这两个函数的行为分别与evbuffer_add()和evbuffer_add_buffer()相同.

    使用这些函数时要当心,永远不要对与bufferevent 共享的evbuffer 使用.这些函数是2.0.1-alpha版本新添加的.

     

    12.7重新排列Evbuffer的内部布局

    有时候需要取出 evbuffer前面的 N 字节,将其看作连续的字节数组.要做到这一点,首先必须确保缓冲区的前面确实是连续的.

     

    接口

    unsigned char* evbuffer_pullup(struct evbuffer * buf, ev_ssize_t size);

    evbuffer_pullup()函数"线性化"buf前面的 size字节,必要时将进行复制或者移动,以保证这些字节是连续的,占据相同的内存块.如果size 是负的,函数会线性化整个缓冲区.如果size 大于缓冲区中的字节数,函数返回NULL.否则,evbuffer_pullup()返回指向buf 中首字节的指针.

     

    调用 evbuffer_pullup()时使用较大的size 参数可能会非常慢,因为这可能需要复制整个缓冲区的内容.

     

    示例

    #include <event2/buffer.h>
    #include <event2/util.h>
    #include <string.h>
    int parse_socks4(struct evbuffer* buf, ev_uint16_t * port, ev_uint32_t * addr)
    {
    	/* Let’s parse the start of a SOCKS4 request! The format is easy:
    	*1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
    	*destip.*/
    	unsigned char* mem;
    	mem = evbuffer_pullup(buf, 8);
    	if (mem == NULL) 
    	{
    		/* Not enough data in the buffer*/
    		return 0;
    	} 
    	else if (mem[0] != 4 || mem[1] != 1) 
    	{
    		/* Unrecognized protocol or command*/
    		return -1;
    	} 
    	else 
    	{
    		memcpy(port, mem+2, 2);
    		memcpy(addr, mem+4, 4);
    		* port = ntohs( * port);
    		* addr = ntohl( * addr);
    		/* Actually remove the data from the buffer now that we know we
    		like it.*/
    		evbuffer_drain(buf, 8);
    		return 1;
    	}
    }

    提示使用 evbuffer_get_contiguous_space()返回的值作为尺寸值调用evbuffer_pullup()不会导致任何数据复制或者移动.

     

    evbuffer_pullup()函数由2.0.1-alpha版本新增加:先前版本的 libevent总是保证 evbuffer中的数据是连续的,而不计开销.

     

    12.8从evbuffer移除数据

     

    接口

    int evbuffer_drain(struct evbuffer* buf, size_t len);
    int evbuffer_remove(struct evbuffer* buf, void * data, size_t datlen);

    evbuffer_remove()函数从buf 前面复制和移除datlen 字节到data 处的内存中.如果可用字节少于datlen,函数复制所有字节.失败时返回-1,否则返回复制了的字节数.

     

    evbuffer_drain()函数的行为与evbuffer_remove()相同,只是它不进行数据复制:而只是将数据从缓冲区前面移除.成功时返回0,失败时返回-1.

     

    evbuffer_drain()由0.8版引入,evbuffer_remove()首次出现在0.9版.

     

    12.9从Evbuffer中复制出数据

    有时候需要获取缓冲区前面数据的副本,而不清除数据.比如说,可能需要查看某特定类型的记录是否已经完整到达,而不清除任何数据(像evbuffer_remove 那样) ,或者在内部重新排列缓冲区(像evbuffer_pullup 那样) .

     

    接口

    ev_ssize_t evbuffer_copyout(struct evbuffer* buf, void * data, size_t datlen);
    ev_ssize_t evbuffer_copyout_from(struct evbuffer* buf,
    				const struct evbuffer_ptr* pos,
    				void* data_out, size_t datlen);

    evbuffer_copyout()的行为与evbuffer_remove()相同,但是它不从缓冲区移除任何数据.也就是说,它从 buf前面复制datlen 字节到data 处的内存中.如果可用字节少于 datlen ,函数会复制所有字节.失败时返回-1,否则返回复制的字节数.如果从缓冲区复制数据太慢,可以使用evbuffer_peek()

     

    示例

    #include <event2/buffer.h>
    #include <event2/util.h>
    #include <stdlib.h>
    #include <stdlib.h>
    int get_record(struct evbuffer* buf, size_t * size_out, char ** record_out)
    {
    	/*Let’s assume that we’re speaking some protocol where records
    	contain a 4-byte size field in network order, followed by that
    	number of bytes. We will return 1 and set the ’out’ fields if we
    	have a whole record, return 0 if the record isn’t here yet, and
    	-1 on error.*/
    	size_t buffer_len = evbuffer_get_length(buf);
    	ev_uint32_t record_len;
    	char* record;
    	if (buffer_len < 4)
    		return 0; /* The size field hasn’t arrived.*/
    	/* We use evbuffer_copyout here so that the size field will stay on
    	the buffer for now.*/
    	evbuffer_copyout(buf, &record_len, 4);
    	/* Convert len_buf into host order.*/
    	record_len = ntohl(record_len);
    	if (buffer_len < record_len + 4)
    		return 0; /* The record hasn’t arrived*/
    	/* Okay, _now_ we can remove the record.*/
    	record = malloc(record_len);
    	if (record == NULL)
    		return -1;
    	evbuffer_drain(buf, 4);
    	evbuffer_remove(buf, record, record_len);
    	* record_out = record;
    	* size_out = record_len;
    	return 1;
    }

    12.10面向行的输入

     

    接口

    enum evbuffer_eol_style 
    {
    EVBUFFER_EOL_ANY,
    EVBUFFER_EOL_CRLF,
    EVBUFFER_EOL_CRLF_STRICT,
    EVBUFFER_EOL_LF,
    EVBUFFER_EOL_NUL
    };
    char* evbuffer_readln(struct evbuffer * buffer, 
    			size_t * n_read_out,
    			enum evbuffer_eol_style eol_style);

    很多互联网协议使用基于行的格式.evbuffer_readln()函数从evbuffer 前面取出一行,用一个新分配的空字符结束的字符串返回这一行.如果n_read_out 不是NULL,则它被设置为返回的字符串的字节数.如果没有整行供读取,函数返回空.返回的字符串不包括行结束符.

     

    evbuffer_readln()理解4种行结束格式:

    • EVBUFFER_EOL_LF:行尾是单个换行符(也就是\n,ASCII值是0x0A)

    • EVBUFFER_EOL_CRLF_STRICT:行尾是一个回车符,后随一个换行符(也就是\r\n,ASCII值是0x0D 0x0A)

    • EVBUFFER_EOL_CRLF:行尾是一个可选的回车,后随一个换行符(也就是说,可以是\r\n或者\n) .这种格式对于解析基于文本的互联网协议很有用,因为标准通常要求\r\n的行结束符,而不遵循标准的客户端有时候只使用\n.

    • EVBUFFER_EOL_ANY:行尾是任意数量、任意次序的回车和换行符.这种格式不是特别有用.它的存在主要是为了向后兼容.

    注意如果使用 event_se_mem_functions()覆盖默认的malloc,则evbuffer_readln 返回

    的字符串将由你指定的 malloc替代函数分配.

    示例

    char* request_line;
    size_t len;
    request_line = evbuffer_readln(buf, &len, EVBUFFER_EOL_CRLF);
    if (!request_line) 
    {
    	/* The first line has not arrived yet.*/
    } 
    else 
    {
    	if (!strncmp(request_line, "HTTP/1.0 ", 9)) 
    	{
    	/* HTTP 1.0 detected ...*/
    	}
    	free(request_line);
    }

    evbuffer_readln()接口在1.4.14-stable及以后版本中可用.

     

    12.11在Evbuffer中搜索

    evbuffer_ptr结构体指示 evbuffer中的一个位置,包含可用于在evbuffer 中迭代的数据.

     

    接口

    struct evbuffer_ptr 
    {
    	ev_ssize_t pos;
    	struct 
    	{
    		/* internal fields*/
    	} _internal;
    };

    接口

    struct evbuffer_ptr evbuffer_search(struct evbuffer* buffer,
    					const char* what, 
    					size_t len, 
    					const struct evbuffer_ptr * start);
    struct evbuffer_ptr evbuffer_search_range(struct evbuffer* buffer,
    					const char* what, size_t len, 
    					const struct evbuffer_ptr * start,
    					const struct evbuffer_ptr* end);
    struct evbuffer_ptr evbuffer_search_eol(struct evbuffer* buffer,
    					struct evbuffer_ptr* start, 
    					size_t * eol_len_out,
    					enum evbuffer_eol_style eol_style);
    

    evbuffer_search()函数在缓冲区中查找含有len 个字符的字符串what. 函数返回包含字符串位置,或者在没有找到字符串时包含-1的evbuffer_ptr 结构体.如果提供了start 参数,则从指定的位置开始搜索;否则,从开始处进行搜索.

     

    evbuffer_search_range()函数和evbuffer_search 行为相同,只是它只考虑在end 之前出现的what.

     

    evbuffer_search_eol()函数像evbuffer_readln()一样检测行结束,但是不复制行,而是返回指向行结束符的evbuffer_ptr.如果eol_len_out 非空,则它被设置为EOL 字符串长度

     

    接口

    enum evbuffer_ptr_how 
    {
    	EVBUFFER_PTR_SET,
    	EVBUFFER_PTR_ADD
    };
    int evbuffer_ptr_set(	struct evbuffer* buffer, 
    			struct evbuffer_ptr * pos,
    			size_t position, 
    			enum evbuffer_ptr_how how);

    evbuffer_ptr_set函数操作 buffer中的位置 pos.如果how 等于EVBUFFER_PTR_SET,指针被移动到缓冲区中的绝对位置position;如果等于EVBUFFER_PTR_ADD,则向前移动position字节.成功时函数返回0,失败时返回-1.

     

    示例

    #include <event2/buffer.h>
    #include <string.h>
    /* Count the total occurrences of ’str’ in ’buf’.*/
    int count_instances(struct evbuffer* buf, const char * str)
    {
    	size_t len = strlen(str);
    	int total = 0;
    	struct evbuffer_ptr p;
    	if (!len)
    		/* Don’t try to count the occurrences of a 0-length string.*/
    		return -1;
    	evbuffer_ptr_set(buf, &p, 0, EVBUFFER_PTR_SET);
    	while (1) 
    	{
    		p = evbuffer_search(buf, str, len, &p);
    		if (p.pos < 0)
    			break;
    		total++;
    		evbuffer_ptr_set(buf, &p, 1, EVBUFFER_PTR_ADD);
    	}
    	return total;
    }

    警告任何修改 evbuffer或者其布局的调用都会使得 evbuffer_ptr失效,不能再安全地使用.这些接口是2.0.1-alpha版本新增加的.

     

    12.12检测数据而不复制

    有时候需要读取 evbuffer中的数据而不进行复制(像evbuffer_copyout()那样) ,也不重新排列内部内存布局(像evbuffer_pullup()那样) .有时候可能需要查看evbuffer 中间的数据.

     

    接口

    struct evbuffer_iovec 
    {
    	void* iov_base;
    	size_t iov_len;
    };
    int evbuffer_peek(	struct evbuffer* buffer, 
    			ev_ssize_t len,
    			struct evbuffer_ptr* start_at,
    			struct evbuffer_iovec* vec_out, 
    			int n_vec);

    调用 evbuffer_peek()的时候,通过vec_out 给定一个evbuffer_iovec 数组,数组的长度是n_vec.函数会让每个结构体包含指向evbuffer 内部内存块的指针(iov_base)和块中数据长度.

     

    如果 len小于0,evbuffer_peek()会试图填充所有evbuffer_iovec 结构体.否则,函数会进行填充,直到使用了所有结构体,或者见到len 字节为止.如果函数可以给出所有请求的数据,则返回实际使用的结构体个数;否则,函数返回给出所有请求数据所需的结构体个数.

     

    如果 ptr为 NULL,函数从缓冲区开始处进行搜索.否则,从ptr 处开始搜索.

     

    示例

    {
    	/* Let’s look at the first two chunks of buf, and write them to stderr.*/
    	int n, i;
    	struct evbuffer_iovec v[2];
    	n = evbuffer_peek(buf, -1, NULL, v, 2);
    	for (i=0; i<n; ++i) 
    	{ /* There might be less than two chunks available.*/
    		fwrite(v[i].iov_base, 1, v[i].iov_len, stderr);
    	}
    }
    
    {
    	/* Let’s send the first 4906 bytes to stdout via write.*/
    	int n, i, r;
    	struct evbuffer_iovec* v;
    	size_t written = 0;
    	/* determine how many chunks we need.*/
    	n = evbuffer_peek(buf, 4096, NULL, NULL, 0);
    	/* Allocate space for the chunks. This would be a good time to use
    	alloca() if you have it.*/
    	v = malloc(sizeof(struct evbuffer_iovec) * n);
    	/* Actually fill up v.*/
    	n = evbuffer_peek(buf, 4096, NULL, v, n);
    	for (i=0; i<n; ++i) 
    	{
    		size_t len = v[i].iov_len;
    		if (written + len > 4096)
    			len = 4096 - written;
    		r = write(1 /* stdout*/, v[i].iov_base, len);
    		if (r<=0)
    			break;
    		/* We keep track of the bytes written separately; if we don’t,
    		we may write more than 4096 bytes if the last chunk puts
    		us over the limit.*/
    		written += len;
    	}
    	free(v);
    }
    
    {
    	/* Let’s get the first 16K of data after the first occurrence of the
    	string "start\n", and pass it to a consume() function.*/
    	struct evbuffer_ptr ptr;
    	struct evbuffer_iovec v[1];
    	const char s[] = "start\n";
    	int n_written;
    	ptr = evbuffer_search(buf, s, strlen(s), NULL);
    	if (ptr.pos == -1)
    		return; /* no start string found.*/
    	/* Advance the pointer past the start string.*/
    	if (evbuffer_ptr_set(buf, &ptr, strlen(s), EVBUFFER_PTR_ADD) < 0)
    	return; /* off the end of the string.*/
    	while (n_written < 16 * 1024) 
    	{
    		/* Peek at a single chunk.*/
    		if (evbuffer_peek(buf, -1, &ptr, v, 1) < 1)
    			break;
    		/* Pass the data to some user-defined consume function*/
    		consume(v[0].iov_base, v[0].iov_len);
    		n_written += v[0].iov_len;
    		/* Advance the pointer so we see the next chunk next time.*/
    		if (evbuffer_ptr_set(buf, &ptr, v[0].iov_len, EVBUFFER_PTR_ADD)<0)
    			break;
    	}
    }

    注意修改 evbuffer_iovec所指的数据会导致不确定的行为如果任何函数修改了 evbuffer,则evbuffer_peek()返回的指针会失效如 果 在 多 个 线 程 中 使 用evbuffer , 确 保 在 调 用evbuffer_peek() 之 前 使 用evbuffer_lock(),在使用完evbuffer_peek()给出的内容之后进行解锁.

     

    这个函数是2.0.2-alpha版本新增加的.

     

    12.13直接向Evbuffer添加数据

    有时候需要能够直接向 evbuffer添加数据,而不用先将数据写入到字符数组中,然后再使用evbuffer_add()进行复制.有一对高级函数可以完成这种功能: evbuffer_reserve_space()和evbuffer_commit_space().跟evbuffer_peek()一样,这两个函数使用evbuffer_iovec 结构体来提供对evbuffer 内部内存的直接访问.

     

    接口

    int evbuffer_reserve_space(struct evbuffer* buf, ev_ssize_t size,
    				struct evbuffer_iovec* vec, 
    				int n_vecs);
    int evbuffer_commit_space(struct evbuffer* buf,
    				struct evbuffer_iovec* vec, 
    				int n_vecs);

    evbuffer_reserve_space()函数给出evbuffer 内部空间的指针.函数会扩展缓冲区以至少提供size 字节的空间.到扩展空间的指针,以及其长度,会存储在通过vec 传递的向量数组中,n_vec是数组的长度.

     

    n_vec的值必须至少是1.如果只提供一个向量,libevent会确保请求的所有连续空间都在单个扩展区中,但是这可能要求重新排列缓冲区,或者浪费内存.为取得更好的性能,应该至少提供2个向量.函数返回提供请求的空间所需的向量数.

     

    写入到向量中的数据不会是缓冲区的一部分,直到调用evbuffer_commit_space(),使得写入的数据进入缓冲区.如果需要提交少于请求的空间,可以减小任何 evbuffer_iovec结构体的 iov_len字段,也可以提供较少的向量.函数成功时返回0,失败时返回-1.

     

    提示和警告:

    • 调用 任 何 重 新排 列 evbuffer或 者 向 其 添 加 数 据 的 函 数 都 将 使 从evbuffer_reserve_space()获取的指针失效.

    • 当前实现中,不论用户提供多少个向量,evbuffer_reserve_space()从不使用多于两个.未来版本可能会改变这一点.

    • 如果在多个线程中使用 evbuffer,确保在调用evbuffer_reserve_space()之前使用evbuffer_lock()进行锁定,然后在提交后解除锁定

    示例

    /* Suppose we want to fill a buffer with 2048 bytes of output from a
    generate_data() function, without copying.*/
    struct evbuffer_iovec v[2];
    int n, i;
    size_t n_to_add = 2048;
    /* Reserve 2048 bytes. */
    n = evbuffer_reserve_space(buf, n_to_add, v, 2);
    if (n<=0)
    	return; /* Unable to reserve the space for some reason.*/
    for (i=0; i<n && n_to_add > 0; ++i) 
    {
    	size_t len = v[i].iov_len;
    	if (len > n_to_add) /* Don’t write more than n_to_add bytes.*/
    		len = n_to_add;
    	if (generate_data(v[i].iov_base, len) < 0) 
    	{
    		/* If there was a problem during data generation, we can just stop
    		here; no data will be committed to the buffer.*/
    		return;
    	}
    	/* Set iov_len to the number of bytes we actually wrote, so we
    	don’t commit too much.*/
    	v[i].iov_len = len;
    }
    /* We commit the space here. Note that we give it ’i’ (the number of
    vectors we actually used) rather than ’n’ (the number of vectors we
    had available.*/
    if (evbuffer_commit_space(buf, v, i) < 0)
    	return; /* Error committing*/

     

    糟糕的示例

    /* Here are some mistakes you can make with evbuffer_reserve().
    DO NOT IMITATE THIS CODE.*/
    struct evbuffer_iovec v[2];
    {
    	/* Do not use the pointers from evbuffer_reserve_space() after
    	calling any functions that modify the buffer.*/
    	evbuffer_reserve_space(buf, 1024, v, 2);
    	evbuffer_add(buf, "X", 1);
    	/* WRONG: This next line won’t work if evbuffer_add needed to rearrange
    	the buffer’s contents. It might even crash your program. Instead,
    	you add the data before calling evbuffer_reserve_space.*/
    	memset(v[0].iov_base, ’Y’, v[0].iov_len-1);
    	evbuffer_commit_space(buf, v, 1);
    }
    {
    	/* Do not modify the iov_base pointers.*/
    	const char* data = "Here is some data";
    	evbuffer_reserve_space(buf, strlen(data), v, 1);
    	/* WRONG: The next line will not do what you want. Instead, you
    	should _copy_ the contents of data into v[0].iov_base.*/
    	v[0].iov_base = (char * ) data;
    	v[0].iov_len = strlen(data);
    	/* In this case, evbuffer_commit_space might give an error if you’relucky*/
    	evbuffer_commit_space(buf, v, 1);
    }

    这个函数及其提出的接口从2.0.2-alpha版本就存在了.

     

    12.14使用Evbuffer的网络IO

    libevent中 evbuffer 的最常见使用场合是网络 IO.将evbuffer 用于网络IO 的接口是:

     

    接口

    int evbuffer_write(struct evbuffer* buffer, evutil_socket_t fd);
    int evbuffer_write_atmost(struct evbuffer* buffer, evutil_socket_t fd,
    	ev_ssize_t howmuch);
    int evbuffer_read(struct evbuffer* buffer, evutil_socket_t fd, int howmuch);

    evbuffer_read()函数从套接字fd 读取至多howmuch 字节到buffer 末尾.成功时函数返回读取的字节数,0表示EOF,失败时返回-1.注意,错误码可能指示非阻塞操作不能立即成功,应该检查错误码EAGAIN(或者Windows 中的WSAWOULDBLOCK) .如果howmuch 为负,evbuffer_read()试图猜测要读取多少数据.

     

    evbuffer_write_atmost()函数试图将buffer 前面至多howmuch 字节写入到套接字fd 中.成功时函数返回写入的字节数,失败时返回-1.跟evbuffer_read()一样,应该检查错误码,看是真的错误,还是仅仅指示非阻塞IO 不能立即完成.如果为howmuch 给出负值,函数会试图写入buffer 的所有内容.

     

    调用 evbuffer_write()与使用负的howmuch 参数调用evbuffer_write_atmost()一样:函数会试图尽量清空buffer 的内容.

     

    在 Unix中,这些函数应该可以在任何支持read 和write 的文件描述符上正确工作.在Windows中,仅仅支持套接字.注意,如果使用bufferevent,则不需要调用这些函数,bufferevent的代码已经为你调用了.

     

    evbuffer_write_atmost()函数在2.0.1-alpha版本中引入.

     

    12.15Evbuffer和回调

    evbuffer的用户常常需要知道什么时候向 evbuffer添加了数据,什么时候移除了数据.为支持这个,libevent为 evbuffer提高了通用回调机制

     

    接口

    struct evbuffer_cb_info 
    {
    	size_t orig_size;
    	size_t n_added;
    	size_t n_deleted;
    };
    typedef void ( * evbuffer_cb_func)(struct evbuffer* buffer,
    				const struct evbuffer_cb_info* info, 
    				void * arg);

    向 evbuffer添加数据,或者从中移除数据的时候,回调函数会被调用.函数收到缓冲区指针、一个evbuffer_cb_info 结构体指针,和用户提供的参数.evbuffer_cb_info结构体的orig_size字段指示缓冲区改变大小前的字节数,n_added字段指示向缓冲区添加了多少字节;n_deleted字段指示移除了多少字节.

     

    接口

    struct evbuffer_cb_entry;
    struct evbuffer_cb_entry* evbuffer_add_cb(struct evbuffer * buffer,
    					evbuffer_cb_func cb, 
    					void* cbarg);

    evbuffer_add_cb()函数为evbuffer 添加一个回调函数,返回一个不透明的指针,随后可用于代表这个特定的回调实例.cb参数是将被调用的函数,cbarg是用户提供的将传给这个函数的指针.

     

    可以为单个 evbuffer设置多个回调,添加新的回调不会移除原来的回调.

     

    示例

    #include <event2/buffer.h>
    #include <stdio.h>
    #include <stdlib.h>
    /* Here’s a callback that remembers how many bytes we have drained in
    total from the buffer, and prints a dot every time we hit a megabyte.*/
    struct total_processed 
    {
    	size_t n;
    };
    void count_megabytes_cb(struct evbuffer* buffer,
    const struct evbuffer_cb_info* info, void * arg)
    {
    	struct total_processed* tp = arg;
    	size_t old_n = tp->n;
    	int megabytes, i;
    	tp->n += info->n_deleted;
    	megabytes = ((tp->n) >> 20) - (old_n >> 20);
    	for (i=0; i<megabytes; ++i)
    		putc(’.’, stdout);
    }
    void operation_with_counted_bytes(void)
    {
    	struct total_processed* tp = malloc(sizeof( * tp));
    	struct evbuffer* buf = evbuffer_new();
    	tp->n = 0;
    	evbuffer_add_cb(buf, count_megabytes_cb, tp);
    	/* Use the evbuffer for a while. When we’re done:*/
    	evbuffer_free(buf);
    	free(tp);
    }

    注意:释放非空evbuffer 不会清空其数据,释放evbuffer 也不会为回调释放用户提供的数据指针.如果不想让缓冲区上的回调永远激活,可以移除或者禁用回调:

     

    接口

    int evbuffer_remove_cb_entry(struct evbuffer* buffer,
    		struct evbuffer_cb_entry* ent);
    int evbuffer_remove_cb(struct evbuffer* buffer, 
    		evbuffer_cb_func cb,
    		void* cbarg);
    #define EVBUFFER_CB_ENABLED 1
    int evbuffer_cb_set_flags(struct evbuffer* buffer,
    		struct evbuffer_cb_entry* cb,
    		ev_uint32_t flags);
    int evbuffer_cb_clear_flags(struct evbuffer* buffer,
    		struct evbuffer_cb_entry* cb,
    		ev_uint32_t flags);

    可以通过添加回调时候的 evbuffer_cb_entry来移除回调,也可以通过回调函数和参数指针来移除.成功时函数返回0,失败时返回-1.

     

    evbuffer_cb_set_flags()和evbuffer_cb_clear_flags()函数分别为回调函数设置或者清除给定的标志.当前只有一个标志是用户可见的:EVBUFFER_CB_ENABLED.这个标志默认是打开的.如果清除这个标志,对evbuffer 的修改不会调用回调函数.

     

    接口

    int evbuffer_defer_callbacks(struct evbuffer* buffer, struct event_base * base);

    跟 bufferevent回调一样,可以让evbuffer 回调不在evbuffer 被修改时立即运行,而是延迟到某event_base 的事件循环中执行.如果有多个evbuffer,它们的回调潜在地让数据添加到evbuffer 中,或者从中移除,又要避免栈崩溃,延迟回调是很有用的.

     

    如果回调被延迟,则最终执行时,它可能是多个操作结果的总和.

     

    与 bufferevent一样,evbuffer具有内部引用计数的,所以即使还有未执行的延迟回调,释放evbuffer 也是安全的.

     

    整个 回 调 系 统是 2.0.1-alpha 版 本 新 引 入 的. evbuffer_cb_(set|clear)_flags() 函 数 从

    2.0.2-alpha版本开始存在.

     

    12.16为基于evbuffer的IO避免数据复制

    真正高速的网络编程通常要求尽量少的数据复制,libevent为此提供了一些机制:

     

    接口

    typedef void ( * evbuffer_ref_cleanup_cb)(const void* data,
    				size_t datalen, 
    				void* extra);
    int evbuffer_add_reference(struct evbuffer* outbuf,
    			const void* data, 
    			size_t datlen,
    			evbuffer_ref_cleanup_cb cleanupfn, 
    			void* extra);

    这个函数通过引用向 evbuffer末尾添加一段数据.不会进行复制:evbuffer只会存储一个到data处的 datlen字节的指针.因此,在evbuffer 使用这个指针期间,必须保持指针是有效的.evbuffer会在不再需要这部分数据的时候调用用户提供的 cleanupfn函数,带有提供的data指针、datlen值和 extra指针参数.函数成功时返回0,失败时返回-1.

     

    示例

    #include <event2/buffer.h>
    #include <stdlib.h>
    #include <string.h>
    /* In this example, we have a bunch of evbuffers that we want to use to
    spool a one-megabyte resource out to the network. We do this
    without keeping any more copies of the resource in memory than
    necessary.*/
    
    #define HUGE_RESOURCE_SIZE (1024 * 1024)
    struct huge_resource 
    {
    	/* We keep a count of the references that exist to this structure,
    	so that we know when we can free it.*/
    	int reference_count;
    	char data[HUGE_RESOURCE_SIZE];
    };
    struct huge_resource* new_resource(void) 
    {
    	struct huge_resource* hr = malloc(sizeof(struct huge_resource));
    	hr->reference_count = 1;
    	/* Here we should fill hr->data with something. In real life,
    	we’d probably load something or do a complex calculation.
    	Here, we’ll just fill it with EEs.*/
    	memset(hr->data, 0xEE, sizeof(hr->data));
    	return hr;
    }
    void free_resource(struct huge_resource* hr) 
    {
    	--hr->reference_count;
    	if (hr->reference_count == 0)
    		free(hr);
    }
    static void cleanup(const void* data, size_t len, void * arg) 
    {
    	free_resource(arg);
    }
    /* This is the function that actually adds the resource to the buffer.*/
    
    void spool_resource_to_evbuffer(struct evbuffer* buf,struct huge_resource* hr)
    {
    	++hr->reference_count;
    	evbuffer_add_reference(buf, hr->data, HUGE_RESOURCE_SIZE,
    	cleanup, hr);
    }

    一些操作系统提供了将文件写入到网络,而不需要将数据复制到用户空间的方法.如果存在 ,可以使用下述接口访问这种机制:

     

     evbuffer_add_reference()函数在LibEvent2.0.2-alpha版本中开始出现.

     

    12.17增加一个文件到Evbuffer

    一些操作系统提供了直接写文件到网络的而不需要从文件拷贝数据到用户数据区的方法.可以用一些简单的接口使用这些机制.

     

    接口

    int evbuffer_add_file(struct evbuffer* output, 
    			int fd, ev_off_t offset,
    			size_t length);

    evbuffer_add_file假定已经有一个用于读的打开的文件描述符fd,从文件的offset位置开始读取length字节数到output中,函数执行成功返回0,失败返回-1.

     

    警告,在LibEvent2.x版本中,唯一可靠的处理数据的补充方式是用evbuffer_write*()发送到网络,用evbuffer_drain()排掉或者用evbuffer_*_buffer*()来移动到另外一个evbuffer上.不能使用evbuffer_remove()可靠地提取数据或使用evbuffer_pullup()来线性化数据等等.LibEvent2.1x试图修复这个限制.

     

    如果你的操作系统支持splice()或者sendfile()函数,LibEvent将会使用这些函数从fd套接字上直接发送数据到网络而不用经过RAM.如果你的操作系统不支持splice()或者sendfile()函数但是支持mmap()函数,LibEvent将会对文件进行内存映射,那么你的内核将会有希望判定其不需要拷贝数据到用户内存空间,否则LibEvent将会从磁盘读取数据到RAM中.

     

    在数据刷新到evbuffer()中之后或者evbuffer释放的时候文件描述符将会关闭,如果你不想这样而想要更细粒度地控制文件,请查看下面的函数.

     

    本函数定义在LibEvent2.0.1-alpha版本中.

     

    12.18细粒度控制文件段

    evbuffer_add_file()接口重复添加文件是无效的,因为该函数已经有了文件的所有权.

     

    接口

    struct evbuffer_file_segment;
    struct evbuffer_file_segment* evbuffer_file_segment_new(
    					int fd, 
    					ev_off_t offset, 
    					ev_off_t length, 
    					unsigned flags);
    void evbuffer_file_segment_free(struct evbuffer_file_segment* seg);
    int evbuffer_add_file_segment(struct evbuffer* buf,
    				struct evbuffer_file_segment* seg, 
    				ev_off_t offset, 
    				ev_off_t length);

    evbuffer_file_segment_new()函数创建并返回了一个新的evbuffer_file_segment对象来表示存储在fd描述符中的一片从offset开始的length字节数的文件片段,如果函数错误则返回NULL.

     

    文件片段可以由sendfile、splice、mmap、CreateFileMapping或者malloc()-and-read()来实现,具体需要视情况而定.这些片段使用最轻量级的支持机制创建并根据需要过度到一个重量级的机制(例如如果你的操作系统支持sendfile和mmap那么文件片段可以仅仅用sendfile去实现,直到你尝试去检查其内容,在这一点上它需要mmap()).可以用更细粒度的行为控制文件片段用这些标志:

    • EVBUF_FS_CLOSE_ON_FREE:如果设置了这个标志,则在使用evbuffer_file_segment_free()关闭文件片段的时候将会在底层关闭文件.

    • EVBUF_FS_DISABLE_MMAP:如果设置了这个标志,file_segment将不会对文件使用内存映射的后台模式,即使内存映射更合适.

    • EVBUF_FS_DISABLE_SENDFILE:如果设置了这个标志,file_segment将不会对文件使用使用sendfile后台模式,即使sendfile更合适.

    • EVBUF_FS_DISABLE_LOCKING:如果设置了这个标志,file_segment将不会再分配锁:多线程中以任何方式使用它将会使得它不再安全.

    一旦有了evbuffer_file_segment,可以用evbuffer_add_file_segment()来添加一些或所有evbuffer.这里的参数offset指的是文件片段内的偏移量而不是文件的偏移量.

     

    接口

    typedef void ( * evbuffer_file_segment_cleanup_cb)(
    			struct evbuffer_file_segment const* seg, 
    			int flags, 
    			void * arg);
    void evbuffer_file_segment_add_cleanup_cb(struct evbuffer_file_segment* seg,
    			evbuffer_file_segment_cleanup_cb cb, 
    			void* arg);

    可以添加一个回调函数到文件段,当最后的文件段引用已经释放并且文件段将要释放的时候调用.该回掉函数不能再生文件段或者增加到任何的buffer等.

     

    这些文件段函数首次出现在LibEvent2.1.1-alpha,evbuffer_file_segment_add_cleanup_cb()首次出现在LibEvent2.1.2-alpha.

     

    12.19添加一个Evbuffer引用到另一个Evbuffer

    可以添加一个引用到另一个Evbuffer:比起移动buffer的内容并且将它们添加到另一个,你可以为另一个添加一个引用,就好像你拷贝了所有的内部字节.

     

    接口

    int evbuffer_add_buffer_reference(struct evbuffer* outbuf,struct evbuffer* inbuf);

    evbuffer_add_buffer_reference()函数就好像从outbuf复制所有数据到inbuf,但是并没有执行任何的非必要拷贝操作.函数成功返回0,失败返回-1.

     

    注意后续inbuf内容的更改对outbuf无影响:该函数添加了当前evbuffer内容的引用而不是evbuffer本身.

     

    注意不能嵌套buffer引用:已经成为evbuffer_add_buffer_reference的outbuf不能成为别的buffer的inbuf.

     

    这个函数首次出现在LibEvent2.1.1-alpha中.

     

    12.20让Evbuffer只能添加和删除

     

    接口

    int evbuffer_freeze(struct evbuffer* buf, int at_front);
    int evbuffer_unfreeze(struct evbuffer* buf, int at_front);

    你可以使用这些函数来临时禁用改变evbuffer的头或尾.bufferevent代码使用这些函数内部来阻止意外编辑输出buffer的头或者输入buffer的尾.

     

     evbuffer_freeze()函数首次出现在LibEvent2.0.1-alpha版本.

     

    12.21废弃的Evbuffer函数

    evbuffer的接口在LibEvent2.0已经有了较大改变.在此之前每个evbuffer被实现为一个连续的块内存,访问效率很低.

     

    event.h头文件常用来显示evbuffer的内部结构,然而这些都是不再可用的,在1.4和2.0版本之中依赖evbuffer工作的函数都有了很大的改变.

     

    要访问evbuffer字节的数量,可以使用EVBUFFER_LENGTHH()宏,实际数据可以使用EVBUFFER_DATA()宏,这两个宏都在event2/bufer_compat.h头文件中.不过,小心EVBUFFER_DATA(b)是evbuvfer_pullup()别名,用它的代价比较昂贵.

     

    别的一些弃用的接口:

     

    接口

    char* evbuffer_readline(struct evbuffer * buffer);
    unsigned char* evbuffer_find(struct evbuffer * buffer,
    				const unsigned char* what, 
    				size_t len);

    evbuffer_find()函数工作原理与当前的evbuffer_readln(buffer,NULL,EVBUFFER_EOL_ANY)类似.

     

    evbuffer_find()函数会搜索在buffer中出现的字符串并返回指向该字符串的指针.与evbuffer_search()不同,它只能找出首个字符串.为了与老版本的该函数保持兼容,现在线性化整个buffer直到字符串的末尾.

     

    回调函数也不同:

     

    弃用的接口

    typedef void ( * evbuffer_cb)(struct evbuffer* buffer,
    				size_t old_len, size_t new_len, 
    				void* arg);
    void evbuffer_setcb(struct evbuffer* buffer, 
    			evbuffer_cb cb, 
    			void * cbarg);

    evbuffer只能一次设置一个回调函数,所以设置一个新的回调函数将会使之前的回调函数失效,设置回调函数为空则将使回调函数禁用.

     

    函数调用的时候使用新老版本的evbuffer的长度而不是获取evbuffer_cb_info_structure,因此如果old_len比new_len大那么数据将会流失,如果new_len比old_len大数据将被添加.由于不可能推迟一个回调调用,因此增加或删除操作将 不会被批处理成一个单个回调函数调用.

     

    这些废弃的函数仍然在event2/buffer_compatch.h中有效.

     

    <<下一章>>
     

    展开全文
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
  • 待补充
  • LibEvent在进程中有一些影响整个库的全局设置,在你调用LibEvent库中任何一个部分之前都需要进行设置,否则libEvent将会进入不一致的状态。 1 日志 LibEvent能记录内部的错误和警告日志,如果编译进日志支持功能,...
  • Linux下编译libevent的指导可以参考《4、《Libevent中文帮助文档》学习笔记4:Linux下编译libevent》,完成编译、安装,生成so库后,其他程序即可依赖libevent的so库,使用libevent的功能。 由于没有通过prefix指定...
  • LibEvent是用于编写高速可移植的非阻塞IO库,它的目标是: 可移植性:使用LibEvent编写的程序应该在LibEvent支持跨越的所有平台上工作,即使没有更好的方法来处理非阻塞式IO,LibEvent也应该支持一般的方法使程序...
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
  • 1、下载libevent软件包 直接在官网上下载软件包后解压libevent-2.1.8-stable.tar.gz即可。 2、在Linux编译机上编译libevent 运行如下命令,将libevent-2.1.8-stable.tar.gz上传到Linux编译机上: scp libevent...
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
  • 1 event客户端服务端示例 客户端: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h>...#include ...
  • 本章描述bufferevent的一些对通常使用不必要的高级特征。如果只想学习如何使用bufferevent,可以跳过这一章,直接阅读下一章。 待补充
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
  • libevent的基本操作单元是事件,每个事件代表一组条件的集合,这些条件包括: 文件描述符已经就绪,可以读取或者写入 文件描述符变为就绪状态,可以读取或者写入(仅对于边沿触发IO) 超时事件 发生某信号 用户...
  • 待补充。
  • 待补充。
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
  • LibEvent中文帮助文档:http://blog.csdn.net/zhouyongku LibEvent快速可移植非阻塞式网络编程
  • 下面有另一个版本的ROT13异步服务器,这一次,我们将使用LibEvent2来代替select(),注意fd_sets已经变为:使用通过select()、poll()、epoll()、kqueue()等一系列函数实现的event_base结构来聚合和分离事件。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,130
精华内容 452
热门标签
关键字:

libevent中文帮助文档