IO多路复用之epoll

IO多路复用之epoll

int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
注意:通过man epoll_create可以看到Linux 2.6.8之后size这个参数就没用了,填大于0的值即可在这里插入图片描述

int epoll_create1(int flags);

flags可以设置为0 或者EPOLL_CLOEXEC,为0时函数表现与epoll_create一致
相对于epoll_create,epoll_create1的允许你指定标志EPOLL_CLOEXEC,可以在某些情况下解决掉一些问题 即在fock后关闭子进程中无用文件描述符的问题
详见:https://blog.csdn.net/ChrisNiu1984/article/details/7050663

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数epfd是epoll_create()的返回值
第二个参数op表示动作,有如下选项:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数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可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待事件的产生,类似于select()调用。
第一个参数epfd是epoll_create()的返回值
第二个参数events是需要处理的事件
第三个参数maxevents是本次可以返回的最大事件数目,通常与预分配的events数组的大小是相等的
第四个参数timeout是超时时间(毫秒,0会立即返回,-1永久阻塞)

工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式(水平触发):当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式(边缘触发):当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

用epoll实现的TCP通信例子:
tcpserver.cpp

#include <iostream>
#include <string.h>
#include <unistd.h>
#include <map>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>

const int MAXEVENTS = 2048;
#define CHECK_RETURN_EXIT(ret) \
    if (ret != 0) { 		   \
        exit(-1);              \
    }

class TCPServer {
public:
    TCPServer() : sockFd(-1) {}

    int Socket()
    {
        sockFd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockFd == -1) {
            perror("socket");
            return -1;
        }
		return 0;
    }

    int Bind()
    {
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        // addr.sin_port = htons("127.0.0.1");
        addr.sin_port = inet_addr("127.0.0.1"); // 将ip地址转换成网络字节序
        addr.sin_port = htons(8081); // 将端口转换成网络字节序

        if (bind(sockFd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
            perror("bind");
            return -1;
        }
		return 0;
    }

    int Listen()
    {
        if (listen(sockFd, SOMAXCONN) != 0) {
            perror("listen");
            return -1;
        }
		return 0;
    }

    std::pair<int, struct sockaddr_in> Accept()
    {
        struct sockaddr_in clientAddr;
        int len = sizeof(clientAddr);
        // int newSocket = accept(sockFd, NULL, NULL); 不需要 clientAddr可以填NULL
        int newSocket = accept(sockFd, (struct sockaddr*)&clientAddr, (socklen_t*)&len);
        if (newSocket == -1) {
            perror("accept");
        }
        return std::make_pair(newSocket, clientAddr);
    }

    int GetSockFd() { return sockFd; }

    // 设置被绑定的端口在断连后可以立即重用
    void SetSocketReuseAddr()
    {
        bool bReuseaddr = true;
        setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(bool));
    }
private:
    int sockFd;
};

int EpollAdd(int epfd, int fd)
{
    struct epoll_event event;
    bzero(&event, sizeof(event));
    event.events = EPOLLIN; // 监控读事件
    event.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
        perror("epoll_add");
        return -1;
    }
    return 0;
}

int EpollDel(int epfd, int fd)
{
    struct epoll_event event;
    bzero(&event, sizeof(event));
    event.events = EPOLLIN;
    event.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event) == -1) {
        perror("epoll_del");
        return -1;
    }
    return 0;
}

int main() {
    TCPServer tcpServer;
    CHECK_RETURN_EXIT(tcpServer.Socket());
    tcpServer.SetSocketReuseAddr();
    CHECK_RETURN_EXIT(tcpServer.Bind());
    CHECK_RETURN_EXIT(tcpServer.Listen());

    int sockFd = tcpServer.GetSockFd();
    std::map<int, struct sockaddr_in> socketMap{};
    char buf[1024] = {};
    int ret;

    // 通过epoll监控socket描述符和输入缓冲区
    int epfd = epoll_create(1);
    EpollAdd(epfd, STDIN_FILENO);
    EpollAdd(epfd, sockFd);
    struct epoll_event evs[MAXEVENTS];
    for (;;) {
        bzero(evs, sizeof(evs));
        int nret = epoll_wait(epfd, evs, MAXEVENTS, -1);
        // epoll轮训到集合中至少有一个描述符可读
        for (int i = 0; i < nret; i++) {
            // 新连接到来
            if (sockFd == evs[i].data.fd) {
                std::pair<int, struct sockaddr_in> tmpNewSockPair(tcpServer.Accept());
                if (tmpNewSockPair.first != -1) {
                    EpollAdd(epfd, tmpNewSockPair.first);
                    socketMap.insert(tmpNewSockPair);
                    continue;
                }
            }
            // 老连接可读
            auto itr = socketMap.find(evs[i].data.fd);
            if (itr != socketMap.end()) {
                memset(buf, 0, sizeof(buf));
                ret = recv(itr->first, buf, sizeof(buf), 0);
                if (ret == -1) {
                    std::cout << "recv from client ip:" << inet_ntoa(itr->second.sin_addr) << " port:" << ntohs(itr->second.sin_port) << " failed" << std::endl;
                    close(sockFd);
                    for (auto &cit : socketMap) {
                        close(cit.first);
                    }
                    return -1;
                } else if (ret == 0) {
                    // 对端断开
                    std::cout << "recv from client ip:" << inet_ntoa(itr->second.sin_addr) << " port:" << ntohs(itr->second.sin_port) << " disconnect" << std::endl;
                    socketMap.erase(itr);
                    EpollDel(epfd, itr->first);
                    close(itr->first);
                    break;
                }
                std::cout << "recv from client ip:" << inet_ntoa(itr->second.sin_addr) << " port:" << ntohs(itr->second.sin_port) << " message: " << buf;
            }
            // 标准输入可读,将输入的信息发送给对端
            if (STDIN_FILENO == evs[i].data.fd) {
                memset(buf, 0, sizeof(buf));
                read(STDIN_FILENO, buf, sizeof(buf));
                for (auto &cit : socketMap) {
                    ret = send(cit.first, buf, sizeof(buf) - 1, 0);
                }
            }
        }
    }

    close(sockFd);
    for (auto &cit : socketMap) {
        close(cit.first);
    }

    return 0;
}

tcpclient.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <sys/epoll.h>
#define ERR_EXIT(m) \
    do { \
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)

const int MAXEVENTS = 2048;
int EpollAdd(int epfd, int fd)
{
    struct epoll_event event;
    bzero(&event, sizeof(event));
    event.events = EPOLLIN; // 监控读事件
    event.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
        perror("epoll_add");
        return -1;
    }
    return 0;
}

int EpollDel(int epfd, int fd)
{
    struct epoll_event event;
    bzero(&event, sizeof(event));
    event.events = EPOLLIN;
    event.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event) == -1) {
        perror("epoll_del");
        return -1;
    }
    return 0;
}

int do_service(int sockfd)
{
    int epfd = epoll_create(1);
    EpollAdd(epfd, STDIN_FILENO);
    EpollAdd(epfd, sockfd);
    struct epoll_event evs[MAXEVENTS];

    char buf[1024] = {};
    for (;;) {
        bzero(evs, sizeof(evs));
        int nret = epoll_wait(epfd, evs, MAXEVENTS, -1);
        // epoll轮训到集合中至少有一个描述符可读
        for (int i = 0; i < nret; i++) {
            // sockfd可读
            if (sockfd == evs[i].data.fd) {
                // bzero(buf, sizeof(buf));
                memset(buf, 0, sizeof(buf));
                int ret = recv(sockfd, buf, sizeof(buf), 0);
                if (ret == -1) {
                    std::cout << "recv from server failed" << std::endl;
                    return -1;
                } else if (ret == 0) {
                    std::cout << "recv from server disconnect" << std::endl;
                    return -1;
                }
                std::cout << "recv from server message: " << buf << std::endl;
            }
            // 标准输入可读,将输入的信息发送给对端
            if (STDIN_FILENO == evs[i].data.fd) {
                memset(buf, 0, sizeof(buf));
                read(STDIN_FILENO, buf, sizeof(buf));
                int ret = send(sockfd, buf, sizeof(buf) - 1, 0);
            }
        }
    }
}

int main(int argc, const char *argv[])
{
    int peerfd = socket(PF_INET, SOCK_STREAM, 0);
    if(peerfd == -1) {
        ERR_EXIT("socket");
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8081);
    socklen_t len = sizeof(addr);
    if(connect(peerfd, (struct sockaddr*)&addr, len) == -1) {
        ERR_EXIT("Connect");
    }

    do_service(peerfd);
    close(peerfd);

    return 0;
}

epoll源码解析 https://blog.csdn.net/weixin_43705457/article/details/104522820
select和epoll区别 详见 https://baijiahao.baidu.com/s?id=1641172494287388070&wfr=spider&for=pc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值