step0: 概述 关于libevent, 先摘来网上某哥们一段描述: libevent是一个跨平台的事件驱动库,他目前支持Linux, *BSD, Mac OS X, Solaris 和 Windows。如果你将要开发的应用程序需要支持以上所列出的平台中的两个以上,那么强烈建议你采用这个库,即使你的应用程序只需要支持一个平台,选择libevent也是有好处的,因为它可以根据编译/运行环境切换底层的事件驱动机制,这既能充分发挥系统的性能,又增加了软件的可移植性。
它封装并且隔离了事件驱动的底层机制,除了一般的文件描述符读写操作外,它还提供有读写超时、定时器和信号回调,另外,它还允许为事件设定不同的优先级,当前版本的libevent还提供dns和http协议的异步封装,这一切都让这个库尤其适合于事件驱动应用程序的开发。 step1: 使用方法介绍 OK,理论到位了,接下来简单介绍下libevent中几个核心函数: event_set(&c->event, sfd, event_flags, event_handler, (void *)c) 把sfd这个文件描述符放入c->event,并且告知当事件event_flags发生时回调event_handler,并以c为回调参数。
event_add(&ev, NULL) 把ev注册到事件队列里面,第二个参数指定的是超时值,设定成NULL表示忽略这项设定。
event_dispatch() 表示进入事件循环,当队列里任何一个文件描述符发生事件的时候就会执行回调函数。 step2: 演示例子 下面举个小例子,就timer server吧,比较SB易懂咯,赶紧贴上源码:
#include < netinet / in .h > #include < sys / socket.h > #include < sys / types.h > #include < stdio.h > #include < time.h > #include < event .h > void connection_time( int fd, short event , struct event * arg) ... { char buf[ 32 ]; struct tm t; time_t now; time( & now); localtime_r( & now, & t); asctime_r( & t, buf); write(fd, buf, strlen(buf)); shutdown(fd, SHUT_RDWR); free(arg); } void connection_accept( int fd, short event , void * arg) ... { /**/ /* for debugging */ fprintf(stderr, " %s(): fd = %d, event = %d. " , __func__, fd, event ); /**/ /* 接受新的连接. */ struct sockaddr_in s_in; socklen_t len = sizeof (s_in); int ns = accept(fd, ( struct sockaddr * ) & s_in, & len); if (ns < 0 ) ... { perror( " accept " ); return ; } struct event * ev = malloc( sizeof ( struct event )); event_set(ev, ns, EV_WRITE, ( void * ) connection_time, ev); event_add(ev, NULL); } int main( void ) ... { /**/ /* 创建socket. */ int s = socket(PF_INET, SOCK_STREAM, 0 ); if (s < 0 ) ... { perror( " socket " ); exit( 1 ); } /**/ /* 绑定bind() */ struct sockaddr_in s_in; bzero( & s_in, sizeof (s_in)); s_in.sin_family = AF_INET; s_in.sin_port = htons( 7000 ); s_in.sin_addr.s_addr = INADDR_ANY; if (bind(s, ( struct sockaddr * ) & s_in, sizeof (s_in)) < 0 ) ... { perror( " bind " ); exit( 1 ); } /**/ /* 监听listen() */ if (listen(s, 5 ) < 0 ) ... { perror( " listen " ); exit( 1 ); } /**/ /* 初始化libevent. */ event_init(); /**/ /* 创建事件 */ struct event ev; event_set( & ev, s, EV_READ | EV_PERSIST, connection_accept, & ev); /**/ /* 添加事件 */ event_add(& ev, NULL); event_dispatch(); return 0 ; 怕有新手不懂,补充两句了:
其实我一直认为,任何单一的编程模型本身,必能找出一个贴切的隐喻或现实参照物,只要洞悉本质,一切皆是那么容易(当然,我这里并不否认复合模型的复杂性)。
libevent是基于事件的,看它名字都知道,我大致说说上面小例子的章法:
拿个龌龊例子来隐喻下,假设有个买春店,这个店好比server socket,各种服务(如按摩,做爱)代表事件(连接,可读),小姐们代表一个个对等客户连接,客房代表空闲线程,嫖客好比过来的客户端连接,嫖客有欲望了,找上买春店,绑定到具体的对等连接(小姐),激发了连接事件并执行对应的回调(比如按摩,做爱),此回调被安排在某个空闲的客房(比如线程池中某个空闲连接)里进行。如果嫖客与小妹是初次见面,那么留下联系方式,日后嫖客有需求,来店后可直接去找老相好(这就是为何在connection_accept回调函数中还要注册一次,因为第一次事件注册是没有目标性的,也即来个嫖客就随便找个小妹,而在回调中对连接事件的注册已经是有目标性的注册了,下次嫖客再来,就找上次的老相好,这样子做是有必要性的,对于嫖客,妹子而言可能旧情未了,需要重温, 而对于网络通信,可能前面的通信并没有把数据传递完,所以需要把对等端连接关联起来,记录上次数据处理的状态信息,以备后续处理)。