• Linux C++ 网络编程学习系列(4)——多路IO之epoll基础


    epoll实现多路IO

    1. 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll
    2. 源码说明:

    1. 概要

    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
    int epoll_wait(int epfd, struct epoll_event *events,
                   int maxevents, int timeout);
    
     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 */ //监听的事件,如EPOLLIN
        epoll_data_t data;      /* User data variable */ // 其他数据部分,这个是我们可以设定的,因为epoll_wait返回时会把这个结构体返回到events中,我们就可获取有信号的属性了
    };
    

    1.1 epoll_create

    epfd = epoll_create(FD_SETSIZE); //返回文件描述符epfd,是红黑树的根节点
    

    1.2 epoll_ctl

    //增加节点
    evt.data.fd = fd_client;//这里的联合体选择了int fd
    evt.events  = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd_client, &evt);
    //删除节点
    epoll_ctl(epfd, EPOLL_CTL_DEL, evts[i].data.fd, nullptr);
    

    增加节点: 是把fd_client文件描述符添加到epfd的红黑树中,同时附带自己的属性,即epoll_event类型的结构体(这个结构体在epoll_wait后会复制到events数组中,这样返回时就可以获取有信号的fd的属性了)
    删除节点: 从树中移除

    1.3 epoll_wait

    nselect = epoll_wait(epfd, evts, FD_SETSIZE, -1);
    

    epfd: 输入,待监听的红黑树
    evts: 输出,数组中的元素类型是epoll_event,当fd==n的文件描述符有信号时,会把fd的属性(详见epoll_ctl, 也是epoll_event类型)拷贝到evts中,这样后续我们就可以访问evts[i].data.xxxevts[i].events来获取我们之前设定的数据

    1.4 原理

    1. 树中每个节点是fd,每个fd映射着一个epoll_event类型的结构体,像属性一样(epoll_ctl添加节点时自定义的)
    2. epoll_wait监听各个节点,有信号的话,就把它的属性(epoll_event)拷贝到evts中,返回后,就可以通过evts[i].data.xxx得到映射的数据
    3. 如果联合体选择fd, 即evts[i].data.fd,我们就相当于直接获取了fd,可以直接read,write.
    4. 后面的epoll_libevent我们会用ptr指向自定义的结构体

    1.5 优势劣势

    一定先epoll实现,epoll是poll的改进版,有以下不同:

    1. 结果返回(判断是否有信号)
      • poll:需要轮询数组中的每个元素,看看是不是有信号,用fds[0].revents & POLLIN
      • epoll:一个数组evts专门存放有信号的节点的属性

    2. 核心代码

    #include "include/wrap.h"
    #include <arpa/inet.h>
    #include <ctype.h>
    #include <errno.h>
    #include <netinet/in.h>
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdnoreturn.h>
    #include <string.h>
    #include <sys/epoll.h>
    #include <sys/select.h>
    #include <sys/socket.h>
    #include <sys/types.h> /* See NOTES */
    #include <unistd.h>
    #include <wait.h>
    #define LOCALIP "127.0.0.1"
    #define PORT 6666
    
    void handler(char *in, char *out) {
        for (int i = 0; i < (int)strlen(out) + 1; ++i) {
            out[i] = toupper(in[i]);
        }
    }
    
    int workthread(const int &fd_client) {
        char recvbuf[2048] = {0};
        char sendbuf[2048] = {0};
        int  ret           = 0;
    
        ret = (int)Read(fd_client, recvbuf, 2048);
        if (ret <= 0) {
            printf("ret==0
    ");
            return ret;
        }
    
        handler(recvbuf, sendbuf);
    
        ret = (int)Write(fd_client, sendbuf, strlen(sendbuf) + 1);
        return ret;
    }
    
    void startsock(int &fd, struct sockaddr_in &addr, const char *ip,
                   const int port) {
        fd = Socket(AF_INET, SOCK_STREAM, 0);
        memset(&addr, 0, sizeof(addr));
        addr.sin_family      = AF_INET;
        addr.sin_addr.s_addr = inet_addr(ip);
        addr.sin_port        = htons(port);
    }
    int main() {
        int                fd_server = 0;
        int                fd_client = 0;
        int                ret       = 0;
        struct sockaddr_in sock_client;
        struct sockaddr_in sock_server;
        socklen_t          client_len = (socklen_t)sizeof(sock_client);
        int                opt        = 0;
        int                epfd       = 0;
        int                nselect    = 0;
        int                i          = 0;
        struct epoll_event evts[FD_SETSIZE];
        struct epoll_event evt;
        startsock(fd_server, sock_server, LOCALIP, PORT);
        opt = 1;
        Setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, &opt,
                   (socklen_t)sizeof(opt));
        Bind(fd_server, (struct sockaddr *)&sock_server, sizeof(sock_server));
        Listen(fd_server, 5);
        epfd = epoll_create(FD_SETSIZE);
        if (epfd == -1) { perror_exit("epoll create failed"); }
    
        evt.events  = EPOLLIN;
        evt.data.fd = fd_server;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd_server, &evt);
    
        while (true) {
            printf("epolling...
    ");
            nselect = epoll_wait(epfd, evts, FD_SETSIZE, -1);
            printf("get %d select
    ", nselect);
            for (i = 0; i < nselect; ++i) {
                if (!(evts[i].events && EPOLLIN)) continue;
                if (evts[i].data.fd == fd_server) {//直接拿到fd_server
                    fd_client = Accept(fd_server, (struct sockaddr *)&sock_client,
                                       &client_len);
                    printf("accept: %s: %d
    ", inet_ntoa(sock_client.sin_addr),
                           ntohs(sock_client.sin_port));
                    evt.data.fd = fd_client;
                    evt.events  = EPOLLIN;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, fd_client, &evt);
                } else {
                    ret = workthread(evts[i].data.fd);//通过data.fd获取fd
                    if (ret <= 0) {
                        Close(evts[i].data.fd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, evts[i].data.fd, nullptr);
                    }
                }
            }
    	}
        Close(fd_server);
    }
    

    3. 参考网址

    1. https://www.bilibili.com/video/av53016117
    2. EPOLLOUT的用处:https://www.zhihu.com/question/22840801 https://github.com/yedf/handy
      write: 不停地写数据,因为EPOLLOUT是只要缓冲区未满,就会有信号
  • 相关阅读:
    多线程之旅:避免死锁——简单的锁分级(锁排序)
    和我一起来学iOS(三)UIView及其子类(上)
    谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用
    和我一起来学iOS(二)iOS中的一些约定、模式与三种回调机制
    多线程之旅六——异步编程模式,自己实现IAsyncResult
    详解JavaScript中的函数与闭包
    和我一起来学iOS(一)ObjectC的语法
    浅谈SQL SERVER中的物理联接算法
    多线程之旅之三——Windows内核对象同步机制
    深入 聚集索引与非聚集索引(一)
  • 原文地址:https://www.cnblogs.com/whuwzp/p/linux_cpp_network_4_epoll_EPOLLIN.html
Copyright © 2020-2023  润新知