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操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。

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

 Linux 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!\n");
 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!\n");
 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!\n");
 55         return -1;
 56     }
 57     printf("bind\n");
 58 
 59     flag = listen(sockfd, 20);
 60     if(flag < 0){
 61         perror("listen error!\n");
 62         return -1;
 63     }
 64     printf("listen\n");
 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\n");
 79 
 80                 char *str = inet_ntoa(clnt_addr.sin_addr);
 81                 printf("accepnt the client ip : %s\n",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\n");
 91 
 92                 if((newfd = events[i].data.fd) < 0)
 93                     continue;
 94                 bzero(buffer,sizeof(buffer));
 95                 strcpy(buffer,"welcome to myserver!\n");
 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\n");
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!\n");
117                     }
118                     else{
119                         perror("readbuffer error!\n");
120                     }
121                 }
122                 else if(n == 0){//表示客户端已经关闭
123                     close(newfd);
124                     events[i].data.fd = -1;
125                     printf("n为0\n");
126                 }
127                 if(buffer[0]!='0')
128                     printf("have read: %s\n",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

 
 
 
标签:  服务器开发

你可能感兴趣的:(服务器开发)