zoukankan      html  css  js  c++  java
  • epoll使用具体解释(精髓)

    epoll - I/O event notification facility

    在linux的网络编程中,非常长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
    相比于select,epoll最大的优点在于它不会随着监听fd数目的增长而减少效率。由于在内核中的select实现中,它是採用轮询来处理的,轮询的fd数目越多,自然耗时越多。而且,在linux/posix_types.h头文件有这种声明:
    #define __FD_SETSIZE    1024
    表示select最多同一时候监听1024个fd,当然,能够通过改动头文件再重编译内核来扩大这个数目,但这似乎并不治本。

    epoll的接口非常easy,一共就三个函数:
    1. int epoll_create(int size);
    创建一个epoll的句柄,size用来告诉内核这个监听的数目一共同拥有多大。这个參数不同于select()中的第一个參数,给出最大监听的fd+1的值。须要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下假设查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。


    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    epoll的事件注冊函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注冊要监听的事件类型。第一个參数是epoll_create()的返回值,第二个參数表示动作,用三个宏来表示:
    EPOLL_CTL_ADD:注冊新的fd到epfd中;
    EPOLL_CTL_MOD:改动已经注冊的fd的监听事件;
    EPOLL_CTL_DEL:从epfd中删除一个fd;
    第三个參数是须要监听的fd,第四个參数是告诉内核须要监听什么事,struct epoll_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 */
    };

    events能够是以下几个宏的集合:
    EPOLLIN :表示相应的文件描写叙述符能够读(包含对端SOCKET正常关闭);
    EPOLLOUT:表示相应的文件描写叙述符能够写;
    EPOLLPRI:表示相应的文件描写叙述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示相应的文件描写叙述符错误发生;
    EPOLLHUP:表示相应的文件描写叙述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:仅仅监听一次事件,当监听完这次事件之后,假设还须要继续监听这个socket的话,须要再次把这个socket加入到EPOLL队列里


    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    等待事件的产生,相似于select()调用。參数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,參数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久堵塞)。该函数返回须要处理的事件数目,如返回0表示已超时。


    4、关于ET、LT两种工作模式:
    能够得出这种结论:
    ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包含缓冲区中还有未处理的数据,也就是说,假设要採用ET模式,须要一直read/write直到出错为止,非常多人反映为什么採用ET模式仅仅接收了一部分数据就再也得不到通知了,大多由于这样;而LT模式是仅仅要有数据没有处理就会一直通知下去的.


    那么到底怎样来使用epoll呢?事实上非常easy。
    通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将能够大大的提高你的网络server的支持人数。

    首先通过create_epoll(int maxfds)来创建一个epoll的句柄,当中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的全部操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

    之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询全部的网络接口,看哪一个能够读,哪一个能够写了。主要的语法为:
    nfds = epoll_wait(kdpfd, events, maxevents, -1);
    当中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存全部的读写事件。max_events是当前须要监听的全部socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示立即返回,为-1的时候表示一直等下去,直到有事件范围,为随意正整数的时候表示等这么长的时间,假设一直没有事件,则范围。一般假设网络主循环是单独的线程的话,能够用-1来等,这样能够保证一些效率,假设是和主逻辑在同一个线程的话,则能够用0来保证主循环的效率。

    epoll_wait范围之后应该是一个循环,遍利全部的事件。

    差点儿全部的epoll程序都使用以下的框架:

        for( ; ; )
        {
            nfds = epoll_wait(epfd,events,20,500);
            for(i=0;i<nfds;++i)
            {
                if(events[i].data.fd==listenfd) //有新的连接
                {
                    connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
                    ev.data.fd=connfd;
                    ev.events=EPOLLIN|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd加入到epoll的监听队列中
                }
                else if( events[i].events&EPOLLIN ) //接收到数据,读socket
                {
                    n = read(sockfd, line, MAXLINE)) < 0    //读
                    ev.data.ptr = md;     //md为自己定义类型,加入数据
                    ev.events=EPOLLOUT|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//改动标识符,等待下一个循环时发送数据,异步处理的精髓
                }
                else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
                {
                    struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据
                    sockfd = md->fd;
                    send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据
                    ev.data.fd=sockfd;
                    ev.events=EPOLLIN|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //改动标识符,等待下一个循环时接收数据
                }
                else
                {
                    //其它的处理
                }
            }
        }



    以下给出一个完整的server端样例:


    #include <iostream>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <errno.h>

    using namespace std;

    #define MAXLINE 5
    #define OPEN_MAX 100
    #define LISTENQ 20
    #define SERV_PORT 5000
    #define INFTIM 1000

    void setnonblocking(int sock)
    {
        int opts;
        opts=fcntl(sock,F_GETFL);
        if(opts<0)
        {
            perror("fcntl(sock,GETFL)");
            exit(1);
        }
        opts = opts|O_NONBLOCK;
        if(fcntl(sock,F_SETFL,opts)<0)
        {
            perror("fcntl(sock,SETFL,opts)");
            exit(1);
        }
    }

    int main(int argc, char* argv[])
    {
        int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
        ssize_t n;
        char line[MAXLINE];
        socklen_t clilen;


        if ( 2 == argc )
        {
            if( (portnumber = atoi(argv[1])) < 0 )
            {
                fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
                return 1;
            }
        }
        else
        {
            fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
            return 1;
        }



        //声明epoll_event结构体的变量,ev用于注冊事件,数组用于回传要处理的事件

        struct epoll_event ev,events[20];
        //生成用于处理accept的epoll专用的文件描写叙述符

        epfd=epoll_create(256);
        struct sockaddr_in clientaddr;
        struct sockaddr_in serveraddr;
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
        //把socket设置为非堵塞方式

        //setnonblocking(listenfd);

        //设置与要处理的事件相关的文件描写叙述符

        ev.data.fd=listenfd;
        //设置要处理的事件类型

        ev.events=EPOLLIN|EPOLLET;
        //ev.events=EPOLLIN;

        //注冊epoll事件

        epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
        bzero(&serveraddr, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        char *local_addr="127.0.0.1";
        inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);

        serveraddr.sin_port=htons(portnumber);
        bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
        listen(listenfd, LISTENQ);
        maxi = 0;
        for ( ; ; ) {
            //等待epoll事件的发生

            nfds=epoll_wait(epfd,events,20,500);
            //处理所发生的全部事件

            for(i=0;i<nfds;++i)
            {
                if(events[i].data.fd==listenfd)//假设新监測到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。

                {
                    connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                    if(connfd<0){
                        perror("connfd<0");
                        exit(1);
                    }
                    //setnonblocking(connfd);

                    char *str = inet_ntoa(clientaddr.sin_addr);
                    cout << "accapt a connection from " << str << endl;
                    //设置用于读操作的文件描写叙述符

                    ev.data.fd=connfd;
                    //设置用于注測的读操作事件

                    ev.events=EPOLLIN|EPOLLET;
                    //ev.events=EPOLLIN;

                    //注冊ev

                    epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
                }
                else if(events[i].events&EPOLLIN)//假设是已经连接的用户,而且收到数据,那么进行读入。

                {
                    cout << "EPOLLIN" << endl;
                    if ( (sockfd = events[i].data.fd) < 0)
                        continue;
                    if ( (n = read(sockfd, line, MAXLINE)) < 0) {
                        if (errno == ECONNRESET) {
                            close(sockfd);
                            events[i].data.fd = -1;
                        } else
                            std::cout<<"readline error"<<std::endl;
                    } else if (n == 0) {
                        close(sockfd);
                        events[i].data.fd = -1;
                    }
                    line[n] = '/0';
                    cout << "read " << line << endl;
                    //设置用于写操作的文件描写叙述符

                    ev.data.fd=sockfd;
                    //设置用于注測的写操作事件

                    ev.events=EPOLLOUT|EPOLLET;
                    //改动sockfd上要处理的事件为EPOLLOUT

                    //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);

                }
                else if(events[i].events&EPOLLOUT) // 假设有数据发送

                {
                    sockfd = events[i].data.fd;
                    write(sockfd, line, n);
                    //设置用于读操作的文件描写叙述符

                    ev.data.fd=sockfd;
                    //设置用于注測的读操作事件

                    ev.events=EPOLLIN|EPOLLET;
                    //改动sockfd上要处理的事件为EPOLIN

                    epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
                }
            }
        }
        return 0;
    }


    client直接连接到这个server就好了。。
  • 相关阅读:
    leetcode 190 Reverse Bits
    vs2010 单文档MFC 通过加载位图文件作为客户区背景
    leetcode 198 House Robber
    记忆化搜索(DP+DFS) URAL 1183 Brackets Sequence
    逆序数2 HDOJ 1394 Minimum Inversion Number
    矩阵连乘积 ZOJ 1276 Optimal Array Multiplication Sequence
    递推DP URAL 1586 Threeprime Numbers
    递推DP URAL 1167 Bicolored Horses
    递推DP URAL 1017 Staircases
    01背包 URAL 1073 Square Country
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/4503471.html
Copyright © 2011-2022 走看看