• 通俗易懂的epoll


    理解epoll工作原理

    • 每一个epoll对象都有eventepoll结构体
    • epoll底层是一颗红黑数来管理文件描述符中的事件。
    • 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法
    • 当事件发生时,回调方法会拷贝一份该节点到一个队列中,该队列的用双链表实现的。
    • 在epoll中没一个事件都会建立节点(epitem结构体)

    在这里插入图片描述
    例子:我们在编写套接字编程的代码时,要把监听套接字放在epoll中来,让epoll帮我们来进行等待连接到来,连接到来事件叫做读事件。
    这时候我们通过调用函数把监听套接字放入到红黑树中,Linux内核会对每一个事件对象创建一个epintm结构体。
    判断也没有事件只需要通过rdllist是否为空。

    struct epitem{ 
     struct rb_node rbn;//红黑树节点 
     struct list_head rdllink;//双向链表节点 
     struct epoll_filefd ffd; //事件句柄信息 
     struct eventpoll *ep; //指向其所属的eventpoll对象 
     struct epoll_event event; //期待发生的事件类型 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    epoll的两种工作模式

    LT:水平触发,不断的提醒你有事件到来,直到你把事件全部执行完
    
    ET:边缘触发:只提醒你一次有事件到来,如果不执行,就要等下一次事件到来。
    
    ET模式下:要采用非阻塞的方式进行读操作。且要不断的读,直到读完。
    如果不采用非阻塞方式读取,则可能会阻塞住。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如何使用epoll

    创建句柄
    int epoll_create(int size);
    返回一个整数,是一个文件描述符。
    使用:int epfd=epoll_create(256);
    
    
    添加事件到红黑树中
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    op:是一个宏
    EPOLL_CTL_ADD:添加
    EPOLL_CTL_MOD:修改
    EPOLL_CTL_DEL:删除
    
    struct epoll_event{
    	uint32_t     events;     //事件
        epoll_data_t data;        /* User data variable */
    }
    typedef union epoll_data {
               void        *ptr;
               int          fd;	//文件描述符
               uint32_t     u32;
               uint64_t     u64;
    } epoll_data_t;
    一般events填EPOLLIN(读事件)、EPOLLOUT(写事件)。
    返回值:成功返回0,失败返回-1
    
    //拿出事件
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    timeout:轮询检查的事件
    返回值:成功返回事件到达的数量。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    使用:
    	int epfd=epoll_create(256);
    	
    	struct epoll_event item;
    	item.data.fd=sock;     //添加套接字
    	item.events=EPOLLIN   //只关心读时间
    	epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&item);//添加事件到红黑树
    	
    	struct events eve[64];
    	int num=epoll_wait(epfd,eve,64,1000);
    	//有事件到来,会把事件的节点拷贝到eve数组中,
    	//我们只需要遍历数组就可以进行读或者写。
    	for(int i=0;i<num;i++){
    		if(eve[i].events & EPOLLIN){
    			int sock=eve[i].data.fd;
    			开始进行读操作(进行读操作时要区分是连接到来,还只是进行读取)
    		}
    		else if(eve[i].event& EPOLLOUT){
    			int sock=eve[i].data.fd;
    			开始进行写操作
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    epoll的优点

    • 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
    • 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
    • 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,
    • epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
    • 没有数量限制: 文件描述符数目无上限

    使用epoll实现一个服务器

    									sock.hpp//创建套接字
    
    • 1
    #pragma once
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include"log.hpp"
    
    #define LONG 5
    
    using namespace std;
    class Socket{
        public:
            static void Sock(int& listen_sock)
            {
                listen_sock=socket(AF_INET,SOCK_STREAM,0);
                if(listen_sock<0){
                    LOG(ERROR,"socket error...");
                    exit(1);
                }
                LOG(INFO,"socket success...");
            }
            static void Bind(int listen_sock,int port)
            {
                struct sockaddr_in own;
                memset(&own,0,sizeof(own));
                own.sin_family=AF_INET;
                own.sin_port=htons(port);
                own.sin_addr.s_addr=INADDR_ANY;
                if(bind(listen_sock,(struct sockaddr*)&own,sizeof(own))<0){
                    LOG(error,"bind error...");
                    exit(2);
                }
                LOG(INFO,"bind sucess...");
            }
            static void Listen(int listen_sock)
            {
                if(listen(listen_sock,LONG)<0){
                    LOG(error,"listen error...");
                    exit(3);
                }
                LOG(INFO,"listen succsee...");
            }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    									reactor.hpp
    
    • 1
    #pragma once
    #include
    #include
    #include
    #include
    #include
    #include"log.hpp"
    using namespace std;
    #define MAX_NUM 64
    
    class Reactor;
    class EventItem;
    
    typedef int(*callback_t)(EventItem *);
    
    每一个事件都要对应一个这个结构体
    class EventItem
    {
        public:
            //定义回调函数
            callback_t recv_handler;
            callback_t send_handler;
            //sock
            int sock;
            //缓存
            string inbuffer;
            string outbuffer;
            //回指向Reactor
            Reactor* R;
        public:
            EventItem():sock(0),R(nullptr),recv_handler(nullptr),send_handler(nullptr)
            {}
    		
    		//注册回调函数
            void MakeCallBack(callback_t _recv,callback_t _send)
            {
                recv_handler=_recv;
                send_handler=_send;
            }
    
            ~EventItem(){}
    };
    
    class Reactor
    {
        private:
        //epoll句柄
        int epfd;
        //使用哈希容器来一一对应
        unordered_map<int,EventItem> mp;
    
        public:
        
        Reactor(){}
        void InitReactor()
        {
            epfd=epoll_create(256);
            if(epfd<0){
                LOG(ERROR,"epoll_create error...");
                exit(5);
            }
            LOG(INFO,"epoll_create success..."+to_string(epfd));
        }
    	
    	添加事件到红黑树中
        void AddToEpoll(int sock,uint32_t ev,const EventItem& item)
        {
            struct epoll_event event;
            event.events=0;
            event.events|=ev;
            event.data.fd=sock;
    
            string s=to_string(sock);
            if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&event)<0){
                LOG(ERROR,"epoll_ctl add error...sock:"+s);
            }
            else{
                mp.insert({sock,item});
                LOG(INFO,"epoll_ctl add success...sock:"+s);
            }
    
        }
    
    	删除红黑树中的事件
        void RevokeEpoll(int sock)
        {
            if(epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr)<0){
                string s=to_string(sock);
                LOG(ERROR,"epoll_ctl del error...sock:"+s);
            }
            删除哈希中的映射
            mp.erase(sock);
        }
        void Assignment(int timeout)
        {
            struct epoll_event revs[MAX_NUM];
            int num=epoll_wait(epfd,revs,MAX_NUM,timeout);
            
            事件到来轮询recv中的节点
            for(int i=0;i<num;i++){
                int sock=revs[i].data.fd;
                uint32_t mask=revs[i].events;
                if(mask & EPOLLIN){
                    if(mp[sock].recv_handler)
                        mp[sock].recv_handler(&mp[sock]);调用回调函数进行读
                }
                else if(mask & EPOLLOUT){
                    if(mp[sock].send_handler)
                        mp[sock].send_handler(&mp[sock]);调用回调函数进行写
                }
            }
        }
        ~Reactor()
        {
            if(epfd>=0)
                close(epfd);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    									insertface.hpp
    
    • 1
    #pragma once
    #include
    #include
    #include
    #include
    #include"reactor.hpp"
    #include"util.hpp"
    
    
    int recver(EventItem* eve);
    
    连接到来
    int accepter(EventItem* eve)
    {
        struct sockaddr_in oth;
        socklen_t len=sizeof(oth);
        int sock=accept(eve->sock,(struct sockaddr*)&oth,&len);
        if(sock<0){
    
        }
        else{
            SelNonBlock(sock);
    
            EventItem item;
            item.sock=sock;
            item.R=eve->R;
    
            item.MakeCallBack(recver,nullptr);
            Reactor* ptr=eve->R;
            ptr->AddToEpoll(sock,EPOLLIN|EPOLLET,item);
        }
    }
    
    只写了连接到来的接口,读没有写
    int recver(EventItem* eve)
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    									log.hpp//日志,方便知道走到哪里了
    
    • 1
    #pragma once
    #include
    #include
    #include
    
    using namespace std;
    
    #define INFO 1
    #define ERROR 2
    
    采用宏来调用,具有可读性
    #define LOG(str1,str2) log(#str1,str2,__FILE__,__LINE__)
    
    void log(string str1,string str2,string str3,int line)
    {
        cout<<"["<<str1<<"]"<<"["<<time(nullptr)<<"]"<<"["<<str2<<"]"<<"["<<str3<<"]"<<"["<<line<<"]"<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    									util.hpp
    
    • 1
    #pragma once
    #include
    #include
    #include
    
    设置非阻塞
    void SelNonBlock(int sock)
    {
         int fl = fcntl(sock, F_GETFL);
            fcntl(sock, F_SETFL, fl | O_NONBLOCK);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    									server.cc
    
    • 1
    #include
    #include
    #include"sock.hpp"
    #include"reactor.hpp"
    #include"insertface.hpp"
    #include"util.hpp"
    int main(int argc,char* argv[])
    {
        if(argc!=2){
            exit(4);
        }
        int port=atoi(argv[1]);
        int listen_sock=-1;
        
        创建套接字
        Socket::Sock(listen_sock);
        Socket::Bind(listen_sock,port);
        Socket::Listen(listen_sock);
    	
    	创建Reactor,并初始化
        Reactor Re;
        Re.InitReactor();
    	
    	创建监听套接字对应的结构
        EventItem item;
        item.sock=listen_sock;
        item.R=&Re;
    	
    	把监听套接字设置成非阻塞
        SelNonBlock(listen_sock);
        
    	注册回调函数,我这里把读注册了accept,没有注册写
        item.MakeCallBack(accepter,nullptr);
    	
    	添加到红黑树中,采用ET模式
        Re.AddToEpoll(listen_sock,EPOLLIN|EPOLLET,item);
    
        int timeout=1000;
    
    	一直调用检查事件是否到来
        while(true){
            Re.Assignment(timeout);
        }
    }
    
    
    整个的精髓是,把 epoll 和	读写分开了,采用了回调函数的方法,只不要管reactor.hpp,只需要在insertface.hpp中添加函数就行。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
  • 相关阅读:
    Vue-loader--使用/教程
    C语言订餐管理系统
    GPIO定义
    什么是分库分表-03
    OneDrive打不开了,怎么办?使用管理员身份也无效,分享解决办法如下
    学习笔记---更进一步的双向链表专题~~
    Nginx查找耗时的接口
    月光宝盒(vivo流量录制回放平台)正式对外开源
    Unity Spine 指定导入新Spine动画的默认材质
    代码随想录day39 || 动态规划 || 不同路径
  • 原文地址:https://blog.csdn.net/qq_53505979/article/details/126108495