今天,完成一下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
来源:51CTO
作者:SherryX
链接:https://blog.51cto.com/13097817/2068332