开发者社区> 技术小阿哥> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

libevent源码分析

简介:
+关注继续查看

1.Libevent有几个显著的亮点
        libevent 封装了底层最高效的网络模型,windows的compIO,linux下的epoll模型,freebsd的kqueue,提供统一的异步调用接口; 以事件方式驱动,chrome,memcached 都在使用该框架.libevent 同时也支持DNS,HTTP协议和RPC调用框架。libevent总是选择对应系统框架下最优的多路复用技术(epoll....)实现libevent框架。
*事件驱动(event-driven),高性能; 
*轻量级,专注于网络,不如 ACE 那么臃肿庞大;  
*跨平台,支持 Windows、Linux、*BSD和 Mac Os; 
*速度:libevent尝试使用每个平台上最高速的非阻塞IO实现,并且不引入太多的额外开销。
*支持多种 I/O多路复用技术, epoll、poll、dev/poll、select 和kqueue 等; 
*支持 I/O,定时器和信号等事件; 
*注册事件优先级;
*可以统一管理非阻塞文件、定时器和信号事件
2.libevent实现了Reactor模式

        反应堆模式最重要的思路就是:阻塞+事件+多路分发,事件驱动、同步非阻塞。目前,libevent支持/dev/poll,kqueue(2),select(2),poll(2)和epoll(4)等高并发网络编程模型。
       Reactor模式是编写高性能网络服务器的必备技术之一,它具有如下的优点: 
 1)响应快,不会被单个同步时间所阻塞,虽然 Reactor本身依然是同步的(对于激活事件的处理也需要一个一个的处理,没有使用多线程技术); 
 2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销; 
 3)可扩展性,可以方便的通过增加 Reactor实例个数来充分利用 CPU资源(可以创建多个互不干扰的反应器,用于管理不同的文件句柄); 
 4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;
 Reactor模型,必备的4个组件:事件源、Reactor框架、多路复用机制和事件处理程序。
多路复用对应到 libevent 中,依然是 select、poll、epoll 等,但是 libevent 使用结构体 eventop 进行了封装,以统一的接口来支持这些 I/O多路复用机制,达到了对外隐藏底层系统机制的目的。
3.libevent的方法函数不是线程安全的,因为他们大都操作了未作互斥(加锁)处理的队列或链表(信号等待队列、文件和定时器等待队列、就绪队列等)资源,所以对libevent使用多线程时,需要一些技巧。比如消息通知机制(队列+管道)
4.libevent部分接口函数
每一个使用libevent的程序,都需要包含<event.h>头文件,并且需要传递-levent标志给连接器linker。
(1)event_init()或者event_base_new()函数执行一次libevent库的初始化,构造一个libevent实例。
(2)事件通知:对于每一个你想监视的文件描述符,必须声明一个事件结构并且调用event_set()去初始化结构中的成员(比如设置该事件的回调函数)。为了激活通知,你需要通过调用event_add()将该结构添加到libevent实例监视事件列表。只要是该事件存活,那么就需要保持该已allocated的事件结构,因此该事件结构需要在堆(heap)上申请。最后,需要调用event_dispatch()函数循环和调度事件。
(3)I/O缓冲区:要实现windows下更高效的IOCP方式的API,IOCP在准备好读写事件时不会立即通知你的程序去拷贝数据,而是在数据完成从内核态拷贝到用户态时才通知应用程序. libevent 2提供了bufferevent 接口,支持这种编程范式 。具体如下:libevent提供了一个定期回调事件顶层的抽象。该抽象被称为缓冲事件(buffered event)。缓冲事件提供自动地填充和流掉(drained)的输入和输出缓冲区。缓冲时间的用户不再需要直接操作I/O,取而待之的是仅仅从输入缓冲区读,向输出缓冲区写就可以了。一旦通过bufferevent_new()进行了初始化,bufferevent结构就可以通过bufferevent_enable()和bufferevent_disable()重复地使用了。作为替代,对一个套接口的读写需要通过调用bufferevent_read()和bufferevent_write()函数来完成。当由于读事件而激活bufferevent时,那么后续将会自动回调读函数从该文件描述符读取数据。写函数将会被回调,无论何时这个输出缓冲区空间被耗尽到低于写的下水位(low watemark),通常该值默认为0。
(4)定时器:libevent通过创建一个定时器来参与到一个经过一定超时时间后的回调事件中。evtimer_set()函数将准备(分配)一个事件结构被用于作为一个定时器。为了激活定时器,需要调用evtimer_add()函数(文件描述符调用event_add函数)。相反,需要调用evtimer_del()函数。
(5)超时:除了简单的定时器,libevent可以为文件描述符指定一个超时事件,用于触发经过一段时间后而没有被激活的文件描述符执行相应的操作。timeout_set()函数可以为一个超时时间初始化一个事件结构。一旦被初始化成功,那么这个事件必须通过timeout_add()函数激活。为了取消一个超时事件,可以调用timeout_del()函数。
(6)异步DNS解析:libevent提供了一个异步DNS解析器,可用于代替标准的DNS解析器。这些函数可以通过在程序中包含<evdns.h>头文件而将其导入。在使用任何解析器函数之前,你必须调用evdns_init()函数初始化函数库。为转化一个域名到IP地址,可以调用evdns_resolve_ipv4()函数。为了执行一个反向查询,你可以调用evdns_resolve_reverse()函数。所有的这些函数,在查找时都会使用回调的方式而避免阻塞的发生。
(7)事件驱动的HTTP服务器:一个简单的HTTP客户端/服务器实现。libevent提供了一个简单的可以嵌入到你的程序中的并能处理HTTP请求的事件驱动HTTP服务器。为了使用这种能力,你应该在你的程序中包含<evhttp.h>头文件。你可以通过调用evhttp_new()函数来创建一个服务器。通过evhttp_bind_socket()函数添加用于监听的地址和端口。然后,你可以注册一个或多个对到来请求的处理句柄。对于每一个URI可以通过evhttp_set_cb()函数指定一个回调。通常,一个回调函数也可以通过evhttp_set_gencb()函数完成注册;如果没有其他的回调已经被注册得到该URI,那么这个回调将会与其关联。
(8)RPC服务器和客户端框架:libevent提供了一个创建RPC服务器和客户端的编程框架。它将托管所有的编组和解组的数据结构。
(9) API参考:要浏览完整的libevent的API文档,点击以下链接。
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/event_8h.html -> event.h libevent主要的头文件
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evdns_8h.html -> evdns.h 异步DNS解析器
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evhttp_8h.html -> evhttp.h 内置的基于libevent的HTTP服务器
    http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evrpc_8h.html -> evrpc.h 创建RPC服务器和客户端的编程框架
struct event_base *event_base_new(void);函数分配并且返回一个新的具有默认设置的event_base(libevent实例、事件管理器实例 )。函数会检测环境变量,返回一个到event_base的指针.如果发生错误,则返回NULL。
struct event_base *event_init(void); 老版本的产生事件管理器实例 
void event_set(struct event *ev, int fd, short evtype, void (*cb)(int fd, short evtype, void * arg), void *arg) 初始化一个事件(一个文件事件或定时器事件),指明该事件具体是什么事件。即设置事件管理的文件句柄、事件类型及回调函数。 
ev:执行要初始化的 event 对象; fd:该 event绑定的句柄,对于信号事件,它就是关注的信号;evtype:在该 fd 上关注的事件类型,它可以是 EV_READ, EV_WRITE, EV_SIGNAL; cb:这是一个函数指针; 由于定时事件不需要 fd,并且定时事件是根据添加时(event_add)的超时值设定的,因此这里 event 也不需要设置。事件管理器可以管理3重类型的事件:entype:(1)I/O事件: EV_WRITE和EV_READ fd=文件描述符(2)定时事件:EV_TIMEOUT fd=-1(3)信号:EV_SIGNAL fd=信号号(4)辅助选项:EV_PERSIST,表明是一个永久事件,该事件不会从等待队列中取走,永久注册,否则只监听一次。entype=EV_READ|EV_PERSIST
事件管理器可以认为有3个队列:信号等待队列,文件和定时器等待队列,就绪队列。
void evtimer_set(struct event *ev, void (*cb)(int, short, void *), NULL,void *arg);只能用来初始化一个定时器事件。
evtimer_set(&ev, timer_cb, NULL); <=>event_set(&ev, -1, 0, timer_cb, NULL); -1就表示这是一个定时器事件,而不是文件句柄。evtimer_set和event_set函数其实是对event结构体赋值的。
void event_base_set(struct event_base* base, struct event *ev);  指定该事件ev由哪个事件管理器来管理。
void event_add(struct event *ev, struct timeval *timeout); 将事件添加到ev的事件管理器的事件等待队列(注册),其中 timeout 是定时值,只有定时器事件才需要;
void event_base_dispatch(struct event_base *base); 事件管理器循环,开始监控等待队列中的事件,如果发生,就将该事件从等待队列中取走,放入就绪事件队列中,然后按优先级处理(如果要重新监控该事件,需要再次使用event_add将该事件添加到等待事件队列中)

5.事件管理器使用的基本步骤
(1)创建一个事件管理器;
(2)创建一个事件,并使用event_set等给事件赋值,指定事件类型回调函数;
(3)给事件指定一个事件管理器,如果不指定,默认为current指针指向的当前管理器;
(4)将事件添加到管理器事件等待队列;
(5)运行管理器事件循环。

6.实例
(1)定时器
#include <stdio.h>   
#include <event.h>     
void onTime(int sock,short event,void *arg)   
{   
    printf("Game over!\n");   
    struct timeval tv;   
    tv.tv_sec = 1;   
    tv.tv_usec = 0;   
    // 事件执行后,默认就被删除,需要重新add,使之重复执行   
    event_add((struct event*)arg,&tv);   
}   
int main()   
{   
    event_init();   // 初始化一个libevent实例(事件管理器),指针放在底层的current当前实例中。    
    struct event evTime; //定义一个定时器事件   
    evtimer_set(&evTime,onTime,&evTime); //设置定时器回调函数  
    struct timeval tv;  // 1s后执行   
    tv.tv_sec = 1;   
    tv.tv_usec = 0;            
    event_add(&evTime,&tv); // 添加事件  
    event_dispatch();  // 循环派发事件  
    return 0; 
}  
编译: gcc -o  time_test time_test.c -I/usr/local/libevent/include -L/usr/local/libevent/lib -levent

(2)TCP服务器
#include <stdio.h>   
#include <string.h>   
#include <sys/socket.h>   
#include <netinet/in.h>   
#include <arpa/inet.h>   
#include <netdb.h>   
#include <event.h>   
struct event_base *base;   
// 读事件   
void onRead(int clifd,short ievent,void *arg)   
{   
    int ilen;   
    char buf[1500];   
    ilen = recv(clifd,buf,1500,0);   
    if(ilen <= 0)   
    {   
        printf("Client close\n");      
        struct event *pread = (struct event*)arg;   
        event_del(pread);   
        delete pread;   
        close(clifd);   
        return;   
    }      
    buf[ilen] = '\0';   
    printf("Accpet: %s\n",buf);   
}   
// 连接事件   
void onAccept(int svrfd,short ievent,void *arg)   
{   
    int clifd;   
    struct sockaddr_in cliaddr;   
  
    socklen_t sinsize = sizeof(cliaddr);   
    clifd = accept(svrfd,(struct sockaddr*)&cliaddr,&sinsize);   
  
    struct event *pread = new event;   
    event_set(pread,clifd,EV_READ|EV_PERSIST,onRead,pread);  // 注册读(写)事件   
    event_base_set(base,pread);   
    event_add(pread,NULL);   
}    
int main()   
{   
    int svrfd;   
    struct sockaddr_in svraddr;   
  
    memset(&svrfd,0,sizeof(svraddr));   
    svraddr.sin_family = AF_INET;   
    svraddr.sin_port = htons(1234);   
    svraddr.sin_addr.s_addr = inet_addr("127.0.0.1");   
    svrfd = socket(AF_INET,SOCK_STREAM,0);   
    bind(svrfd,(struct sockaddr*)&svraddr,sizeof(svraddr));   
    listen(svrfd,10);   
    // 初始化事件库   
    base = event_base_new();   
    // 初始化一个连接事件,EV_PRESIST指定重复执行该事件   
    struct event evlisten;   
    event_set(&evlisten,svrfd,EV_READ|EV_PERSIST,onAccept,NULL);    
    // 设置为base事件   
    event_base_set(base,&evlisten);   
    // 添加事件   
    event_add(&evlisten,NULL);   
    // 事件循环   
    event_base_dispatch(base);   
    return 0;   
}  
要实现windows下更高效的IOCP方式的API,IOCP在准备好读写事件时不会通知你的程序去拷贝数据,而是在数据完成从内核态拷贝到用户态时才通知应用程序. libevent 2提供了bufferevent 接口,支持这种编程范式 。


(3)HTTP服务器
#include <stdio.h>   
#include <stdlib.h>   
#include <unistd.h>   
#include <event.h>   
#include <evhttp.h>    
void reqHandler(struct evhttp_request *req,void *arg)   
{   
    struct evbuffer *buf = evbuffer_new();        
    evbuffer_add_printf(buf, "Thanks for the request"); // 发送响应    
    evhttp_send_reply(req,HTTP_OK,"Client",buf);         
    evbuffer_free(buf);          
    return;   
}    
int main(int argc,char **argv)   
{   
    short port = 8000;   
    const char *addr = "192.168.1.11";   
    struct evhttp *httpserv = NULL;   
    event_init();           
    httpserv = evhttp_start(addr,port);  // 启动http服务   
    evhttp_set_gencb(httpserv, reqHandler,NULL);  // 设置回调   
    printf("Server started on port %d\n",port);   
    event_dispatch();   
    return 0;   



本文转自 a_liujin 51CTO博客,原文链接:http://blog.51cto.com/a1liujin/1707982,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
把libevent 2.1.8源码的最小堆提取出来,自己封装成定时器使用(2)(★firecat推荐★)
把libevent 2.1.8源码的最小堆提取出来,自己封装成定时器使用(2)(★firecat推荐★)
64 0
把libevent 2.1.8源码的最小堆提取出来,自己封装成定时器使用(1)(★firecat推荐★)
把libevent 2.1.8源码的最小堆提取出来,自己封装成定时器使用(1)(★firecat推荐★)
57 0
把libevent 2.1.8源码的最小堆提取出来,自己封装成定时器使用(5)(★firecat推荐★)
把libevent 2.1.8源码的最小堆提取出来,自己封装成定时器使用(5)(★firecat推荐★)
43 0
ls -al /usr/lib | grep libevent --- 查看libevent是否已安装
ls -al /usr/lib | grep libevent --- 查看libevent是否已安装
66 0
Windows下libevent C++封装类实现
题记 windows平台下对于服务器高并发的网络模型选型中,使用libevent是个不错的选择。 本文的背景基于:国内博客对于libevent大多介绍linux实现,大多是c语言的实现,Windows平台下C++相对较少或者较旧。 本文针对libevent从服务端、客户端两个角度实现了封装类,真正做到,我们在使用封装类时候,只需要关注业务逻辑的处理,不需要关注socket的通信的细节。
140 0
Windows下libevent C++封装类实现
本文针对libevent从服务端、客户端两个角度实现了封装类,真正做到,我们在使用封装类时候,只需要关注业务逻辑的处理,不需要关注socket的通信的细节。
656 0
13688
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载