多路复用I/O Epoll的简单使用

不负责任地放上中文版Epoll维基,科学上网。

原理什么的不多讲,我也不是很熟,这里就简单写一些实际应用要怎么写,当然也是很简单的demo。

结构体介绍

typedef union epoll_data {     
    void        *ptr;
    int          fd;           // 发生事件的主体,各种描述符
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
   uint32_t     events;      /* Epoll events */       // 要监听的事件类型
   epoll_data_t data;        /* User data variable */ 
};

API介绍

int epoll_create(int size);
// 这个函数用来创建epoll
// 参数size是指这个epoll最多能同时监听多少个文件描述符的事件
// 函数返回值是epoll的文件描述符,linux下面文件描述符简直无处不在
int efd = epoll_create(10);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 这个函数用来管理事件的监听
// 参数epfd就是用epoll_create函数获得的文件描述符
// 参数op是一个用位数来表示配置的变量,表示当前操作是增加事件还是删除事件
// 参数event保存的是具体的事件配置
int fd = socket(FA_INET, STREAM, 0);
...
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLRDHUP;
epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event);
  • op参数
    • EPOLL_CTL_ADD 将目标文件描述符fd已经fd对应的事件,通过efd注册到对应的epoll上
    • EPOLL_CTL_MOD 修改事件的具体事件通过文件描述符
    • EPOLL_CTL_DEL 将fd对应的事件从epoll中删除
  • struct epoll_event 事件及结构体的书写
    • 一个epoll_event可以设置监听多种事件类型
    • 最重要的是两件事,一个是设置对应的要监听的文件描述符,一个是设置监听事件类型,两者都是直接赋值的方式。
    • 常用事件类型:
      • EPOLLIN 可读事件(对等方套接字合法关闭也是可读事件)
      • EPOLLOUT 写事件
      • EPOLLRDHUP 对等方套接字关闭连接事件
      • EPOLLPRI 有紧急需要读的事件
      • EPOLLERR 文件描述符发生异常条件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// 这个函数用来获取当前的事件处于待处理状态的文件描述符
// epfd是epoll描述符
// events是事件列表,用来获取待处理的事件,
// maxevents表示最多能取多少个struct epoll_event
// timeout表示超时时间,如果一直没有事件响应,那么到了超时时间,函数会阻塞状态返回回来,-1表示没有超时时间,一直阻塞等待事件的到达
struct epoll_event events[10];
int ret = epoll_wait(efd, events, 10, -1);
for (int i = 0; i < ret; i++) {
    char buf[1024] = { 0 };
    int res = read(events[i].data.fd, buf, sizeof buf);
    if (res == 0) {
        epoll_ctl(efd, EPOLl_CTL_DEL, &events[i]);   // 从epoll中删除文件描述符
        close(events[i].data.fd); 
    } else {
        cout << buf << endl;
    }
}

代码实现

#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <sys/epoll.h>              

#include <crazy/net/Socket.h>                   // 个人封装的网络库,相当简单,未来会完善(说出来我自己都不信)

using namespace std;

int main() {
    map<int, Socket> fdClient;                  
    Socket server(8080);                        // 本地套接字(用我自己写的一个库封装了起来)
    server.init();                              // 初始化套接字
    server.Bind();                              // 绑定套接字
    server.Listen(10);                          // 设置监听数量

    int efd = epoll_create(10);                 // 创建epoll事件监听器,efd事件该事件监听器的文件描述符
    struct epoll_event ev;                      // 一次性事件
    struct epoll_event events[10];                  // 事件列表,用来从epoll中取出事件
    ev.events = EPOLLIN;                            // 仅当有可读事件事件发生唤醒
    ev.data.fd = server.getFd();                    // 将本地套接字放入事件结构体中
    epoll_ctl(efd, EPOLL_CTL_ADD, server.getFd(), &ev);     // 将准备好的事件放入监听列表中

    while (true) {                              // 事件循环
        int ret = epoll_wait(efd, events, 10, -1);          // 用events从epoll中获取事件列表
        for (int i = 0; i < ret; i++) {
            if (events[i].data.fd == server.getFd()) {      // 如果该套接字是本地套接字,那么证明有新链接
                SocketData clientSock;
                server.Accept(&clientSock);
                ev.data.fd = clientSock.fd;                 // 给临时事件结构体赋值网络套接字
                ev.data.fd == EPOLLIN | EPOLLET;            // 设置要监听的事件
                epoll_ctl(efd, EPOLL_CTL_ADD, clientSock.fd, &ev);  // 将配置好的事件放入监听列表中
                Socket client(clientSock);                  // 创建对应的客户端套接字(方便读写)
                fdClient.insert(pair<int, Socket>(clientSock.fd, client));
            } else {                                // 正常的I/O读写事件
                char buf[100] = { 0 };
                int ret = fdClient[events[i].data.fd].Read(buf, 100);
                if (ret == 0) {                     // 如果read返回值为0的话,对等方关闭套接字
                    map<int, Socket>::iterator it = fdClient.find(events[i].data.fd);       // 从映射列表中找到要删除的套接字
                    if (it != fdClient.end()) {     // 确定有这个映射
                        epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, NULL);         // 从epoll中删除事件
                        close(events[i].data.fd);           // 关闭回收文件描述符
                        fdClient.erase(it);             
                    }
                } else if (ret == -1) {             // I/O异常
                    perror("peer connection error:");
                } else {                            // 读到有效信息
                    cout << buf << endl;
                    fdClient[events[i].data.fd].Write(buf, strlen(buf));            // 并将收到的消息转发回去
                }
            }
        }
    }

    server.Close();         // 关闭本地套接字
    close(efd);             // 关闭epoll文件描述符

    return 0;
}

上面代码中用到的网络套接字封装我放在github中了 : crazy_net

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值