Unix-epoll
?本人不才,只能从表面理解epoll函数的机制,看了很多博客,由于缺乏基础知识,所以对内核中的实现和其数据结构理解不到位,粗浅地来认识一下。 系统打开的最大文件描述符 也是有限制的,并且这个最大量和内存有关 cat /proc/sys/fs/file-max
194720 ?
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *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_data_t data;epoll高效的原因 }; .........................? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 看一下select低效的原因 同理poll也一样是O(n). 接着看epoll: epoll_create(int size)//这个函数是创建一个哈希表,size表示哈希表的容量 epoll_create1(int flags)//这个是实现了一棵红黑树,不需要指定容量 ? #include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include<iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<sys/select.h> #include<errno.h> #include<sys/poll.h> #include<vector> #include<algorithm> #include<sys/signal.h> #include<fcntl.h> #include<sys/epoll.h> using namespace std; typedef std::vector<struct epoll_event> EventList; #define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); }while(0) void activate_nonblock(int fd){ int ret; int flags=fcntl(fd,F_GETFL); if(flags==-1) ERR_EXIT("FCNTL"); flags|=O_NONBLOCK; ret=fcntl(fd,F_SETFL,flags); if(ret==-1) ERR_EXIT("fcntl"); } int main() { signal(SIGPIPE,SIG_IGN); //signal(SIGPIPE,handler); int sockfd = socket(AF_INET,SOCK_STREAM,0); if (sockfd == -1) { perror("socket() err"); return -1; } int on = 1; if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) == -1) { perror("setsockopt() err"); return -1; } int conn; vector<int> client; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(sockfd,(struct sockaddr *) &addr,sizeof(addr)) == -1) { perror("bind() err"); return -1; } if (listen(sockfd,SOMAXCONN) == -1) { perror("bind() err"); return -1; } struct sockaddr_in peeraddr; int nready; int epollfd; struct epoll_event event; event.data.fd=sockfd; event.events=EPOLLIN|EPOLLET; epollfd=epoll_create1(EPOLL_CLOEXEC); epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);//将sockfd和event加入epollfd进行管理 EventList Events(16); int count=0; while(1){ int i;socklen_t peerlen; nready=epoll_wait(epollfd,&*Events.begin(),static_cast<int>(Events.size()),-1); if((size_t)nready==Events.size()) Events.resize(Events.size()*2); for( i=0;i<nready;i++){ if(Events[i].data.fd==sockfd){ peerlen=sizeof(peeraddr); conn=accept(sockfd,(struct sockaddr *)&peeraddr,&peerlen); printf("ip=%s port=%dn",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); printf("count=%dn",++count); client.push_back(conn); activate_nonblock(conn); event.data.fd=conn; event.events=EPOLLIN|EPOLLET; epoll_ctl(epollfd,conn,&event); } else if(Events[i].events&EPOLLIN){ conn=Events[i].data.fd; if(conn<0) continue; char recvbuf[1024]={0}; int ret=read(conn,recvbuf,1024); if(ret==-1) ERR_EXIT("read"); if(ret==0){ printf("client closen"); close(conn); event=Events[i]; epoll_ctl(epollfd,EPOLL_CTL_DEL,&event); client.erase(std::remove(client.begin(),client.end(),conn),client.end()); } fputs(recvbuf,stdout); write(conn,strlen(recvbuf)); } } } } ? ? epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)) 通过epoll_ctl函数添加进来的事件都会被放在红黑树的某个节点内,所以,重复添加是没有用的。当把事件添加进来的时候时候会完成关键的一步,那就是该事件都会与相应的设备(网卡)驱动程序建立回调关系,当相应的事件发生后,就会调用这个回调函数,该回调函数在内核中被称为:ep_poll_callback,这个回调函数其实就所把这个事件添加到rdllist这个双向链表中。一旦有事件发生,epoll就会将该事件添加到双向链表中。那么当我们调用epoll_wait时,epoll_wait只需要检查rdlist双向链表中是否有存在注册的事件,效率非常可观。这里也需要将发生了的事件复制到用户态内存中即可。 ? ? ? 也就是说我们得到epoll_wait返回准备好的时间并不需要遍历完整个描述符。这和底层实现有关,由于能力不足,只能理解到这。以后再慢慢补充 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |