最近在看memcached的源码,觉得它那种libevent+多线程的服务器模型真的很不错,我将这个模型封装成一个C++类,根据我的简单测试,这个模型的效率真的很不错,欢迎大家试用。
这个类的使用方法很简单(缺点是不太灵活),只要派生一个类,根据需要重写以下这几个虚函数就行了:
//新建连接成功后,会调用该函数
virtual void ConnectionEvent(Conn *conn) { }
//读取完数据后,会调用该函数
virtual void ReadEvent(Conn *conn) { }
//发送完成功后,会调用该函数(因为串包的问题,所以并不是每次发送完数据都会被调用)
virtual void WriteEvent(Conn *conn) { }
//断开连接(客户自动断开或异常断开)后,会调用该函数
virtual void CloseEvent(Conn *conn, short events) { }
//发生致命错误(如果创建子线程失败等)后,会调用该函数
//该函数的默认操作是输出错误提示,终止程序
virtual void ErrorQuit(const char *str);
如果大家有什么建议或意见,欢迎给我发邮件:aa1080711@163.com
上代码:
头文件:TcpEventServer.h
//TcpEventServer.h
#ifndef TCPEVENTSERVER_H_
#define TCPEVENTSERVER_H_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
#include <fcntl.h>
#include <map>
using std::map;
#include <event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
class TcpEventServer;
class Conn;
class ConnQueue;
struct LibeventThread;
//这个类一个链表的结点类,结点里存储各个连接的信息,
//并提供了读写数据的接口
class Conn
{
//此类只能由TcpBaseServer创建,
//并由ConnQueue类管理
friend class ConnQueue;
friend class TcpEventServer;
private:
const int m_fd; //socket的ID
evbuffer *m_ReadBuf; //读数据的缓冲区
evbuffer *m_WriteBuf; //写数据的缓冲区
Conn *m_Prev; //前一个结点的指针
Conn *m_Next; //后一个结点的指针
LibeventThread *m_Thread;
Conn(int fd=0);
~Conn();
public:
LibeventThread *GetThread() { return m_Thread; }
int GetFd() { return m_fd; }
//获取可读数据的长度
int GetReadBufferLen()
{ return evbuffer_get_length(m_ReadBuf); }
//从读缓冲区中取出len个字节的数据,存入buffer中,若不够,则读出所有数据
//返回读出数据的字节数
int GetReadBuffer(char *buffer, int len)
{ return evbuffer_remove(m_ReadBuf, buffer, len); }
//从读缓冲区中复制出len个字节的数据,存入buffer中,若不够,则复制出所有数据
//返回复制出数据的字节数
//执行该操作后,数据还会留在缓冲区中,buffer中的数据只是原数据的副本
int CopyReadBuffer(char *buffer, int len)
{ return evbuffer_copyout(m_ReadBuf, buffer, len); }
//获取可写数据的长度
int GetWriteBufferLen()
{ return evbuffer_get_length(m_WriteBuf); }
//将数据加入写缓冲区,准备发送
int AddToWriteBuffer(char *buffer, int len)
{ return evbuffer_add(m_WriteBuf, buffer, len); }
//将读缓冲区中的数据移动到写缓冲区
void MoveBufferData()
{ evbuffer_add_buffer(m_WriteBuf, m_ReadBuf); }
};
//带头尾结点的双链表类,每个结点存储一个连接的数据
class ConnQueue
{
private:
Conn *m_head;
Conn *m_tail;
public:
ConnQueue();
~ConnQueue();
Conn *InsertConn(int fd, LibeventThread *t);
void DeleteConn(Conn *c);
//void PrintQueue();
};
//每个子线程的线程信息
struct LibeventThread
{
pthread_t tid; //线程的ID
struct event_base *base; //libevent的事件处理机
struct event notifyEvent; //监听管理的事件机
int notifyReceiveFd; //管理的接收端
int notifySendFd; //管道的发送端
ConnQueue connectQueue; //socket连接的链表
//在libevent的事件处理中要用到很多回调函数,不能使用类隐含的this指针
//所以用这样方式将TcpBaseServer的类指针传过去
TcpEventServer *tcpConnect; //TcpBaseServer类的指针
};
class TcpEventServer
{
private:
int m_ThreadCount; //子线程数
int m_Port; //监听的端口
LibeventThread *m_MainBase; //主线程的libevent事件处理机
LibeventThread *m_Threads; //存储各个子线程信息的数组
map<int, event*> m_SignalEvents; //自定义的信号处理
public:
static const int EXIT_CODE = -1;
private:
//初始化子线程的数据
void SetupThread(LibeventThread *thread);
//子线程的入门函数
static void *WorkerLibevent(void *arg);
//(主线程收到请求后),对应子线程的处理函数
static void ThreadProcess(int fd, short which, void *arg);
//被libevent回调的各个静态函数
static void ListenerEventCb(evconnlistener *listener, evutil_socket_t fd,
sockaddr *sa, int socklen, void *user_data);
static void ReadEventCb(struct bufferevent *bev, void *data);
static void WriteEventCb(struct bufferevent *bev, void *data);
static void CloseEventCb(struct bufferevent *bev, short events, void *data);
protected:
//这五个虚函数,一般是要被子类继承,并在其中处理具体业务的
//新建连接成功后,会调用该函数
virtual void ConnectionEvent(Conn *conn) { }
//读取完数据后,会调用该函数
virtual void ReadEvent(Conn *conn) { }
//发送完成功后,会调用该函数(因为串包的问题,所以并不是每次发送完数据都会被调用)
virtual void WriteEvent(Conn *conn) { }
//断开连接(客户自动断开或异常断开)后,会调用该函数
virtual void CloseEvent(Conn *conn, short events) { }
//发生致命错误(如果创建子线程失败等)后,会调用该函数
//该函数的默认操作是输出错误提示,终止程序
virtual