epoll总结

来源:互联网 发布:python 字符串 表示 编辑:程序博客网 时间:2024/05/16 00:48

首先援引http://blog.csdn.net/xiajun07061225/article/details/9250579的一些东西,紧接着给出个人的观点和代码,如有不足,请各位大神补充更正


三个函数:

1. int epoll_create(int size);

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好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:注册新的fdepfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd

 

第三个参数是需要监听的fd

第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

[cpp] view plain copy
 print?
  1. //保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)  
  2.   
  3. typedef union epoll_data {  
  4.     void *ptr;  
  5.     int fd;  
  6.     __uint32_t u32;  
  7.     __uint64_t u64;  
  8. } epoll_data_t;  
  9.  //感兴趣的事件和被触发的事件  
  10. struct epoll_event {  
  11.     __uint32_t events; /* Epoll events */  
  12.     epoll_data_t data; /* User data variable */  
  13. };  

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);

收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。


linuxepoll如何实现高效处理百万句柄的

开发高性能网络程序时,windows开发者们言必称iocplinux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的selectpoll效率高大发了。我们用起epoll来都感觉挺爽,确实快,那么,它到底为什么可以高速处理这么多并发连接呢?

 

使用起来很清晰,首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。

 

epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。

 

epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。

 

从上面的调用方式就可以看到epollselect/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socketselect/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。

 

所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。


个人观点:首先,当A用户向服务器发送数据时,服务器内核红黑树会有一个节点(插入节点时给节点绑定了一个event事件)与之建立连接;当有数据到达时,操作系统会将这个event事件返回到用户空间的event数组中,(ep[1024]数组);event数组封装了文件描述符和回调函数,服务器可以通过访问event的回调函数来响应A的请求。

efd = epoll_create(1024);//在此创建一个句柄,执行后会在内核创建一颗红黑树

#include<stdlib.h>//#include<sys/socket.h>#define SERVPORT 8000void main(){int sockfd,cfd,efd;int i,j;char buf[1024];struct sockaddr_in serv_addr,cli_addr;struct epoll_event tep,ep[1024];bzero(&serv_addr,sizeof(struct sockaddr_in));bzero(&cli_addr,sizeof(struct sockaddr_in));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.port = htons(SERVPORT);if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){perror("socket create error\n");exit(0);}if((bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0){perror("bind error\n");exit(0);}if(listen(sockfd,128) < 0){perror("listen error\n");exit(0);}efd = epoll_create(1024);//在此创建一个句柄,执行后会在内核创建一颗红黑树tep.events = EPOLLIN;tep.data.fd = sockfd;int res = epoll_ctl(efd,EPOLL_CTL_ADD,sockfd,&tep);//插入一个lfd(即listen监听的套接字描述符),此处是sockfd;while(1){int nready = epoll_wait(efd,ep,1024,-1);for(i = 0;i<nready;i++){if(ep[i].data.fd == sockfd)//如果是新的监听套接字到来,则执行以下操作{int cli_len = sizeof(cli_addr);cfd = accept(sockfd,(struct sockaddr_in *)&cli_addr,&cli_len);//在此返回一个与特定请求相关的套接字描述符,用于通信if(cfd < 0){perror("acept error\n");exit(0);}tep.events = EPOLLIN;tep.data.fd = cfd;res = epoll_ctl(efd,EPOLL_CTL_ADD,cfd,&tep);//将此socket句柄塞入内核事先创建的红黑树中,此时仍然有一个lfd(即listen监听的套接字描述符)}else//如果是红黑树中已经存在的套接字,则执行以下操作{sockfd = ep[i].data.fd;int n = read(sockfd,buf,1024);if(n==0)//超时,从红黑树删除对应套接字{res = epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);close(sockfd);printf("client[%d] closed connection\n",j);}else//将小写变大写,返回给客户端{for(j=0;j<n;j++)buf[j] = toupper(buf[j]);write(sockfd,buf,n);}}}}return;}

0 0