• Linux epoll总结


    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监听事件中删除。

     

     实例

    复制代码
      1 //epoll_server.c
      2 #include <stdio.h>
      3 #include <string.h>
      4 #include <sys/socket.h>
      5 #include <sys/epoll.h>
      6 #include <errno.h>
      7 #include <netinet/in.h>
      8 #include <arpa/inet.h>
      9 #include <sys/types.h>
     10 #include <unistd.h>
     11 
     12 #define SERV_PORT  8802
     13 
     14 
     15 int main()
     16 {
     17     int i,flag;
     18     int sockfd,clntfd,newfd;
     19     int epfd,nfds;
     20     ssize_t n;
     21     char buffer[1024];
     22     int s = sizeof(struct sockaddr);
     23 
     24     struct sockaddr_in serv_addr;
     25     struct sockaddr_in clnt_addr;
     26     //定义epoll数据结构
     27     struct epoll_event ev,events[20];
     28 
     29     epfd = epoll_create(256);
     30 
     31     //创建socket,并初始化事件ev
     32     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     33     if(sockfd < 0){
     34         perror("socket error!
    ");
     35         return -1;
     36     }
     37     ev.data.fd = sockfd;
     38     ev.events = EPOLLIN|EPOLLET;
     39 
     40     //注册epoll事件
     41     flag = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
     42     if(flag < 0){
     43         perror("epoll_ctl error!
    ");
     44         return -1;
     45     }
     46 
     47     bzero(&serv_addr, sizeof(serv_addr));
     48     serv_addr.sin_family = AF_INET;
     49     serv_addr.sin_port = htons(SERV_PORT);
     50     serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
     51 
     52     flag = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr));
     53     if(flag < 0){
     54         perror("bind error!
    ");
     55         return -1;
     56     }
     57     printf("bind
    ");
     58 
     59     flag = listen(sockfd, 20);
     60     if(flag < 0){
     61         perror("listen error!
    ");
     62         return -1;
     63     }
     64     printf("listen
    ");
     65 
     66     //开始循环
     67     while(1){
     68         //等待事件发生,返回请求数目
     69         nfds = epoll_wait(epfd, events, 20, 500);
     70         //一次处理请求
     71         for(i = 0; i < nfds; ++i){
     72             if(events[i].data.fd == sockfd){
     73                 clntfd = accept(sockfd, (struct sockaddr*)&clnt_addr,(unsigned int*)&s);
     74                 if(clntfd < 0){
     75                     perror("accept error");
     76                     continue;
     77                 }
     78                 printf("accept
    ");
     79 
     80                 char *str = inet_ntoa(clnt_addr.sin_addr);
     81                 printf("accepnt the client ip : %s
    ",str);
     82 
     83                 //设置文件标识符,设置操作属性:写操作
     84                 ev.data.fd = clntfd;
     85                 ev.events = EPOLLOUT|EPOLLET;
     86                 //向创建的的epoll进行注册写操作
     87                 epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &ev);
     88             }
     89             else if(events[i].events&EPOLLOUT){
     90                 printf("EPOLLOUT
    ");
     91 
     92                 if((newfd = events[i].data.fd) < 0)
     93                     continue;
     94                 bzero(buffer,sizeof(buffer));
     95                 strcpy(buffer,"welcome to myserver!
    ");
     96                 flag = send(newfd, buffer, 1024, 0);
     97                 if(flag < 0){
     98                     perror("send error");
     99                     continue;
    100                 }
    101                 //修改操作为读操作
    102                 ev.data.fd = clntfd;
    103                 ev.events = EPOLLIN|EPOLLET;
    104                 epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev);
    105             }
    106             else if(events[i].events&EPOLLIN){
    107                 printf("EPOLLIN
    ");
    108 
    109                 bzero(buffer,sizeof(buffer));
    110                 if((newfd = events[i].data.fd) < 0)
    111                     continue;
    112                 if((n = read(newfd, buffer, 1024)) < 0){
    113                     if(errno == ECONNRESET){
    114                         close(newfd);
    115                         events[i].data.fd = -1;
    116                         printf("errno ECONRESET!
    ");
    117                     }
    118                     else{
    119                         perror("readbuffer error!
    ");
    120                     }
    121                 }
    122                 else if(n == 0){//表示客户端已经关闭
    123                     close(newfd);
    124                     events[i].data.fd = -1;
    125                     printf("n为0
    ");
    126                 }
    127                 if(buffer[0]!='0')
    128                     printf("have read: %s
    ",buffer);
    129             }
    130         }
    131     }
    132     close(sockfd);
    133     return 0;
    134 }
    复制代码

    源码下载:这里

    参考

        

            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

     
     
     
  • 相关阅读:
    纪念一下Jerry
    在中信66楼微软Offic参加讲座
    关于地理信息信息点数据采集一些方法
    简单的C#进行图片操作
    PHP访问C#建立的Webservice
    搜索引擎一:介绍
    介绍一况挺好用的Javascript编辑器(带项目)
    服务器不支持WebResource.axd的特殊处理
    Daily Web Words
    php psr 编码规范(PSR[04])
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3147307.html
Copyright © 2020-2023  润新知