高性能服务器(libevent网络库)

来源:互联网 发布:Mac电脑打魔兽世界卡吗 编辑:程序博客网 时间:2024/05/01 00:49
简介
libevent是一个事件出发的网络库,使用与 windows,linux,bad, mac os 等的高性能跨平台网络库 ,

支持的多种I/O 网络模型

linux(epoll),poll dev/poll select freebsd(kqueue ),windows(iocp)


libevent是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,
内部使用select、epoll、kqueue等系统调用管理事件机制。
著名分布式缓存软件memcached也是libevent based,
而且libevent在使用上可以做到跨平台,而且根据libevent官方网站上公布的数据统计,似乎也有着非凡的性能。

编译库代码,编译脚本会判断OS支持哪种类型的事件机制(select、epoll或kqueue),
然后条件编译相应代码,供上层使用的接口仍然是保持统一的(否则也不能所谓的跨平台了)。
在linux redhat as 4 u 2 上编译相当容易,configure以后make,make install就可以了,
windows上编译似乎有点小麻烦,不过稍微改点东西也就通过了。
从代码中看,libevent支持用户使用三种类型的事件,分别是网络IO、定时器、信号三种,
在定时器的实现上使用了RB tree的数据结构,以达到高效查找、排序、删除定时器的目的,
网络IO上,主要关注了一下linux上的epoll(因为目前的开发主要在linux平台),
结果发现libevent的epoll居然用的EPOLLLT,水平触发的方式用起来比较方便,不容易出错,
但是在效率上可能比EPOLLET要低一些。
跟网络无关的,libevent也有一些缓冲区管理的函数,而且是c风格的函数,实用性不是太大。
libevent没有提供缓存的函数。
虽然libevent实用上的价值不大,但它提供的接口形式还是不错的,实现类似的lib的时候仍然是可以参考的。
Libevent定时器的数据结构自version 1.4起已由红黑树改为最小堆(Min Heap),以提高效率;
网络IO和信号的数据结构采用了双向队列(TAILQ)。在实现上主要有3种链表:
EVLIST_INSERTED, EVLIST_ACTIVE, EVLIST_TIMEOUT,一个ev在这3种链表之间被插入或删除,
处于EVLIST_ACTIVE链表中的ev最后将会被调度执行。
Libevent提供了DNS,HTTP Server,RPC等组件,HTTP Server可以说是Libevent的经典应用。
从http.c可看到Libevent的很多标准写法。
写非阻塞式的HTTP Server很容易将socket处理与HTTP协议处理纠缠在一起,Libevent在这点上似乎也有值得推敲的地方。

--------------------------------------------------------------------------------------------------------------------------


Libevent安装过程
Version:libevent-2.0.16
#http://cloud.github.com/downloads/libevent/libevent/libevent-2.0.16-stable.tar.gz
#tar -xzvf libevent-2.0.16.tar.gz
#cd libevent-2.0.16
#./configure -prefix=/usr/local
#make
#sudo make install

源代码文件组织结构
头文件:
1.libevent共用的头文件都在event2目录里 2.正常头文件后面没有特效后缀
3.比如后缀“xx_struct.h”这种类型的文件里的任何结构体要是直接以来的话会破坏程序对其
他版本libevent的二进制前荣幸,有时候是以非常难以调试的方式出现
内部头文件:
1.xxx-internal.h 后最的文件内部使用的头文件 2.目的是内部数据结构和函数,信息隐藏
libevent框架
1.event.c里有对event的整体框架实现
对系统I/O多路复用机制的封装
1.epool.c : 对epoll的封装
2.select.c : 对select的封装
3.devpoll.c : 对dev/poll的封装
4.kqueue.c : 对kqueue的封装
定时事件管理
1.min-heap.h 定时器事件管理 堆结构
信号事件管理
signal.c : 对新号事件的处理
补助功能函数
evutil.h 和 evutil.c util.h : 一些补助功能函数,
包括创建socket pair 和一些时间操作函数 加,减,等缓冲区管理
evbuffer.h bufferevent.h 对缓冲区的封装
lievent 里用到的基本数据结构
compat\sys 下的queue.h 文件对 链表,双向链表,

Libevent 库的结构
Libevent_core
包含所有核心的事件和缓冲功能
event_base,evbuffer,bufferevent,和几个附加补助功能
Libevent_extra
定义了程序可能需要,也可能不需要的协议特定功能,包括HTTP、DNS和RPC
Libevent
这个库没用以后版本会删掉
Libevent_pthreads
pthread可一直线程库的线程和锁定实现
Libevent_openssl这个库为使用bufferevent和OpenSSL进行加密的通信提供支持。
注意bufferevent 目前只支持 tcp 不支持udp



Libevent主要功能主键
Evutil : 网络补助工具
Event,eventbase : Libevent核心事件和事件管理
Bufferevent :为libevent基于事件的核心提供使用方便的封装除了通知程序套接字(注意 : 目前只针对于tcp udp不支持)
Evbuffer : 在bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数。

Libevent普通简单例子实现Source code

int main( )
{
int listen_fd;
struct event ev_accept;
//--相当于创建一个事件堆 以后事件可以往这里注册了--
base = event_base_new();
listen_fd = socket( AF_INET,SOCK_STREAM,0 );
if( listen_fd <0 )
return;
int reuseaddr_on = 1;
if( setsockopt( listen_fd,SOL_SOCKET,SO_REUSEADDR,&reuseaddr_on,sizeof(reuseaddr_on) ) == -1 )
{
cout<<"Error : Setsockopt failed" <<endl;
}
//--SetSocket_listenaddr--
struct sockaddr_in listen_addr;
memset( &listen_addr,0,sizeof( listen_addr ) );
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = INADDR_ANY;
listen_addr.sin_port = htons( 80800 );
if( bind( listen_fd,(struct sockaddr*)&listen_addr,sizeof( listen_addr ) ) < 0)
{
cout<<"Error : Bind failed"<<endl;
}
if( listen( listen_fd,500 ) < 0 )
{
cout<<"Error : Listen failed"<<endl; return ;
}
if( SetNonBlock(listen_fd) < 0 )
{
cout<<"Error : SetNonBlock failed"<<endl; return;
}
//--初始化事件ev_accept 设置accept回调函数和 和事件类型--
event_set( &ev_accept,listen_fd,EV_READ|EV_PERSIST,on_accept,NULL );
//--设置完的ev_accept事件注册到 base里--
event_base_set( base,&ev_accept );
//--正事添加事件 相当于注册完的事件激活--
event_add( &ev_accept,NULL );
//--事件堆run部分--
event_base_dispatch(base);
return 1;
}



accpet callback codeSource code

void on_accept(int fd,short ev,void* arg)
{
int client_fd;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof( client_addr );
client_fd = accept( fd,(struct sockaddr*)&client_addr,&client_len );
if( client_fd == -1 )
{
cout<<"Error : accept client failed"<<endl;
}
if (SetNonBlock(client_fd) < 0)
{
cout<<"Error : Set client socket nonblock"<<endl;
}
static int index = 0;
cout << "客户端 "<< index <<"-----" << inet_ntoa( client_addr.sin_addr ) <<" 已链接 ~~~~~" <<endl;
index++;
//--accept到的 新客户端 注册一个recv 事件--
stMyClient* pClient = new stMyClient();
//--使用刚连接客户端的文件描述符 监视 recv 消息--
event_set( &pClient->ev_read,client_fd,EV_READ|EV_PERSIST,on_read,pClient );
event_base_set( base,&pClient->ev_read );
event_add( &pClient->ev_read,NULL );
}

recv callback codeSource code

void on_read( int fd,short ev,void* arg )
{
struct stMyClient* client = (stMyClient*)arg;
char buff[65535];
memset( buff,0x00,sizeof( buff ) );
int nSize = read( fd,buff,65535 );
if( nSize == 0 )
{
cout<<"Client disconnected "<<endl;
close(fd);
event_del( &client->ev_read );
delete client;
return;
}
else if( nSize < 0 )
{
cout<<"Socket failed disconnected "<<endl;
close(fd);
event_del( &client->ev_read );
delete client;
return;
}
cout<<"Read :"<<buff<<endl;
}

关于使用Bufferevent 和 多线程用法Source code

//--线程回调--
void* ProcessThread( void* pthread )
{
CVitNetThread* p = (CVitNetThread*)pthread;
//--p->GetEvQueue() 这里获取到的是event_base对象--
//--这里相当于把事件堆绑定在当前线程里--
event_base_dispatch(p->GetEvQueue());
return NULL;
}
void main()
{
//----listen bind 部分同上---
//
//----------------------------
m_pEvQueue = event_base_new();
event_assign( &m_incEvent, m_pEvQueue,fd, evType, accept_cb, this);
event_base_set( m_pEvQueue,&m_incEvent );
event_add( &m_incEvent,NULL );
//--注意这里event_base堆里一个事件都没注册情况下 不能创建线程--
//--因为event_base_dispatch();此函数判断没有事件注册了的就会退出线程的--
int ret;
if ((ret = pthread_create(&m_incThreadID, NULL, ProcessThread,this)) != 0)
{
s_pLog->Log(LOG_ERROR, "%s %s => CreateThread failed",__FILE__,__FUNCTION__) ;return false;
}
}Source code

void accept_cb( int fd,short ev,void* arg )
{
CVitNetThread *p = (CVitNetThread*)arg;
int client_fd = -1;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof( client_addr );
client_fd = accept( fd,(struct sockaddr*)&client_addr,&client_len );
if( client_fd == -1 )
{
s_pLog->Log(LOG_ERROR, "%s %s => accept client failed fd = [%d]",__FILE__,__FUNCTION__,client_fd) ;return;
}
if (evutil_make_socket_nonblocking(client_fd) < 0)
{
s_pLog->Log(LOG_ERROR, "%s %s => Set client socket nonblock is failed",__FILE__,__FUNCTION__,client_fd) ;return;
}
//--这里创建一个缓存事件 对象 设置 recv write error 回调函数--
//--这里系统要是有recv到得信息的时候会自动调用recv_cb回调函数的
//--这里系统要是有send信息的时候自动调用write_cb回调函数
//--这里系统要是有出错或延迟的时候回调用此error_cb回调函数
bufferevent* bufferev = bufferevent_new( client_fd,recv_cb,write_cb,error_cb,p );
if( bufferev == NULL )
{
s_pLog->Log(LOG_ERROR, "%s %s => bufferev bis NULL",__FILE__,__FUNCTION__) ;return;
}
//--bufferev 事件 注册到 消息堆里 event_base 这里( p->GetEvQueue()返回一个event_base对象)
bufferevent_base_set( p->GetEvQueue(),bufferev );
bufferevent_enable( bufferev, EV_READ | EV_WRITE );
}

bufferevent recv,errorSource code

void recv_cb( struct bufferevent *ev, void *arg )
{
CVitNetThread *p = (CVitNetThread*)arg;
if( p == NULL )
{
s_pLog->Log(LOG_ERROR, "%s %s => argument is NULL",__FILE__,__FUNCTION__) ;return;
}
int fd = bufferevent_getfd(ev);
char buffer[READ_MAX]; memset( buffer, 0x00,sizeof( buffer ) );
int ret = bufferevent_read(ev, &buffer,evbuffer_get_length( ev->input ));
cout<<buffer<<endl;
}
void error_cb( struct bufferevent *ev, short events ,void *arg )
{
CVitNetThread *p = (CVitNetThread*)arg;
int fd = bufferevent_getfd(ev);
if( events & BEV_EVENT_CONNECTED )
cout<<"Connect ok"<<endl;
else if( events & (BEV_EVENT_ERROR | BEV_EVENT_EOF) )
cout<<"disconnect"<<endl;
else if( events & BEV_EVENT_TIMEOUT )
cout<<"TimeOut"<<endl;
else if( events & BEV_EVENT_WRITING )
cout<<"Wrting"<<endl;
bufferevent_free(ev);
}