• Server Develop (六) Linux epoll总结


     Linux  epoll

      epoll是Kernel 2.6后新加入的事件机制,在高并发条件下,远优于select。epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明: 

    #define __FD_SETSIZE    1024 //select最多同时监听1024个fd

      当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。

      所以在Nginx中采用了epoll来实现其高并发特性。 

    工作方式

      LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的selectpoll都是这种模型的代表。

      ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

      区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。

    主要的数据结构

       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 event */
             epoll_data_t data;      /* User data variable */
    };

      events表示感兴趣的事件和被触发的事件,可能的取值为:

    EPOLLIN 对应的文件描述符可以读
    EPOLLOUT 对应的文件描述符可以写
    EPOLLPRI 对应的文件描述符有紧急的数可读
    EPOLLERR 对应的文件描述符发生错误
    EPOLLHUP 对应的文件描述符被挂断
    EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
    EPOLLONESHOT 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里 

     操作函数

        epoll的接口非常简单,用三个相关函数来创建epoll句柄、注册epoll事件以及等待事件的发生

      创建epoll句柄:

    int epoll_create(int size); 
    //size表示内核需要监听的数目
    //return : epoll文件描述符

      需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值(文件标识符),在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。 

       epoll事件注册函数:

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    
    //epfd是epoll_create()的返回值
    //op表示动作
    /* op可被表示为:
        EPOLL_CTL_ADD:注册新的fd到epfd中; 
        EPOLL_CTL_MOD:修改已经注册的fd的监听事件; 
        EPOLL_CTL_DEL:从epfd中删除一个fd; 
    */
    //fd是需要监听的fd
    //event是内核需要监听的事件

      等待事件发生函数:

    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
    //epfd是函数返回值
    //events是内核监听事件的集合
    //maxevents是epoll_wait可以处理的连接事件的最大限度值
    //timeout是超时时间

    //返回值:请求数

    epoll工作流程

       首先,需要调用epoll_create创建epoll,此后我们就可以进行socket/bind/listen,然后调用epoll_ctl进行注册。接下来,就可以通过一个while(1)循环调用epoll_wait来等待事件的发生,然后循环查看接收到的事件并进行处理。如果事件是sever的socketfd我们就要进行accept,并且把接收到client的socketfd加入到要监听的事件中。如果在监听过程中,需要修改操作方式(读/写),可以调用epoll_ctl来重新修改。如果监听到某一个客户端关闭,那么我就需要再次调用epoll_ctl把它从epoll监听事件中删除。

     

     实例

    #include <stdio.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <sys/epoll.h>
    #include <netinet/in.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <errno.h>
    
    void setnonblocking(int sockfd)
    {
        int opts;
        opts = fcntl(sockfd, F_GETFL);
        if(opts < 0){
            perror("fcntl1 error!
    ");
            exit(1);
        }
        opts = opts | O_NONBLOCK;
        if(fcntl(sockfd, F_SETFL, opts) < 0){
            perror("fcntl2 error!
    ");
            exit(1);
        }
    }
    
    int main()
    {
        int fd;
        int on;
        int rs;
        int len;
        int conn;
        char buffer[100];
        int flag1, flag2;
        struct sockaddr_in serv_addr, clt_addr;
        struct timeval timeout;
    
        int i;
        int nfds;
        int epfd;
        int newfd;
    
        struct epoll_event ev;
        struct epoll_event events[20];
    
        fd = socket(AF_INET, SOCK_STREAM, 0);
    
        on = 1;
        setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
        timeout.tv_sec  = 5;
        timeout.tv_usec = 0;
        setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
    
        bzero(&serv_addr, sizeof(struct sockaddr_in));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port   = htons(9090);
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    
        rs = bind(fd, (struct sockaddr*)(&serv_addr), sizeof(struct sockaddr));
        if(rs < 0){
            perror("");
            close(fd);
            return -1;
        }
    
        setnonblocking(fd);
    
        epfd = epoll_create(100);
    
        ev.data.fd = fd;
        ev.events = EPOLLIN|EPOLLET;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    
        rs = listen(fd, 5);
        if(rs < 0){
            perror("");
            close(fd);
            return -1;
        }
    
        len = sizeof(struct sockaddr);
        
        for(;;){
            nfds = epoll_wait(epfd, events, 20, 500);
    
            for(i = 0; i < nfds; ++i){
                if(events[i].data.fd == fd){
                    conn = accept(fd, (struct sockaddr*)(&clt_addr), (unsigned int*)(&len));
                    
                    setnonblocking(conn);
    
                    ev.data.fd = conn;
                    ev.events = EPOLLIN|EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, conn, &ev);
                }
                else if(events[i].events & EPOLLIN){
                    if((newfd = events[i].data.fd) < 0)
                        continue;
                    bzero(buffer, sizeof(buffer));
                    flag1 = recv(newfd, buffer, 100, 0);
                    printf("recv: %s
    ", buffer);
                    printf("recv return: %d
    ", flag1);
    
                    ev.data.fd = newfd;
                    ev.events = EPOLLOUT|EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev);
                }
                else if(events[i].events & EPOLLOUT){
                    if((newfd = events[i].data.fd) < 0)
                        continue;
    
                    bzero(buffer, sizeof(buffer));
                    strcpy(buffer, "ACK");
                    flag2 = send(newfd, buffer, sizeof(buffer), 0);
                    printf("recv return: %d
    
    ", flag2);    
    
                    ev.data.fd = newfd;    
                    ev.events = EPOLLIN|EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev);
                }
            }
        }
        close(fd);
    
        return 0;
    }

    参考

        

            http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html

         http://hi.baidu.com/jingweiyoung/item/ae9fc81714be67dbbf9042b9

         http://www.linuxidc.com/Linux/2011-04/35156p3.htm

       http://www.cppblog.com/converse/archive/2008/04/29/48482.html

  • 相关阅读:
    用上CommonMark.NET,.NET平台终于有了好用的markdown引擎团队
    上周热点回顾(9.21-9.27)团队
    云计算之路-阿里云上:13:43-13:44之间RDS故障影响了全站的正常访问团队
    CentOS6.5下如何正确下载、安装Intellij IDEA、Scala、Scala-intellij-bin插件、Scala IDE for Eclipse助推大数据开发(图文详解)
    spark-2.2.0-bin-hadoop2.6和spark-1.6.1-bin-hadoop2.6发行包自带案例全面详解(java、python、r和scala)之Basic包下的JavaPageRank.java(图文详解)
    spark-2.2.0-bin-hadoop2.6和spark-1.6.1-bin-hadoop2.6发行包自带案例全面详解(java、python、r和scala)之Basic包下的SparkPageRank.scala(图文详解)
    spark-2.2.0-bin-hadoop2.6和spark-1.6.1-bin-hadoop2.6发行包自带案例全面详解(java、python、r和scala)之Basic包下的JavaTC.java(图文详解)
    spark-2.2.0-bin-hadoop2.6和spark-1.6.1-bin-hadoop2.6发行包自带案例全面详解(java、python、r和scala)之Basic包下的SparkTC.scala(图文详解)
    Spark Mllib里数据集如何取前M行(图文详解)
    Spark Mllib里如何将数值特征字段用StandardScaler进行标准化(图文详解)
  • 原文地址:https://www.cnblogs.com/coder2012/p/3143953.html
Copyright © 2020-2023  润新知