今天,完成一下epoll的封装。

类图

  首先,还是画下类图,初步设计一下。   具体函数,我们下面详解。

epoll封装

EpollBase类

CEpollBase.h:

class CEpollBase
{
public:
	CEpollBase(int max_events);
	virtual ~CEpollBase();

	bool Create(int size);
	bool AddEvent(int fd,int events);
	bool ModEvent(int fd,int events);
	bool DelEvent(int fd,int events);
	bool Wait(int timeout = -1);	//监听一次事件的发生 
	virtual void onEvent() = 0;
	void Start();
	void Stop();

protected:
	struct epoll_event *m_rlt_events;
	struct epoll_event m_event;
	int m_nEvent;
	int m_epfd;
private:

	int m_max_events;
	bool isRun;

};

  在之前,我们监听事件的发生并进行处理,是放在while(1)循环里的,但是,真正项目里是不能存在死循环的,所以,这里的Wait()就是用来监听一次事件的发生,通过标志isRun的值来决定是否一直监听(即Start(),Stop())。纯虚函数onEvent()用来处理发生的事件,意味着该类是不能实例化的,并且子类都要重写。   CEpollBase.cpp:

CEpollBase::CEpollBase( int max_events )
{
	m_max_events = max_events;
	m_rlt_events = new struct epoll_event[max_events];
	memset(m_rlt_events,0,max_events * sizeof(struct epoll_event));
	isRun = true;
}

CEpollBase::~CEpollBase()
{
	delete []m_rlt_events;
	m_rlt_events = NULL;
}

bool CEpollBase::Create( int size )
{
	m_epfd = epoll_create(size);
	if (m_epfd == -1)
	{
		return false;
	}
	return true;
}

bool CEpollBase::AddEvent( int fd,int events )
{
	m_event.data.fd = fd;
	m_event.events = events ;	
	if ( epoll_ctl(m_epfd,EPOLL_CTL_ADD,fd,&m_event) == -1)
	{
		return false;
	}
	return true;
}

bool CEpollBase::ModEvent(int fd,int events)
{
	m_event.data.fd = fd;
	m_event.events = events ;	
	if ( epoll_ctl(m_epfd,EPOLL_CTL_MOD,fd,&m_event) == -1)
	{
		return false;
	}
	return true;
}

bool CEpollBase::DelEvent( int fd,int events )
{
	m_event.data.fd = fd;
	m_event.events = events ;	
	if ( epoll_ctl(m_epfd,EPOLL_CTL_DEL,fd,&m_event) == -1)
	{
		return false;
	}
	return true;
}

bool CEpollBase::Wait( int timeout /*= -1*/ )
{
	m_nEvent = epoll_wait(m_epfd,m_rlt_events,m_max_events,timeout);	 //阻塞
	if(m_nEvent == -1)
	{
		perror("epoll_wait");
		return false;
	}
	else if(m_nEvent == 0)
	{
		printf("time out.");
		return true;
	}
	else
	{
		//有事件发生,立即处理
		onEvent();
	}
}	

void CEpollBase::Start()
{
	while(isRun)
	{
		Wait();
	}
}

void CEpollBase::Stop()
{
	isRun = false;
}

ServerEpoll类

  客户端类应该是作为epoll类的成员的,所以,我们需要编写一个通用的服务器处理事件的类,这个就是CServerEpoll类。 CServerEpoll.h:

class CServerEpoll:public CEpollBase
{
public:
	CServerEpoll(char *ip,unsigned short port,int max_events,int backlog,int size,int type);
	CServerEpoll(CAddress &addr,int max_events,int backlog,int size,int type);

	void onEvent();
	virtual void onChat(char acbuf[],int fd);

protected:
private:
	CTcpServer m_server;
	map<int,CTcpClient> m_clientMap;	//<fd,CTcpClient>存放客户端的地址信息
};

CServerEpoll.cpp:

CServerEpoll::CServerEpoll( char *ip,unsigned short port,int max_events,int backlog,int size,int type = tcp_sock)
:CEpollBase(max_events)
{
	m_server.SetAddr(ip,port);
	m_server.Socket(type);
	m_server.Bind();
	m_server.Listen(backlog);

	Create(size);
	AddEvent(m_server.GetFd(),EPOLLIN);
}

CServerEpoll::CServerEpoll( CAddress &addr ,int max_events,int backlog,int size,int type = tcp_sock)
:CEpollBase(max_events)
{
	m_server.SetAddr(addr);
	m_server.Socket(type);
	m_server.Bind();
	m_server.Listen(backlog);

	Create(size);
	AddEvent(m_server.GetFd(),EPOLLIN);
}

void CServerEpoll::onEvent()
{
	int ret;
	char acbuf[1024] = "";
	CTcpClient conn;

	//有事件发生,立即处理
	for(int i = 0; i < m_nEvent; i++)
	{
		//如果是 sockfd
		if( m_rlt_events[i].data.fd == m_server.GetFd() )
		{
			conn = m_server.Accept();
			//添加到事件集合
			AddEvent(conn.GetFd(),EPOLLIN);
			printf("client ip:%s ,port:%u connect.\n",conn.GetAddr().GetIP(),conn.GetAddr().GetPort());
			//添加到客户端链表当中
			m_clientMap.insert(pair<int,CTcpClient> (conn.GetFd(), conn));
		}
		else	//否则 connfd 
		{
			ret = read(m_rlt_events[i].data.fd,acbuf,100);
			if( ret == 0) //客户端退出
			{
				close(m_rlt_events[i].data.fd);
				//从事件集合里删除
				DelEvent(m_rlt_events[i].data.fd, EPOLLIN);
				//从客户端链表中删除
				map<int,CTcpClient>::iterator it;
				for (it = m_clientMap.begin() ; it != m_clientMap.end(); it++)
				{
					if (it->first == m_rlt_events[i].data.fd)
					{
						m_clientMap.erase(it->first);
						break;
					}
				}
				printf("client ip:%s ,port:%u disconnect.\n\n",it->second.GetAddr().GetIP(),it->second.GetAddr().GetPort());
			}
			else
			{	
				onChat(acbuf,m_rlt_events[i].data.fd);
			}


		}

	}
}

void CServerEpoll::onChat( char acbuf[],int fd )
{
	//做解包的处理
	write(fd,acbuf,1024);

}

  这里的onData()可以是纯虚函数,也可以是虚函数,看自己设计。这个函数是针对不同项目服务器不同处理数据的。在这个项目中,我们还需要一个类:ChatServerEpoll类,该项目中用来处理聊天事件的服务器程序,也就是在这个类中,我们重写onData(),在该函数中处理:登录、聊天、用户列表等等的功能。

ChatServerEpoll类

CChatServerEpoll.h:

class CChatServerEpoll:public CServerEpoll
{
public:
	CChatServerEpoll(char *ip,unsigned short port,int max_events,int backlog,int size,int type);
	CChatServerEpoll(CAddress &addr,int max_events,int backlog,int size,int type);

	void onChat(char acbuf[],int fd);
protected:
private:
	map<string,int> m_userMap;	//<用户名,文件描述符>
};

CChatServerEpoll.cpp:

CChatServerEpoll::CChatServerEpoll( char *ip,unsigned short port,int max_events,int backlog,int size,int type = tcp_sock)
:CServerEpoll(ip,port,max_events,backlog,size,type)
{

}

CChatServerEpoll::CChatServerEpoll( CAddress &addr,int max_events,int backlog,int size,int type = tcp_sock)
:CServerEpoll(addr,max_events,backlog,size,type)
{

}

void CChatServerEpoll::onChat( char acbuf[],int fd )
{
	PK_HEAD head = {0};		//包头
	PK_LOGIN login ={0};	//登录包
	PK_CHAT chat = {0};		//聊天包
	int reply;				//登录应答包。 1-成功 0-失败
	PK_USERLIST userlist = {0};	//用户列表包

	map<string,int>::iterator it;

	//解包
	memset(&head,0,sizeof(head));
	memcpy(&head,acbuf,HEAD_SIZE);

	for(int i = 0; i < m_nEvent; i++)
	{
		switch(head.type)
		{
		case 1:	//登录或退出
			memset(&login,0,sizeof(login));
			memcpy(&login,acbuf + HEAD_SIZE,LOGIN_SIZE);

			if(login.isOnline)
			{
				//通过connfd区分不同客户端
				reply = LOGIN_OK;
				memcpy(acbuf + HEAD_SIZE , &reply , 4);
				write(m_rlt_events[i].data.fd,acbuf,HEAD_SIZE + 4);	//登录成功应答包
				m_userMap.insert(pair<string,int>(login.name,m_rlt_events[i].data.fd));
				printf("client %s login.\n\n",login.name);
			}
			else
			{
				m_userMap.erase(login.name);
				printf("client %s exit.\n",login.name);
			}
			
			break;

		case 2:		//聊天
			memset(&chat,0,CHAT_SIZE);
			memcpy(&chat,acbuf + HEAD_SIZE,CHAT_SIZE);
			if(strcmp(chat.toName,"all") == 0)
			{
				//群聊
				for (it = m_userMap.begin(); it != m_userMap.end(); it++)
				{
					//转发消息
					if (it->second != m_rlt_events[i].data.fd)
					{
						write(it->second, acbuf, HEAD_SIZE + CHAT_SIZE);
					}

				}
			}
			else
			{
				//私聊
				if ( (it = m_userMap.find(chat.toName)) != m_userMap.end())	//找到了
				{
					//转发消息
					write(it->second, acbuf, HEAD_SIZE + CHAT_SIZE);
				}
				else	//用户不存在
				{
					memset(&chat.msg,0,100);
					strcpy(chat.msg,"the acccount is not exist.");
					memcpy(acbuf + HEAD_SIZE, &chat, CHAT_SIZE);
					write(m_rlt_events[i].data.fd, acbuf, HEAD_SIZE + CHAT_SIZE);
				}
			}
			break;

		case 3:
 			memset(&userlist,0,USERLIST_SIZE);
 			int j = 0;
			for (it = m_userMap.begin();j<MAX_USERS && it!= m_userMap.end(); it++,j++)
			{
 				strncpy(userlist.userName[10*j],it->first.c_str(),10);
			}

			memcpy(acbuf + HEAD_SIZE, &userlist, USERLIST_SIZE);
			write(m_rlt_events[i].data.fd, acbuf, HEAD_SIZE + USERLIST_SIZE);

			break;

		}
	}

}

功能测试

  编写好类之后,我们就可以修改之前的服务器端的代码了,客户端不变。 server.cpp:

#include "common.h"
#include "ChatServerEpoll.h"

#define MAX_LISTEN_SIZE 10
#define MAX_EPOLL_SIZE 1000
#define MAX_EVENTS 20

int main()
{	
	char ip[20] = "192.168.159.6";
	unsigned short port = 1234;
	SOCKET_TYPE type = tcp_sock;
	CChatServerEpoll ser_epoll(ip,port,MAX_EVENTS,MAX_LISTEN_SIZE,MAX_EPOLL_SIZE,type);
	ser_epoll.Start();

	return 0;
}

  是不是简化很多了。   到这里为止,代码已经基本完善。编译的时候,可以分两个文件夹。完整的代码已经上传。SocketChat