Socket封裝之聊天程序(三)
阿新 • • 發佈:2018-02-02
pro eat iter fine else if 通過 ext 51cto string 今天,完成一下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;
}
是不是簡化很多了。
到這裏為止,代碼已經基本完善。編譯的時候,可以分兩個文件夾。完整的代碼已經上傳。
Socket封裝之聊天程序(三)