libevent入门(转)

花了两天的时间在libevent上,想总结下,就以写简单tutorial的方式吧,貌似没有一篇简单的说明,让人马上就能上手用的。

首先给出官方文档吧: http://libevent.org ,首页有个Programming with Libevent,里面是一节一节的介绍libevent,但是感觉信息量太大了,而且还是英文的-。-(当然,如果想好好用libevent,看看还是很有必要的),还有个Reference,大致就是对各个版本的libevent使用doxgen生成的文档,用来查函数原型和基本用法什么的。

下面假定已经学习过基本的socket编程(socket,bind,listen,accept,connect,recv,send,close),并且对异步/callback有基本认识。基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求(所谓c10k problem?),例如内存:默认情况下每个线程需要占用2~8M的栈空间。posix定义了可以使用异步的select系统调用,但是因为其采用了轮询的方式来判断某个fd是否变成active,效率不高[O(n)],连接数一多,也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。

libevent大概是这样的:

默认情况下是单线程的(可以配置成多线程,如果有需要的话),每个线程有且只有一个event_base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。

//创建一个event_base

struct event_base *base = event_base_new();  

assert(base != NULL);

event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有一个/一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应一个struct event,可以是监听一个fd或者POSIX信号量之类(这里只讲fd了,其他的看manual吧)。

struct event使用event_new来创建和绑定,使用event_add来启用:

//创建并绑定一个event

struct event *listen_event;  

//参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数

listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);  

//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置)

event_add(listen_event, NULL);

注:libevent支持的事件及属性包括(使用bitfield实现,所以要用 | 来让它们合体)    

(a) EV_TIMEOUT: 超时    

(b) EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发    

(c) EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发    

(d) EV_SIGNAL: POSIX信号量,参考manual吧    

(e) EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除    

(f) EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET

然后需要启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。

//启动事件循环

event_base_dispatch(base);

接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。

其原型是:

typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)

对于一个服务器而言,上面的流程大概是这样组合的:    

1. listener = socket(),bind(),listen(),

//设置nonblocking(POSIX系统中可使用fcntl设置,windows不需要设置,实际上libevent提供了统一的包装evutil_make_socket_nonblocking)    

2. 创建一个event_base    

3. 创建一个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)。

对于listener socket来说,只需要监听EV_READ|EV_PERSIST    

4. 启用该事件    

5. 进入事件循环     ---------------    

6. (异步) 当有client发起请求的时候,调用该回调函数,进行处理。

问题:为什么不在listen完马上调用accept,获得客户端连接以后再丢给event_base呢?这个问题先想想噢。

回调函数要做什么事情呢?当然是处理client的请求了。首先要accept,获得一个可以与client通信的sockfd,然后……调用recv/send吗?错!大错特错!如果直接调用recv/send的话,这个线程就阻塞在这个地方了,如果这个客户端非常的阴险(比如一直不发消息,或者网络不好,老是丢包),libevent就只能等它,没法处理其他的请求了——所以应该创建一个新的event来托管这个sockfd。

在老版本libevent上的实现,比较罗嗦[如果不想详细了解的话,看下一部分]。    

对于服务器希望先从client获取数据的情况,大致流程是这样的:    

1. 将这个sockfd设置为nonblocking    

2. 创建2个event:        

event_read,绑上sockfd的EV_READ|EV_PERSIST,设置回调函数和参数(后面提到的struct)        

event_write,绑上sockfd的EV_WRITE|EV_PERSIST,设置回调函数和参数(后面提到的struct)    

3. 启用event_read事件     ------    

4. (异步) 等待event_read事件的发生, 调用相应的回调函数。这里麻烦来了:回调函数用recv读入的数据,不能直接用send丢给sockfd了事——因为sockfd是nonblocking的,丢给它的话,不能保证正确(为什么呢?)。所以需要一个自己管理的缓存用来保存读入的数据中(在accept以后就创建一个struct,作为第2步回调函数的arg传进来),在合适的时间(比如遇到换行符)启用event_write事件【event_add(event_write, NULL)】,等待EV_WRITE事件的触发     ------    

5. (异步) 当event_write事件的回调函数被调用的时候,往sockfd写入数据,然后删除event_write事件【event_del(event_write)】,等待event_read事件的下一次执行。     以上步骤比较晦涩,具体代码可参考官方文档里面的【Example: A low-level ROT13 server with Libevent】

由于需要自己管理缓冲区,且过程晦涩难懂,并且不兼容于Windows的IOCP,所以libevent2开始,提供了bufferevent这个神器,用来提供更加优雅、易用的API。struct bufferevent内建了两个event(read/write)和对应的缓冲区【struct evbuffer *input,*output】,并提供相应的函数用来操作缓冲区(或者直接操作bufferevent)。每当有数据被读入input的时候,read_cb函数被调用;每当output被输出完的时候,write_cb被调用;在网络IO操作出现错误的情况(连接中断、超时、其他错误),error_cb被调用。

于是上一部分的步骤被简化为:    

1. 设置sockfd为nonblocking    

2. 使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base    

3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数    

4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件     ------    

5. (异步)        

在read_cb里面从input读取数据,处理完毕后塞到output里(会被自动写入到sockfd)        

在write_cb里面(需要做什么吗?对于一个echo server来说,read_cb就足够了)        

在error_cb里面处理遇到的错误    

*. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时, 在error_cb里面处理超时。    

*. read_cb和write_cb的原型是        

void read_or_write_callback(struct bufferevent *bev, void *arg)      

error_cb的原型是        

void error_cb(struct bufferevent *bev, short error, void *arg) //这个是event的标准回调函数原型      

可以从bev中用libevent的API提取出event_base、sockfd、input/output等相关数据,详情RTFM~    

于是代码简化到只需要几行的read_cb和error_cb函数即可:

void read_cb(struct bufferevent *bev, void *arg) {     

char line[256];     

int n;     

evutil_socket_t fd = bufferevent_getfd(bev);     

while (n = bufferevent_read(bev, line, 256), n > 0)         

bufferevent_write(bev, line, n);  

}

void error_cb(struct bufferevent *bev, short event, void *arg) {     

bufferevent_free(bev);  

}

于是一个支持大并发量的echo server就成型了!下面附上无注释的echo server源码,110行,多抄几遍,就能完全弄懂啦!

更复杂的例子参见官方文档里面的【Example: A simpler ROT13 server with Libevent】

#include <stdio.h>  

#include <stdlib.h>  

#include <errno.h>  

#include <assert.h>

#include <event2/event.h>  

#include <event2/bufferevent.h>

#define LISTEN_PORT 9999  

#define LISTEN_BACKLOG 32

void do_accept(evutil_socket_t listener, short event, void *arg);

void read_cb(struct bufferevent *bev, void *arg);

void error_cb(struct bufferevent *bev, short event, void *arg);

void write_cb(struct bufferevent *bev, void *arg);

int main(int argc, char *argv[])  {     

int ret;     

evutil_socket_t listener;     

listener = socket(AF_INET, SOCK_STREAM, 0);     

assert(listener > 0);     

evutil_make_listen_socket_reuseable(listener);

struct sockaddr_in sin;     

sin.sin_family = AF_INET;     

sin.sin_addr.s_addr = 0;     

sin.sin_port = htons(LISTEN_PORT);

 

if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {         

perror("bind");         

return 1;     

}

 

if (listen(listener, LISTEN_BACKLOG) < 0) {         

perror("listen");         

return 1;     

}

    

printf ("Listening...\n");

    

evutil_make_socket_nonblocking(listener);

    

struct event_base *base = event_base_new();     

assert(base != NULL);     

struct event *listen_event;     

listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);     

event_add(listen_event, NULL);     

event_base_dispatch(base);

printf("The End.");     

return 0;  

}

 

void do_accept(evutil_socket_t listener, short event, void *arg)  {     

struct event_base *base = (struct event_base *)arg;     

evutil_socket_t fd;     

struct sockaddr_in sin;     

socklen_t slen;     

fd = accept(listener, (struct sockaddr *)&sin, &slen);     

if (fd < 0) {         

perror("accept");         

return;     

}     

if (fd > FD_SETSIZE) {         

perror("fd > FD_SETSIZE\n");         

return;     

}

    

printf("ACCEPT: fd = %u\n", fd);

    

struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);     

bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);     

bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);  

}

 

void read_cb(struct bufferevent *bev, void *arg)  {  

#define MAX_LINE    256     

char line[MAX_LINE+1];     

int n;     

evutil_socket_t fd = bufferevent_getfd(bev);

  

while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {         

line[n] = '\0';         

printf("fd=%u, read line: %s\n", fd, line);

        

bufferevent_write(bev, line, n);     

}  

}

 

void write_cb(struct bufferevent *bev, void *arg) {}

void error_cb(struct bufferevent *bev, short event, void *arg)  {     

evutil_socket_t fd = bufferevent_getfd(bev);     

printf("fd = %u, ", fd);     

if (event & BEV_EVENT_TIMEOUT) {         

printf("Timed out\n"); //if bufferevent_set_timeouts() called     

}     

else if (event & BEV_EVENT_EOF) {         

printf("connection closed\n");     

}     

else if (event & BEV_EVENT_ERROR) {         

printf("some other error\n");     

}     

bufferevent_free(bev);  

}

转载于:https://www.cnblogs.com/gaoyang853/p/4103567.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HTTPLook 是一个 HTTP 的嗅探器,它能捕捉本机与其它任何主机的 HTTP 通讯(不是 HTTPS 哦 ),然后显示详细的 HTTP 操作(如 GET/POST)、访问资源的 URL 、字节数大小等,这个软件简单易用,不用对 Internet Explorer 做任何其它设置(有的软件通过在 IE 中设置代理来监控数据),也不需要其它任何软件的支持,是一款较为绿色的、轻量级的软件。 HTTPLook 的应用场景: 1、程序开发及调试 在 CGI、ASP/PHP/JSP、ASP.NET、Web Service 的开发中,经常要查看 GET 或 POST 的数据是否正确,用这个工具能很好地协助完成此工作。 2、复杂页面分析 上网有时会碰到的很复杂的页面,查看源码也不能了解它的工作原理,这一般是作者为了保护 Web 在页面而加上了一些保护机制(如使用 Frame/IFrame、捕捉键盘或 Mouse 事件、使用 Script 来访问资源等),使用 HTTPLook 有助于对此页面进行分析,进而破解其保护机制。 比较典型的一个例子就是 SharePoint Team Services 中使用了 WebBot ,查看源码根本不知道它调用了那些 ASP/Script/CSS 文件,但使用 HTTPLook 之后,一目了然,非常有效,可以据此来自定义原有页面风格,如色彩,字体等。 3、获得被保护的 Web 资源 在很多网站上,尤其是 Microsoft 的网站上,经常见到一些制做精美的 Flash ,但是由于 Flash 不是一个单一文件,而是在最先启动的 FLASH 中再调用其它 Flash 资源文件,由于无法获得这些文件的 URL ,所以不能下载到本地,但如果使用 HTTPLook ,通过对整个播放过程的监视,就可以完全侦测出所有在程序中访问的资源的地址,进而保存到本地,可以离线浏览。当然也可以保存其它资源,如图片等。 4、学习 HTTP 协议 可以详细地了解 HTTP 通讯的细节,如 GET/POST、User-Agent、Cookie、Proxy 设置及验证、HTTP 协议出错代码及意义等。
对于学习libevent,你可以按照以下步骤进行: 1. 了解libeventlibevent是一个开源的事件通知库,它提供了事件驱动的网络编程接口,可以用于开发高性能的网络服务器和客户端应用。它支持多种I/O模型(包括基于事件的和多线程的),并提供了跨平台的兼容性。 2. 安装libevent:你可以从libevent的官方网站(https://libevent.org/)上下载最新版本的libevent,并按照官方文档中的指南进行安装。根据你使用的操作系统不同,安装步骤可能会有所不同。 3. 学习libevent的基本概念:了解libevent中的一些核心概念,如事件循环(event loop)、事件处理器(event handler)、事件回调函数(event callback)等。理解这些概念对于正确使用libevent非常重要。 4. 掌握libevent的使用方法:学习如何使用libevent来编写网络应用程序。这包括创建事件循环、注册事件、定义事件回调函数等。libevent提供了丰富的API,你可以根据自己的需求选择合适的接口进行开发。 5. 深入研究libevent的高级特性:学习libevent的更高级功能,如定时器、信号处理、缓冲区管理等。这些功能可以帮助你更好地控制和优化你的网络应用。 6. 查阅文档和示例代码:libevent的官方网站提供了详细的文档和示例代码,你可以利用这些资源来加深对libevent的理解。此外,还可以参考一些开源项目中使用libevent的实际案例,以便更好地应用于自己的项目中。 记住,学习任何新的库或工具都需要有耐心和实践。不断尝试和练习,结合实际项目,才能更好地掌握和应用libevent。祝你学习顺利!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值