C++游戏服务器框架笔记(五)_封装Epoll

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 _WIN32

class 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);
    
    //创建epoll
    m_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连接,以及在数据的流转,最后和上层调用者的交互了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为您提供一种可能的实现方式。 首先,我们需要定义一个名为 `EpollWrapper` 的类,并且包含以下成员函数: ```cpp class EpollWrapper { public: EpollWrapper(); ~EpollWrapper(); int add(int fd, uint32_t events); int modify(int fd, uint32_t events); int remove(int fd); int wait(int timeout_ms, epoll_event* events, int max_events); int close(); private: int epoll_fd_; }; ``` 其中,`add()` 函数用于向 epoll 实例中添加一个文件描述符及其对应的事件类型,`modify()` 函数用于修改已添加的文件描述符对应的事件类型,`remove()` 函数用于从 epoll 实例中移除一个文件描述符,`wait()` 函数用于等待事件就绪并返回就绪的文件描述符,`close()` 函数用于关闭 epoll 实例。 接下来,我们可以实现这些成员函数。首先是构造函数和析构函数: ```cpp EpollWrapper::EpollWrapper() { epoll_fd_ = epoll_create1(0); if (epoll_fd_ == -1) { throw std::runtime_error("Failed to create epoll instance"); } } EpollWrapper::~EpollWrapper() { close(); } ``` 构造函数中使用 `epoll_create1()` 函数创建一个 epoll 实例,并且检查返回值以确保实例创建成功。析构函数中调用 `close()` 函数以关闭 epoll 实例。 接下来,我们可以实现 `add()`、`modify()`、`remove()`、`wait()` 和 `close()` 函数: ```cpp int EpollWrapper::add(int fd, uint32_t events) { epoll_event event; event.data.fd = fd; event.events = events; return epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event); } int EpollWrapper::modify(int fd, uint32_t events) { epoll_event event; event.data.fd = fd; event.events = events; return epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &event); } int EpollWrapper::remove(int fd) { return epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr); } int EpollWrapper::wait(int timeout_ms, epoll_event* events, int max_events) { return epoll_wait(epoll_fd_, events, max_events, timeout_ms); } int EpollWrapper::close() { int ret = 0; if (epoll_fd_ != -1) { ret = ::close(epoll_fd_); epoll_fd_ = -1; } return ret; } ``` 这些函数中,`add()`、`modify()` 和 `remove()` 函数都是通过调用 `epoll_ctl()` 函数来实现的,`wait()` 函数则是通过调用 `epoll_wait()` 函数来等待就绪的事件,`close()` 函数则是通过调用 `::close()` 函数来关闭 epoll 实例。 最后,我们需要编写单元测试来验证我们的实现是否正确。我们可以使用 Google Test 框架来编写单元测试。以下是一个简单的单元测试示例: ```cpp #include <gtest/gtest.h> #include <sys/socket.h> #include <sys/epoll.h> #include <unistd.h> #include "epoll_wrapper.h" TEST(EpollWrapperTest, Basic) { EpollWrapper epoll; ASSERT_TRUE(epoll.add(STDIN_FILENO, EPOLLIN) == 0); int pipe_fds[2]; ASSERT_TRUE(pipe(pipe_fds) == 0); ASSERT_TRUE(epoll.add(pipe_fds[0], EPOLLIN) == 0); epoll_event events[2]; int ret = epoll.wait(1000, events, 2); ASSERT_TRUE(ret >= 0); for (int i = 0; i < ret; ++i) { if (events[i].data.fd == STDIN_FILENO) { char buf[1024]; int n = read(STDIN_FILENO, buf, sizeof(buf)); ASSERT_TRUE(n >= 0); } else if (events[i].data.fd == pipe_fds[0]) { char buf[1024]; int n = read(pipe_fds[0], buf, sizeof(buf)); ASSERT_TRUE(n >= 0); } } ASSERT_TRUE(epoll.remove(STDIN_FILENO) == 0); ASSERT_TRUE(epoll.remove(pipe_fds[0]) == 0); close(pipe_fds[0]); close(pipe_fds[1]); } ``` 在这个单元测试中,我们首先创建了一个 `EpollWrapper` 实例,然后向其中添加标准输入(`STDIN_FILENO`)和一个管道读端的文件描述符,并且等待这些文件描述符上的事件就绪。如果在等待过程中发生错误,单元测试会失败。如果等待成功,则断言返回的就绪事件个数大于等于 0。然后,我们可以遍历就绪事件,读取就绪的文件描述符中的数据并且进行断言。最后,我们移除已添加的文件描述符,并且关闭管道。 这就是一个可能的 C++ epoll/poll 封装类及单元测试的实现方式。当然,这只是一种参考,具体的实现方式可能因应用场景不同而不同。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春休夏末

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值