显示标签为“Libevent”的博文。显示所有博文
显示标签为“Libevent”的博文。显示所有博文

2013年2月17日星期日

memcache 服务器端分析--libevent队列 线程模型


目前网上关于memcached的分析主要是内存管理部分,下面对memcached的线程模型做下简单分析 

有不对的地方还请大家指正,对memcahced和libevent不熟悉的请先google之 
先看下memcahced启动时线程处理的流程 

memcache 服务器端分析--libevent队列 线程模型 - zhengjunwei2007 - 我的博客
 
memcached的多线程主要是通过实例化多个libevent实现的,分别是一个主线程和n个workers线程 
无论是主线程还是workers线程全部通过libevent管理网络事件,实际上每个线程都是一个单独的libevent实例 
主线程负责监听客户端的建立连接请求,以及accept 连接 
workers线程负责处理已经建立好的连接的读写等事件 
先看一下大致的图示: 
memcache 服务器端分析--libevent队列 线程模型 - zhengjunwei2007 - 我的博客

简单介绍libevent快速上手


简介
libevent是一个事件出发的网络库,使用与 windows,linux,bad, mac os 等的高性能跨平台网络库 ,支持的多种I/O 网络模型 epoll poll dev/poll select kqueue 等

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普通简单例子实现

 
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 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 code

Source 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 和 多线程用法



 
//--线程回调--
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,error

Source 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);
}

libevent(源于维基百科,自由的百科全书)



原作者 Nick Mathewson, Niels Provos
稳定版本 2.0.10/
2010年12月16日
平台 跨平台
语言 C语言、C++
类型 函式库
许可协议 BSD许可证
网站 http://www.monkey.org/~provos/libevent/
libevent是一个异步事件处理软件函式库,以BSD许可证发布。
libevent提供了一组应用程序编程接口(API),让程式设计师可以设定某些事件发生时所执行的函式,也就是说,libevent可以用来取代网络服务器所使用的事件循环检查框架。
由于可以省去对网络的处理,且拥有不错的效能,有些软件使用libevent作为网络底层的函式库,如:memcached、Tor。

目前libevent支持以下的方式判断IO事件:
poll(2)
select(2)
几乎所有的Unix平台都有提供的函式。
/dev/pool
以Solaris平台为主。
kqueue(2)
以BSD平台为主。
epoll(2)
以Linux平台为主。

libevent源码深度剖析二——Reactor模式


前面讲到,整个libevent本身就是一个Reactor,因此本节将专门对Reactor模式进行必要的介绍,并列出libevnet中的几个重要组件和Reactor的对应关系,在后面的章节中可能还会提到本节介绍的基本概念。

1 Reactor的事件处理机制

首先来回想一下普通函数调用的机制:程序调用某函数?函数执行,程序等待?函数将结果和控制权返回给程序?程序继续处理。
Reactor释义“反应堆”,是一种事件驱动机制。和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的时间发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。使用Libevent也是想Libevent框架注册相应的事件和回调函数;当这些时间发声时,Libevent会调用这些回调函数处理相应的事件(I/O读写、定时和信号)。
    用“好莱坞原则”来形容Reactor再合适不过了:不要打电话给我们,我们会打电话通知你。
    举个例子:你去应聘某xx公司,面试结束后。
“普通函数调用机制”公司HR比较懒,不会记你的联系方式,那怎么办呢,你只能面试完后自己打电话去问结果;有没有被录取啊,还是被据了;

“Reactor”公司HR就记下了你的联系方式,结果出来后会主动打电话通知你:有没有被录取啊,还是被据了;你不用自己打电话去问结果,事实上也不能,你没有HR的留联系方式。

2 Reactor模式的优点

Reactor模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:
    1)响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
    2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
    3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
    4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

3 Reactor模式框架

    使用Reactor模型,必备的几个组件:事件源、Reactor框架、多路复用机制和事件处理程序,先来看看Reactor模型的整体框架,接下来再对每个组件做逐一说明。
 components 

1) 事件源
Linux上是文件描述符,Windows上就是Socket或者Handle了,这里统一称为“句柄集”;程序在指定的句柄上注册关心的事件,比如I/O事件。

2) event demultiplexer——事件多路分发机制
由操作系统提供的I/O多路复用机制,比如select和epoll。
    程序首先将其关心的句柄(事件源)及其事件注册到event demultiplexer上;
当有事件到达时,event demultiplexer会发出通知“在已经注册的句柄集中,一个或多个句柄的事件已经就绪”;
    程序收到通知后,就可以在非阻塞的情况下对事件进行处理了。
对应到libevent中,依然是select、poll、epoll等,但是libevent使用结构体eventop进行了封装,以统一的接口来支持这些I/O多路复用机制,达到了对外隐藏底层系统机制的目的。

3) Reactor——反应器
    Reactor,是事件管理的接口,内部使用event demultiplexer注册、注销事件;并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。
对应到libevent中,就是event_base结构体。
一个典型的Reactor声明方式
  1. class Reactor  
  2. {  
  3. public:  
  4.     int register_handler(Event_Handler *pHandler, int event);  
  5.     int remove_handler(Event_Handler *pHandler, int event);  
  6.     void handle_events(timeval *ptv);  
  7.     // ...  
  8. };  

4) Event Handler——事件处理程序
    事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供Reactor在相应的事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。
对应到libevent中,就是event结构体。
下面是两种典型的Event Handler类声明方式,二者互有优缺点。
  1. class Event_Handler  
  2. {  
  3. public:  
  4.     virtual void handle_read() = 0;  
  5.     virtual void handle_write() = 0;  
  6.     virtual void handle_timeout() = 0;  
  7.     virtual void handle_close() = 0;  
  8.     virtual HANDLE get_handle() = 0;  
  9.     // ...  
  10. };  
  11. class Event_Handler  
  12. {  
  13. public:  
  14.     // events maybe read/write/timeout/close .etc  
  15.     virtual void handle_events(int events) = 0;  
  16.     virtual HANDLE get_handle() = 0;  
  17.     // ...  
  18. };  

4 Reactor事件处理流程

前面说过Reactor将事件流“逆置”了,那么使用Reactor模式后,事件控制流是什么样子呢?
可以参见下面的序列图。
 reactor sequences

5 小结

上面讲到了Reactor的基本概念、框架和处理流程,对Reactor有个基本清晰的了解后,再来对比看libevent就会更容易理解了,接下来就正式进入到libevent的代码世界了,加油!

参考资料:
Pattern-Oriented Software Architecture, Patterns for Concurrent and Networked Objects, Volume 2

libevent源码深度剖析一



1 前言
Libevent是一个轻量级的开源高性能网络库,使用者众多,研究者更甚,相关文章也不少。写这一系列文章的用意在于,一则分享心得;二则对libevent代码和设计思想做系统的、更深层次的分析,写出来,也可供后来者参考。

附带一句:Libevent是用c语言编写的(MS大牛们都偏爱c语言哪),而且几乎是无处不函数指针,学习其源代码也需要相当的c语言基础。
2 Libevent简介
上来当然要先夸奖啦,Libevent 有几个显著的亮点:
事件驱动(event-driven),高性能;
轻量级,专注于网络,不如ACE那么臃肿庞大;
源代码相当精炼、易读;
跨平台,支持Windows、Linux、*BSD和Mac Os;
支持多种I/O多路复用技术, epoll、poll、dev/poll、select和kqueue等;
支持I/O,定时器和信号等事件;
注册事件优先级;

Libevent已经被广泛的应用,作为底层的网络库;比如memcached、Vomit、Nylon、Netchat等等。
Libevent当前的最新稳定版是1.4.13;这也是本文参照的版本。
3 学习的好处
学习libevent有助于提升程序设计功力,除了网络程序设计方面外,Libevent的代码里有很多有用的设计技巧和基础数据结构,比如信息隐藏、函数指针、c语言的多态支持、链表和堆等等,都有助于提升自身的程序功力。
程序设计不止要了解框架,很多细节之处恰恰也是事关整个系统成败的关键。只对libevent本身的框架大概了解,那或许仅仅是一知半解,不深入代码分析,就难以了解其设计的精巧之处,也就难以为自己所用。

事实上Libevent本身就是一个典型的Reactor模型,理解Reactor模式是理解libevent的基石;因此下一节将介绍典型的事件驱动设计模式——Reactor模式。

参考资料:
Libevent: http://monkey.org/~provos/libevent/