Linux網絡編程之epoll知識點備忘
首先是關於IO多路復用的基礎概念:
select,poll,epoll都是IO多路復用的機制。I/O多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。
關鍵要了解阻塞非阻塞、同步異步之間的關系與區別,然後對於常用的IO多路復用方法要有所了解。
epoll是Linux獨有的IO多路復用技術,也是較新的方法,和傳統的select/poll方法相比主要優勢可以這樣理解:
select的幾大缺點:
(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
(3)select支持的文件描述符數量太小了,默認是1024
poll和select基本相似,只不過交換的數據結構有所差別罷了。
epoll的特色:
epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。epoll提供了三個函數,epoll_create,epoll_ctl和epoll_wait,epoll_create是創建一個epoll句柄;epoll_ctl是註冊要監聽的事件類型;epoll_wait則是等待事件的產生。
對於第一個缺點,epoll的解決方案在epoll_ctl函數中。每次註冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進內核,而不是在epoll_wait的時候重復拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。
對於第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)並為每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。
對於第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。
總結:
(1)select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。
(2)select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,並且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,註意這裏的等待隊列並不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。
通過對於epoll進一步的了解,我個人覺得要理解epoll最關鍵的是需要了解以下幾點:
1.和select等的比較。主要從效率,方法,監聽數量等方面需要有一個比較全面的了解
2.關於epoll的三個函數需要了解用法
3.對於epoll_event這個數據結構需要有深刻了解,特別是其中的epoll_data數據,其中的union可以保存許多類型的用戶數據,這為回調函數等提供了途徑
最後摘抄一點代碼用於提示epoll的基本使用方法:
1 int main() 2 { 3 int i, maxi, listenfd, new_fd, sockfd,epfd,nfds; 4 ssize_t n; 5 char line[MAXLINE]; 6 socklen_t clilen; 7 struct epoll_event ev,events[20];//ev用於註冊事件,數組用於回傳要處理的事件 8 struct sockaddr_in clientaddr, serveraddr; 9 listenfd = socket(AF_INET, SOCK_STREAM, 0);//生成socket文件描述符 10 setnonblocking(listenfd);//把socket設置為非阻塞方式 11 epfd=epoll_create(256);//生成用於處理accept的epoll專用的文件描述符 12 ev.data.fd=listenfd;//設置與要處理的事件相關的文件描述符 13 ev.events=EPOLLIN|EPOLLET;//設置要處理的事件類型 14 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);//註冊epoll事件 15 //設置服務器端地址信息 16 bzero(&serveraddr, sizeof(serveraddr)); 17 serveraddr.sin_family = AF_INET; 18 char *local_addr= LOCAL_ADDR; 19 inet_aton(local_addr,&(serveraddr.sin_addr)); 20 serveraddr.sin_port=htons(SERV_PORT); 21 bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));//綁定socket連接 22 listen(listenfd, LISTENQ);//監聽 23 maxi = 0; 24 for ( ; ; ) 25 { 26 /* epoll_wait:等待epoll事件的發生,並將發生的sokct fd和事件類型放入到events數組中; 27 * nfds:為發生的事件的個數。 28 * 註: 29 */ 30 nfds=epoll_wait(epfd,events,20,500); 31 //處理所發生的所有事件 32 for(i=0;i<nfds;++i) 33 { 34 if(events[i].data.fd==listenfd)//事件發生在listenfd上 35 { 36 /* 獲取發生事件端口信息,存於clientaddr中; 37 *new_fd:返回的新的socket描述符,用它來對該事件進行recv/send操作*/ 38 new_fd = accept(listenfd,(struct sockaddr *)&clientaddr, &clilen); 39 if(new_fd<0) 40 { 41 perror("new_fd<0"); 42 exit(1); 43 } 44 setnonblocking(new_fd); 45 char *str = inet_ntoa(clientaddr.sin_addr); 46 ev.data.fd=new_fd;//設置用於讀操作的文件描述符 47 ev.events=EPOLLIN|EPOLLET;//設置用於註測的讀操作事件 48 epoll_ctl(epfd,,,&ev);//註冊ev 49 } 50 else if(events[i].events&EPOLLIN) 51 { 52 if ( (sockfd = events[i].data.fd) < 0) 53 continue; 54 if ( (n = read(sockfd, line, MAXLINE)) < 0) 55 { 56 if (errno == ECONNRESET) 57 { 58 close(sockfd); 59 events[i].data.fd = -1; 60 } 61 else 62 std::cout<<"readline error"<<std::endl; 63 } 64 else if (n == 0) 65 { 66 close(sockfd); 67 events[i].data.fd = -1; 68 } 69 ev.data.fd=sockfd;//設置用於寫操作的文件描述符 70 ev.events=EPOLLOUT|EPOLLET;//設置用於註測的寫操作事件 71 epoll_ctl(epfd,,sockfd,&ev);//修改sockfd上要處理的事件為EPOLLOUT 72 } 73 else if(events[i].events&EPOLLOUT) 74 { 75 sockfd = events[i].data.fd; 76 write(sockfd, line, n); 77 ev.data.fd=sockfd;//設置用於讀操作的文件描述符 78 ev.events=EPOLLIN|EPOLLET;//設置用於註測的讀操作事件 79 epoll_ctl(epfd,,sockfd,&ev);//修改sockfd上要處理的事件為EPOLIN 80 } 81 } 82 } 83 }
參考:http://blog.51cto.com/7666425/1261446
http://www.cnblogs.com/Anker/p/3265058.html
Linux網絡編程之epoll知識點備忘