什么是libevent
livevent是一个轻量级的开源的高性能的事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。著名分布式缓存软件memcached也是基于libevent,而且libevent在使用上可以做到跨平台,而且根据libevent官方网站上公布的数据统计,似乎也有着非凡的性能。它的特点是:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。libevent支持用户使用三种类型的事件,分别是网络IO、定时器、信号三种,最新版本在定时器的实现上使用了最小堆的数据结构,以达到高效查找、排序、删除定时器的目的,IO和信号的实现均使用了双向队列(用链表实现)。libevent包括事件管理、缓存管理、DNS、HTTP、缓存事件几大部分。事件管理包括各种IO(socket)、定时器、信号等事件;缓存管理是指evbuffer功能;DNS是libevent提供的一个异步DNS查询功能;HTTP是libevent的一个轻量级http实现,包括服务器和客户端。libevent也支持ssl,这对于有安全需求的网络程序非常的重要,但是其支持不是很完善,比如http server的实现就不支持ssl。
学习libevent语言,首先要下载安装它的库。我刚开始的时候并不知道要安装库,就从网上找了一篇博客,跟着博客学习了一下编写服务器端大概的流程,编译的时候发现头文件就出错了,我试着加了-levent发现还是不行。然后就百度了一下,发现果然要下载库,我就搜索了怎么安装库,我尝试了很多方法,发现都没有成功,最后发现了一篇博客,比较简单而且还成功了。主要步骤如下:
1、在虚拟机下输入命令下载libevent-1.2.tar.gz库: Ubuntu-14:~$ wget http://www.monkey.org/~provos/libevent-1.2.tar.gz下载目录应该是可以自己选择;
2、下载之后解压:Ubuntu-14:~$ tar zxvf libevent-1.2.tar.gz;
3、解压之后打开libevent-1.2文件夹:Ubuntu-14:~$ cd libevent-1.2/; 4、打开文件后执行:'Ubuntu-14:~$./configure –prefix=/home/mjf/lib'-prefix是自己配置的目录,默认可以不用加;
5、然后执行:Ubuntu-14:~$make Ubuntu-14:~$sudo make install就可以了,成功没成功自己可以复制一个简单的代码编译运行一下,有一个命令可以查看,但是我的没有显示,你们可以试一下:whereis libevent-2.0.so.5 ls -al /usr/lib | grep libevent
实现简单的网络服务器端程序
libevent默认情况下是单线程的,可以配置成多线程,每个线程有且只有一个event_base,对应一个struct event_base结构体以及附于其上的事件管理器,用来调度托管给它的一系列event。所以首先要创建一个event_base,内部有一个循环,循环阻塞在epoll等系统调用上,直到有一个/一些时间发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上,每个事件对应一个struct event,可以是监听一个fd或者信号量之类的,struct event使用event_new来创建和绑定,使用event_add来将event绑定到event_base中。然后启动event_base的循环,开始处理事件。循环地启动使用event_base_dispatch,循环将一直持续,找到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。接下来再来关注事件发生时的回调函数,传给callback_func的是一个监听的fd,监听的事件类型,以及event_new中最后一个参数。所以服务器程序简单的总结为:
1、创建一个event_base;
2、创建一个event,将该socket托管给event_base;
3、将事件加入到event_base中;
4、启用事件循环。
程序中还会用到socket()、bind()、listen()、accept(),这几个函数应该很熟悉了,就不说了,还会用到bufferevent_getfd()、bufferevent_readi()、bufferevent_write()、bufferevent_free(),说到这几个函数,就得说一下bufferevent。
**bufferevent的简介:**一般通过libevent进行网络编程,都是将一个socket的fd与一个event进行绑定,并自行维护一个buffer用于存储从socket上接收的数据,同时可能也用于待发送数据的缓存。然后通过可读可写事件从socket上收取数据写入缓存并进行相应处理,或者将缓存中的数据通过socket发送。
libevent为这种带缓存的IO模式提供了一种通用的机制,那就是bufferevent。一个bufferevent包含了一个底层传输的fd(通常为socket),一个输入buffer和一个输出buffer,并且bufferevent已经帮我们完成了从socket上接收数据写入输入buffer,同时从输出buffer中取出数据通过socket发送,当输入输出缓存中的数据达到一定量时调用我们设置的回调函数。这样使得我们可以更加关注数据的处理。详细用法可以参考此篇博客:(https://blog.csdn.net/u010710458/article/details/80067885)。
下面是我的代码,我觉得代码还是有问题,连接上之后断开连接,再次连接就会出现连接失败,原因是无效参数,我觉得是要在创建事件之前初始化,但是会报错:
{
printf("The progname:%s\n",progname);
printf("-p(--port):specify listen port!\n");
printf("-h(--help):print help information!\n");
return ;
}
void do_accept(evutil_socket_t sockfd, short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t clifd;
struct sockaddr_in servaddr;
socklen_t len;
clifd=accept(sockfd,(struct sockaddr *)&servaddr,&len);
if( clifd<0 )
{
perror("accept");
return ;
}
if( clifd>FD_SETSIZE )
{
perror("fd > FD_SETSIZE\n");
return ;
}
printf("ACCEPT: clifd = %u\n", clifd);
/*使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base*/
struct bufferevent *bev=bufferevent_socket_new(base,clifd,BEV_OPT_CLOSE_ON_FREE);
/*使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数*/
bufferevent_setcb(bev,read_cb,NULL,error_cb,arg);
/*使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件*/
bufferevent_enable(bev,EV_READ|EV_WRITE|EV_PERSIST);
}
void read_cb(struct bufferevent *bev, void *arg)
{
char msg[MAXSIZE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while( n=bufferevent_read(bev,msg,MAXSIZE),n>0 )
{
msg[n]='\0';
printf("fd = %u\n",fd);
bufferevent_write(bev,msg,n);
printf("msg is:%s\n",msg);
}
}
/*服务器端write什么都不用做*/
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);
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);
}
int main(int argc,char **argv)
{
int ch;
int port=0;
evutil_socket_t sockfd;
struct sockaddr_in cliaddr;
struct sockaddr_in servaddr;
socklen_t len;
struct event *listen_event;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if( sockfd<0 )
{
printf("create socket failure:%s\n",strerror(errno));
return -1;
}
printf("create socket[%d] successfully\n",sockfd);
/*setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));*/
evutil_make_listen_socket_reuseable(sockfd);/*实现端口重用,返回值为0成功,为-1代表失败*/
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(port);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
if( (bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0 )
{
printf("bind on listen port failure:%s\n",strerror(errno));
return -2;
}
printf("bind on listen port[%d] successfully!\n",port);
listen(sockfd,64);
struct event_base* base = event_base_new();/*创建一个event_base*/
assert(base != NULL);
listen_event = event_new(base, sockfd, EV_READ|EV_PERSIST, do_accept, (void*)base);/*创建一个event,将该socket托管给event_base*/
/*参数:event_base,监听的对象,需要监听的事件,事件发生后的回调函数,传给回调函数的参数*/
/*将事件加入到event_base中*/
event_add(listen_event, NULL);
/*启用事件循环*/
event_base_dispatch(base);
printf("The end!\n");
return 0;
}