1、概述linux
說到Linux下的IO複用,系統提供了三個系統調用,分別是select poll epoll。那麼這三者之間有什麼不一樣呢,何時使用三個之間的其中一個呢?編程
下面,我將從系統調用原型來分析其中的不一樣。windows
2、系統接口原型數組
1. selectsocket
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
2. pollide
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts, const sigset_t *sigmask);
struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };
3. epoll函數
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);
3、參數對比性能
1. select測試
2. poll優化
poll事件類型
事件 | 描述 | 是否可做爲輸入 | 是否可做爲輸出 |
POLLIN | 數據(包括普通數據和優先數據) | 是 | 是 |
POLLRDNORM | 普通數據可讀 | 是 | 是 |
POLLRDBAND | 優先級帶數據可讀(Linux不支持) | 是 | 是 |
POLLPRI | 高優先級數據可讀,好比TCP帶外數據 | 是 | 是 |
POLLOUT | 數據(包括普通數據和優先數據)可寫 | 是 | 是 |
POLLWRNORM | 普通數據可寫 | 是 | 是 |
POLLWRBAND | 優先級帶數據可寫 | 是 | 是 |
POLLRDHUP | TCP鏈接被對方關閉,或者對方關閉了寫操做。它由GNU引入 | 是 | 是 |
POLLERR | 錯誤 | 否 | 是 |
POLLHUP | 掛起。好比管道的寫端被關閉後,讀端描述符上將收到POLLHUP事件 | 否 | 是 |
POLLNVAL | 文件描述符沒有打開 | 否 | 是 |
3. epoll
4、性能對比
select、poll的內部實現機制類似,性能差異主要在於向內核傳遞參數以及對fdset的位操做上,另外,select存在描述符數的硬限制,不能處理很大的描述符集合。
這裏主要考察poll與epoll在不一樣大小描述符集合的狀況下性能的差別。
測試程序會統計在不一樣的文件描述符集合的狀況下,1s 內poll與epoll調用的次數。
統計結果以下,從結果能夠看出,對poll而言,每秒鐘內的系統調用數目雖集合增大而很快下降,而epoll基本保持不變,具備很好的擴展性。
描述符集合大小 |
poll |
epoll |
1 |
331598 |
258604 |
10 |
330648 |
297033 |
100 |
91199 |
288784 |
1000 |
27411 |
296357 |
5000 |
5943 |
288671 |
10000 |
2893 |
292397 |
25000 |
1041 |
285905 |
50000 |
536 |
293033 |
100000 |
224 |
285825 |
5、鏈接數
我本人也曾經在項目中用過select和epoll,對於select,感觸最深的是linux下select最大數目限制(windows 下彷佛沒有限制),每一個進程的select最多能處理FD_SETSIZE個FD(文件句柄),若是要處理超過1024個句柄,只能採用多進程了。
常見的使用select的多進程模型是這樣的:
一個進程專門accept,成功後將fd經過UNIX socket傳遞給子進程處理,父進程能夠根據子進程負載分派。
曾經用過1個父進程 + 4個子進程 承載了超過4000個的負載。
這種模型在咱們當時的業務運行的很是好。
epoll在鏈接數方面沒有限制,固然可能須要用戶調用API重現設置進程的資源限制。
6、相同點
7、不一樣點
對於select:
1. 只能經過三個結構體參數處理三種事件,分別是:可讀、可寫和異常事件,而不能處理更多的事件;
2. 這三個參數既是輸入參數,也是輸出參數,所以,在每次調用select以前,都得對fd_set進行重置;
對於poll:
1. 將文件描述符和事件關聯在一塊兒,任何事件都被統一處理,從而使得編程接口簡潔很多;
2. 內核改變的變量是revents,而不是events,所以,調用以前不須要再重置;
因爲每次select和poll調用都返回整個用戶註冊的事件集合(其中包括就緒的和未就緒的),因此應用程序索引就緒文件描述符的時間複雜度爲O(n)。
而epoll採用與select和poll徹底不一樣的方式來管理用戶註冊的事件。
8、poll和epoll在使用上的差異
/*poll example*/ /*如何索引poll返回的就緒文件描述符*/ int ret = poll(fds, MAX_EVENT_NUMBER, -1); /*必須遍歷全部已註冊文件描述符並找到其中的就緒者(固然,能夠利用ret來稍做優化)*/ for(int i = 0; i < MAX_EVENT_NUMBER; ++i) { if(fds[i].revents & POLLIN) { int sockfd = fds[i].fd; //deal with sockfd. } } /*epoll example*/ int epfd = epoll_create(MAXSIZE); struct epoll_event ev,events[5000]; //設置與要處理的事件相關的文件描述符 ev.data.fd=listenfd; //設置要處理的事件類型 ev.events=EPOLLIN|EPOLLET; //註冊epoll事件 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); int nfds = epoll_wait(epfd,events,6000,-1); //處理所發生的全部事件 for(int i = 0; i< nfds; ++i) { //new accept. if(events[i].data.fd == listenfd) { printf("listen=%d\n",events[i].data.fd); connfd = accept(listenfd,(sockaddr *)(&clientaddr), &clilen); if(connfd<0) { perror("connfd<0"); exit(1); } setnonblocking(connfd); char *str = inet_ntoa(clientaddr.sin_addr); std::cout<<"connec_ from >>"<<str<<" "<<connfd<<std::endl; //設置用於讀操做的文件描述符 ev.data.fd = connfd; //設置用於注測的讀操做事件 //ev.data.ptr = NULL; ev.events = EPOLLIN|EPOLLET; //註冊ev epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); ev.data.fd = listenfd; //設置要處理的事件類型 ev.events=EPOLLIN|EPOLLET; //註冊epoll事件 epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev); continue; } else if(events[i].events & EPOLLIN) { num1++; //fprintf(stderr,"reading! %d\n",num1); if( (sockfd = events[i].data.fd) <= 0) { num1--; continue; } new_task = NULL; while(new_task == NULL) new_task = new task(); new_task->fd = sockfd; new_task->next=NULL; //fprintf(stderr,"sockfd %d",sockfd); //添加新的讀任務 pthread_mutex_lock(&mutex); if(readhead == NULL) { readhead = new_task; readtail = new_task; } else { readtail->next=new_task; readtail=new_task; } //喚醒全部等待cond1條件的線程 pthread_cond_broadcast(&cond1); pthread_mutex_unlock(&mutex); continue; } else if(events.events & EPOLLOUT) { //fprintf(stderr,"EPOLLOUT"); num++; rdata=(struct user_data *)events[i].data.ptr; sockfd =rdata->fd; if(old == sockfd) { fprintf(stderr,"repreted sockfd=%d\n",sockfd); //exit(1); } old=sockfd; //fprintf(stderr,"write %d\n",num); int size=write(sockfd, rdata->line, rdata->n_size); //fprintf(stderr,"write=%d delete rdata\n",size); fprintf(stderr,"addr=%x fdwrite=%d size=%d\n",rdata,rdata->fd,size); if(rdata!=NULL)//主要問題致使delete重複相同對象 events返回對象相同 { delete rdata; rdata=NULL; } //設置用於讀操做的文件描述符 //fprintf(stderr,"after delete rdata\n"); ev.data.fd=sockfd; //設置用於注測的讀操做事件 ev.events=EPOLLIN|EPOLLET; //修改sockfd上要處理的事件爲EPOLIN res = epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); while(res==-1) { //fprintf(stderr,"out error"); exit(1); } //fprintf(stderr,"out EPOLLOUT\n"); continue; } else if(events.events&(EPOLLHUP|EPOLLERR)) { //fprintf(stderr,"EPPOLLERR\n"); int fd=events.data.fd; if(fd>6000) { fd=((struct user_data*)(events.data.ptr))->fd; } //設置用於注測的讀操做事件 ev.data.fd=fd; ev.events=EPOLLIN|EPOLLET|EPOLLOUT; //修改sockfd上要處理的事件爲EPOLIN epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev); } }