C++游戏服务器框架笔记(一)_封装数据包类
C++游戏服务器框架笔记(二)_封装Socket类
C++游戏服务器框架笔记(三)_封装ByteBuffer类
C++游戏服务器框架笔记(四)_封装Select
C++游戏服务器框架笔记(五)_封装Epoll类
在上一文中已经介绍了,为了将Windows下的select()和linux下的epoll()封装为统一接口,所以需要首先定义一个接口类,来约定统一接口和一些类型,做到不同平台,调用接口一致,内部实现根据平台不同的跨平台效果,并给出了接口类的实现,这里为了方便阅读,再贴一遍代码:
统一接口类(多路复用基类):
#ifndef __POLLBASE_H_
#define __POLLBASE_H_#include <stdlib.h>
#include <map>
#include <vector>//统一描述符的类型命名,都为SOCKET
#ifndef _WIN32
typedef int SOCKET;
#define INVALID_HANDLE_VALUE (-1)
#endif//重定义Windows下FD_SETSIZE的大小
#ifdef _WIN32
#define FD_SETSIZE 1024
#include <WinSock2.h>
#else
#include <unistd.h>
#include <sys/time.h>
#include <sys/epoll.h>
#include <sys/select.h>
#endif#define MAXEVENT 1024//统一定义Windows下select()和linux下epoll()的事件的宏
#define NONE 0 //无
#define POLLREAD 1 //读事件
#define POLLWRITE 2 //写事件
#define POLLET 3 //边缘触发(用于EPOLL, 是否设置边缘触发,默认是水平触发)//定义统一的事件结构
typedef struct PollEvent {int event;int fd;
}PollEvent;//接口类(抽象类)
class PollBase
{
public:virtual ~PollBase() {};/*创建(初始化)接口size: 可关注的描述符数量返回值:0: 成功-1: 失败*/virtual int Create(int size) = 0;/*为fd描述符添加关注的事件fd: socket文件描述符mask: 上面统一定义的事件宏(读事件、写事件)返回值:0: 成功-1: 失败*/virtual int AddEvent(SOCKET fd, int mask) = 0;/*删除fd描述符关注的事件fd: socket文件描述符mask: 上面统一定义的事件宏(读事件、写事件)返回值:0: 成功-1: 失败*/virtual int DelEvent(SOCKET fd, int mask) = 0;/*监听事件到来,IO多路复用的核心active_events: 输出参数,如果有事件到来,则会将事件设置到active_events输出timeout: 超时时间(秒)返回值:0:无事件>0:到来事件的数量*/virtual int Poll(PollEvent* active_events, int timeout = 0) = 0;
};#endif
Epoll.h文件中定义Epoll类继承自PollBase
#ifndef _EPOLL_H_
#define _EPOLL_H_#include "PollBase.h"
#ifndef _WIN32class Epoll :public PollBase
{
public:~Epoll();int Create(int size);int AddEvent(SOCKET fd, int event);int DelEvent(SOCKET fd, int event);int Poll(PollEvent* active_events, int timeout = 0);private://epoll文件描述符(epoll实例句柄,epoll_create()的返回值)int m_EpollFd;//epoll事件数量int m_EventSize;//epoll fd对应关注事件(POLLREAD、POLLWRITE、POLLET)映射表std::map<SOCKET, int> m_EventMap;//存储到来的临时事件列表std::vector<struct epoll_event> m_Events;
};
#endif#endif
Epoll.cpp
#include "Epoll.h"
#ifndef _WIN32//析构函数中清理数据
Epoll::~Epoll() {m_Events.clear();m_Events.shrink_to_fit();m_EventMap.clear();//关闭epoll文件描述符if (m_EpollFd != -1)close(m_EpollFd);
}//创建多路IO
int Epoll::Create(int size) {m_EventSize = size;m_Events.resize(m_EventSize);//创建epollm_EpollFd = epoll_create(size);if (-1 == m_EpollFd) {return -1;}m_EventMap.clear();return 0;
}//增加关注事件
int Epoll::AddEvent(SOCKET fd, int event) {//临时存储旧的关注事件int old_event = m_EventMap[fd];//存储新的关注事件int new_event = m_EventMap[fd];//判断添加可读事件(|=表示不删除原来关注的事件的前提下,增加关注新的事件)if (event & POLLREAD) {new_event |= EPOLLIN;}//判断添加可写事件if (event & POLLWRITE) {new_event |= EPOLLOUT;}//判断设置边缘触发模式(默认是水平触发模式)if (event & POLLET){new_event |= EPOLLET;}//更新socketfd对应的事件m_EventMap[fd] = new_event;//创建新的epoll事件结构,赋值新的关注事件struct epoll_event ev;ev.events = new_event;ev.data.fd = fd;//old_event>0表示该fd之前有在epoll中关注过其他事件,所以这里应该是使用修改更新事件的模式,//否则应该使用添加新事件的模式int ctl_mode = old_event > 0 ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;if (-1 == epoll_ctl(m_EpollFd, ctl_mode, fd, &ev)) {return -1;}return 0;
}//移除关注的事件 (基本逻辑同添加)
int Epoll::DelEvent(SOCKET fd, int event) {int old_event = m_EventMap[fd];int new_event = old_event;if (event & POLLREAD) {new_event ^= EPOLLIN;}if (event & POLLWRITE) {new_event ^= EPOLLOUT;}m_EventMap[fd] = new_event;struct epoll_event ev;ev.events = new_event;ev.data.fd = fd;int ctl_mode = ev.events > 0 ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;if (-1 == epoll_ctl(m_EpollFd, ctl_mode, fd, &ev)) {return -1;}return 0;
}//事件分发
int Epoll::Poll(PollEvent* active_events, int timeout) { //epoll_wait 等待关注的事件到来//当新事件到来时,会将到来的事件写入m_Events数组int num = epoll_wait(m_EpollFd, &m_Events[0], m_EventSize, timeout);//num<=0 表示无事件到来,不作处理(本系列统一使用非阻塞套接字,因此当没有事件的时候,num一般都为0)if (num <= 0)return 0;//当有事件到来时,num则是到来的事件数量,遍历m_Events事件数组处理各个事件for (int i = 0; i < num; i++) {int event = 0;//可读事件if (m_Events[i].events & EPOLLIN) {event |= POLLREAD;}//可写事件if (m_Events[i].events & EPOLLOUT) {event |= POLLWRITE;}//将事件信息赋值给输出参数active_events数组active_events[i].fd = m_Events[i].data.fd;active_events[i].event = event;}//返回事件数量return num;
}
#endif
封装后的大概调用流程,和前文的Select接口调用代码一致:
......void EventLoop::Run() {while (!m_Stop) {memset(m_ActiveEvents, 0, m_Size);int num = m_Poll->Poll(m_ActiveEvents);if (num > 0) {//分发事件进行处理this->ActiveEvents(num);}}
}......
经过前面各类基础类的封装,到现在封装之后,接下来就是对各个事件的处理,处理各个tcp连接,以及在数据的流转,最后和上层调用者的交互了。