epoll接口使用 -- 非阻塞式网络io(仅读事件)

10 篇文章 0 订阅

目录

epoll接口使用

思路 

注意点

代码

封装epoll接口

epoll.sever.hpp

运行结果 


epoll接口使用

接口+epoll原理介绍 -- epoll接口介绍,epoll模型介绍+原理,接口和模型的关系,epoll优点(和select/poll进行对比)-CSDN博客

思路 

我们可以先将系统提供的epoll简单封装一下,更方便我们使用

  • 比如在对象被创建时,就创建epoll模型 ; 在对象被销毁时,自动关闭模型对应文件
  • 每次在操作红黑树结点时,不需要我们手动传入struct epoll_event结构,而是在类内创建,只 ; 并且细分操作类型,在执行删除时就不用创建了
  • 加入打印日志

虽然epoll原理上和select/poll截然不同,但在代码逻辑上是类似的

  • 监听套接字创建好后,就将它加入红黑树
  • 每次循环检测并获取[已就绪的文件及其事件]
  • 根据fd,将读事件分为两类 -- 新连接到来,读取数据就绪,这样可以将事件派发给相应的处理器
  • 处理还是老一套,和select,epoll是一样的,直接获取新连接/读取数据即可

注意点

将[通信用套接字文件]从红黑树移除时,需要调用epoll_ctl

  • 但注意,在调用前要保证该文件的fd是有效
  • 也就是,先删除,再关闭文件

代码

封装epoll接口

#include <sys/epoll.h>
#include <cstring>
#include <errno.h>
#include "Log.hpp"

static const int def_epoll_size = 128;

struct no_copy
{
    no_copy() {}
    ~no_copy() {}
    no_copy(const no_copy &t) = delete;
    no_copy operator=(const no_copy &t) = delete;
};

// 我们要保证这个对象唯一,因为没有必要存在多个[自定义操作epoll模型的方法],可以直接禁止拷贝/赋值,也可以继承一个无法拷贝/赋值的类
class MY_EPOLL : public no_copy
{
public:
    MY_EPOLL(int timeout)
    {
        sock_ = epoll_create(def_epoll_size);
        if (sock_ < 0)
        {
            lg(ERROR, "epoll_create error,%s\n", strerror(errno));
        }
        else
        {
            lg(DEBUG, "epoll_create success,fd: %d\n", sock_);
        }
        timeout_ = timeout;
    }
    ~MY_EPOLL()
    {
        if (sock_ >= 0)
        { // 保证在fd有效的情况下,关闭
            close(sock_);
        }
    }
    int wait(struct epoll_event *events, int max)
    {
        return epoll_wait(sock_, events, max, timeout_);
    }
    int ctl(int op, int fd, uint32_t event) // 这里我们设置成直接传入事件名,然后在内部构建epoll_event结构
    {
        int n = 0;
        if (op == EPOLL_CTL_DEL)
        {
            n = epoll_ctl(sock_, op, fd, nullptr); // 删除后,无论什么事件都无所谓了
            if (n == -1)
            {
                lg(ERROR, "EPOLL_CTL_DEL error\n");
            }
        }
        else
        {
            struct epoll_event t;
            t.events = event;
            t.data.fd = fd; // 方便用户层拿到时,可以知道是哪个fd就绪
            n = epoll_ctl(sock_, op, fd, &t);
            if (n == -1)
            {
                lg(ERROR, "EPOLL_CTL_ADD/MOD error,%s\n", strerror(errno));
            }
        }
        return n;
    }

private:
    int timeout_;
    int sock_;
};

epoll.sever.hpp

#include "Log.hpp"
#include "socket.hpp"
#include "myepoll.hpp"
#include <memory>

static const int def_port = 8080;
static const int def_timeout = 1000;
static const int def_size = 64;

class epoll_server
{
public:
    epoll_server()
        : p_listen_socket_(new MY_SOCKET), p_epoll_(new MY_EPOLL(def_timeout)) {}
    ~epoll_server()
    {
    }
    void start()
    {
        init();
        // 添加监听套接字
        int fd = p_listen_socket_->get_fd();
        p_epoll_->ctl(EPOLL_CTL_ADD, fd, EPOLLIN);

        while (true)
        {
            struct epoll_event events[def_size];
            int ret = p_epoll_->wait(events, def_size);
            if (ret > 0) // 有事件就绪
            {
                handle(ret, events);
            }
            else if (ret == 0) // 超时
            {
                continue;
            }
            else
            {
                perror("epoll_wait");
                break;
            }
        }
    }

private:
    void init()
    {
        p_listen_socket_->Socket();
        p_listen_socket_->Bind(def_port);
        p_listen_socket_->Listen();
    }
    void handle(int n, struct epoll_event *events)
    {
        int listen_sock_ = p_listen_socket_->get_fd();
        for (int i = 0; i < n; ++i) // 遍历有效事件
        {
            int fd = events[i].data.fd;     // 这里我们就可以知道是哪个文件上的事件就绪了
            if (events[i].events & EPOLLIN) // 有读事件就绪
            {
                if (fd == listen_sock_) // 获取新连接
                {
                    accepter(fd);
                }
                else // 读事件
                {
                    receiver(fd);
                }
            }
        }
    }
    void receiver(int fd)
    {
        char in_buff[1024];
        int n = read(fd, in_buff, sizeof(in_buff) - 1);
        if (n > 0)
        {
            in_buff[n - 1] = 0;
            std::cout << "get message: " << in_buff << std::endl;
        }
        else if (n == 0) // 客户端关闭连接
        {
            lg(INFO, "%d quit", fd);
            p_epoll_->ctl(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
        else
        {
            lg(ERROR, "fd: %d ,read error");
            p_epoll_->ctl(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
    }
    void accepter(int fd)
    {
        std::string clientip;
        uint16_t clientport;
        int sock = p_listen_socket_->Accept(clientip, clientport);
        if (sock == -1)
        {
            return;
        }
        else // 把新文件上的读事件加入红黑树
        {
            p_epoll_->ctl(EPOLL_CTL_ADD, sock, EPOLLIN);
            lg(INFO, "get a new link, client info@ %s:%d", clientip.c_str(), clientport);
        }
    }

private:
    std::shared_ptr<MY_SOCKET> p_listen_socket_;
    std::shared_ptr<MY_EPOLL> p_epoll_;
};

运行结果 

  • 服务器可以成功读取到客户端发来的数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值