Linux系统编程—epoll

1. 简介

epoll 的优点:

  • 当检查大量的文件描述符时,epoll 的性能比 select() 和 poll() 要高。
  • epoll 既支持水平触发也支持边缘触发,select() 和 poll() 只支持水平触发。

epoll 的缺点:epoll 是 Linux 特有的,不可移植。

2. 函数原型

#include <sys/epoll.h>

int epoll_create(int size);
  • 创建一个新的 epoll 实例。size 参数告诉内核应如何为内部数据结构划分初始大小,自 Linux 2.6.8 以后,size 参数被忽略不用。
  • 出错时返回 -1,成功时返回 epoll 实例的文件描述符,当这个文件描述符不再需要时,应通过 close() 来关闭它。
#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 修改文件描述符 epfd 所指代的 epoll 实例的兴趣列表。

  • 成功时返回 0,出错时返回 -1。

  • fd 指明了要修改兴趣列表中的哪一个文件描述符的设置,它可以是管道、FIFO、套接字、POSIX 消息队列、inotify 实例、另一个 epoll 实例的文件描述符,但不能是普通文件或目录。

  • op 指明了要执行何种操作,可以为:

    • EPOLL_CTL_ADD:添加 fd 至兴趣列表中去,感兴趣的事件由 event 参数指定;兴趣列表中必须事先不存在 fd
    • EPOLL_CTL_MOD:修改 fd 的感兴趣事件为 event 中指定的值,fd 必须已存在于兴趣列表中。
    • EPOLL_CTL_DEL:从兴趣列表中移除 fd,忽略 event 参数;fd 必须已存在于兴趣列表中。
  • event 参数指定了感兴趣的事件:

    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 */
    };
    
    • epoll_event 中的 events 字段指定了要监听哪些事件,data 字段用于指定当事件就绪时回传给调用进程的信息。
    • 常用的事件如下:
      • EPOLLIN:读就绪;
      • EPOLLOUT:写就绪;
      • EPOLLRDHUP:流套接字的对端关闭或关闭了写端;
      • EPOLLERR:有错误发生,总是会监听此类事件,无需手动指定;
      • EPOLLHUP:出现挂断,总是会监听此类事件,无需手动指定;
      • EPOLLET:使用边缘触发;
      • EPOLLONESHOT:一次性检查,即完成通知后禁用检查;
#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 保持阻塞,直到有事件就绪或超时。
  • 出错时返回 -1,超时时返回 0,否则返回就绪的文件描述符个数。
  • events 是一个传入传出参数,其大小为 maxevents,调用返回时,其保存了所有已就绪的文件描述符及相应的就绪事件。
  • timeout 用于指定超时时间,以毫秒为单位,-1 表示一直阻塞,0 表示执行一次非阻塞查询。
  • 可以在一个线程中调用 epoll_ctl(),另一个线程中执行 epoll_wait()

3. 例子

#include <iostream>
#include <string>
#include <sys/epoll.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <thread>
#include <mutex>
#include <chrono>

static uint16_t Port = 6666;
static const char* IP = "127.0.0.1";
static std::string Msg("Hello, World!");
static constexpr size_t MSG_LEN = 13;
static std::mutex ioLock;

class IOManager {
public:
    IOManager(int listenFd=-1, int size=4);
    ~IOManager();

    void run();
    void add_read(int fd);
    void add_write(int fd);
    void del(int fd);

private:
    int m_epFd;
    int m_listenFd;
    int m_size;
    struct epoll_event* m_eventList;
};

IOManager::IOManager(int listenFd, int size): m_listenFd(listenFd), m_size(size), m_eventList(nullptr) {
    m_epFd = epoll_create(m_size);
    m_eventList = new struct epoll_event[m_size];

    if (m_listenFd >= 0) {
        add_read(m_listenFd);
    }
}

IOManager::~IOManager() {
    if (m_eventList) {
        delete [] m_eventList;
    }
    if (m_epFd >= 0) {
        close(m_epFd);
    }
}

void IOManager::add_read(int fd) {
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = fd;

    epoll_ctl(m_epFd, EPOLL_CTL_ADD, fd, &ev);
}

void IOManager::add_write(int fd) {
    struct epoll_event ev;
    ev.events = EPOLLOUT;
    ev.data.fd = fd;

    epoll_ctl(m_epFd, EPOLL_CTL_MOD, fd, &ev);
}

void IOManager::del(int fd) {
    epoll_ctl(m_epFd, EPOLL_CTL_DEL, fd, NULL);
}

void IOManager::run() {
    int nReady;
    char buf[MSG_LEN];
    int fd;
    uint32_t events;

    while (true) {
        nReady = epoll_wait(m_epFd, m_eventList, m_size, -1);
        if (nReady == -1) {
            perror("epoll_wait()");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < nReady; i++) {
            fd = m_eventList[i].data.fd;
            events = m_eventList[i].events;

            if (fd == m_listenFd) {
                int connFd = accept(m_listenFd, NULL, NULL);
                if (connFd == -1) {
                    perror("accept()");
                } else {
                    add_read(connFd);
                }
            } else if (events & EPOLLIN) {
                read(fd, buf, MSG_LEN);
                add_write(fd);
            } else if (events & EPOLLOUT) {
                write(fd, Msg.data(), MSG_LEN);
                del(fd);
                close(fd);
            }

            if (events & (EPOLLERR | EPOLLRDHUP | EPOLLHUP)) {
                del(fd);
                close(fd);
            }
        }
    }
}

void server() {
    int listenSock = socket(AF_INET, SOCK_STREAM, 0);
    int value = 1;
    setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int));

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_port = htons(Port);
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, IP, &addr.sin_addr);

    bind(listenSock, (const struct sockaddr*)&addr, sizeof(addr));
    listen(listenSock, 4);

    IOManager manager(listenSock);
    manager.run();
}

void client() {
    using namespace std::literals;

    std::this_thread::sleep_for(1s);

    int sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_port = htons(Port);
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, IP, &addr.sin_addr);

    connect(sock, (const struct sockaddr*)&addr, sizeof(addr));

    char buf[MSG_LEN+1] = {0};
    ssize_t n = write(sock, Msg.c_str(), MSG_LEN);
    read(sock, buf, n);

    std::lock_guard<std::mutex> lock(ioLock);
    std::cout << "client: " << buf << '\n';

    close(sock);
}

int main() {
    constexpr int N = 4;
    for (int i = 0; i < N; i++)
    {
        std::thread t(client);
        t.detach();
    }

    std::thread st(server);
    st.join();

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值