本文原創版權歸 csdn piaojun_pj 全部,轉載請詳細標明原創做者及出處,以示尊重!!linux
做者:piaojun_pj編程
原文:http://blog.csdn.net/piaojun_pj/article/details/6103709服務器
epoll的優勢:
1.支持一個進程打開大數目的socket描述符(FD)
select 最不能忍受的是一個進程所打開的FD是有必定限制的,由FD_SETSIZE設置,默認值是2048。對於那些須要支持的上萬鏈接數目的IM服務器來講顯然太少了。這時候你一是能夠選擇修改這個宏而後從新編譯內核,不過資料也同時指出這樣會帶來網絡效率的降低,二是能夠選擇多進程的解決方案(傳統的 Apache方案),不過雖然linux上面建立進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,因此也不是一種完美的方案。不過 epoll則沒有這個限制,它所支持的FD上限是最大能夠打開文件的數目,這個數字通常遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目能夠cat /proc/sys/fs/file-max察看,通常來講這個數目和系統內存關係很大。網絡
2.IO效率不隨FD數目增長而線性降低
傳統的select/poll另外一個致命弱點就是當你擁有一個很大的socket集合,不過因爲網絡延時,任一時間只有部分的socket是"活躍"的,可是select/poll每次調用都會線性掃描所有的集合,致使效率呈現線性降低。可是epoll不存在這個問題,它只會對"活躍"的socket進行操做---這是由於在內核實現中epoll是根據每一個fd上面的callback函數實現的。那麼,只有"活躍"的socket纔會主動的去調用 callback函數,其餘idle狀態socket則不會,在這點上,epoll實現了一個"僞"AIO,由於這時候推進力在os內核。在一些 benchmark中,若是全部的socket基本上都是活躍的---好比一個高速LAN環境,epoll並不比select/poll有什麼效率,相反,若是過多使用epoll_ctl,效率相比還有稍微的降低。可是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。架構
3.使用mmap加速內核與用戶空間的消息傳遞。
這點實際上涉及到epoll的具體實現了。不管是select,poll仍是epoll都須要內核把FD消息通知給用戶空間,如何避免沒必要要的內存拷貝就很重要,在這點上,epoll是經過內核於用戶空間mmap同一塊內存實現的。而若是你想我同樣從2.5內核就關注epoll的話,必定不會忘記手工 mmap這一步的。socket
4.內核微調
這一點其實不算epoll的優勢了,而是整個linux平臺的優勢。也許你能夠懷疑linux平臺,可是你沒法迴避linux平臺賦予你微調內核的能力。好比,內核TCP/IP協議棧使用內存池管理sk_buff結構,那麼能夠在運行時期動態調整這個內存pool(skb_head_pool)的大小--- 經過echo XXXX>/proc/sys/net/core/hot_list_length完成。再好比listen函數的第2個參數(TCP完成3次握手的數據包隊列長度),也能夠根據你平臺內存大小動態調整。更甚至在一個數據包面數目巨大但同時每一個數據包自己大小卻很小的特殊系統上嘗試最新的NAPI網卡驅動架構。ide
epoll簡介函數
在linux的網絡編程中,很長的時間都在使用select來作事件觸發。在linux新的內核中,有了一種替換它的機制,就是epoll。
相比於select,epoll最大的好處在於它不會隨着監聽fd數目的增加而下降效率。由於在內核中的select實現中,它是採用輪詢來處理的,輪詢的fd數目越多,天然耗時越多。而且,在linux/posix_types.h頭文件有這樣的聲明:
#define __FD_SETSIZE 1024
表示select最多同時監聽1024個fd,固然,能夠經過修改頭文件再重編譯內核來擴大這個數目,但這彷佛並不治本。ui
epoll的接口很是簡單,一共就三個函數:spa
1. int epoll_create(int size);
建立一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。這個參數不一樣於select()中的第一個參數,給出最大監聽的fd+1的值。須要注意的是,當建立好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:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數是須要監聽的fd,第四個參數是告訴內核須要監聽什麼事,struct epoll_event結構以下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
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);
等待事件的產生,相似於select()調用。參數events用來從內核獲得事件的集合,maxevents告以內核這個events有多大,這個maxevents的值不能大於建立epoll_create()時的size,參數timeout是超時時間(毫秒,0會當即返回,-1將不肯定,也有說法說是永久阻塞)。該函數返回須要處理的事件數目,如返回0表示已超時。
下面是我在redhat9上用epoll實現的簡單的C/S通訊,已經運行經過了。
server.c
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/epoll.h>
- #define BUFFER_SIZE 40
- #define MAX_EVENTS 10
- int main(int argc, char * argv[])
- {
- int server_sockfd;// 服務器端套接字
- int client_sockfd;// 客戶端套接字
- int len;
- struct sockaddr_in my_addr; // 服務器網絡地址結構體
- struct sockaddr_in remote_addr; // 客戶端網絡地址結構體
- int sin_size;
- char buf[BUFFER_SIZE]; // 數據傳送的緩衝區
- memset(&my_addr,0,sizeof(my_addr)); // 數據初始化--清零
- my_addr.sin_family=AF_INET; // 設置爲IP通訊
- my_addr.sin_addr.s_addr=INADDR_ANY;// 服務器IP地址--容許鏈接到全部本地地址上
- my_addr.sin_port=htons(8000); // 服務器端口號
- // 建立服務器端套接字--IPv4協議,面向鏈接通訊,TCP協議
- if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
- {
- perror("socket");
- return 1;
- }
- // 將套接字綁定到服務器的網絡地址上
- if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
- {
- perror("bind");
- return 1;
- }
- // 監聽鏈接請求--監聽隊列長度爲5
- listen(server_sockfd,5);
- sin_size=sizeof(struct sockaddr_in);
- // 建立一個epoll句柄
- int epoll_fd;
- epoll_fd=epoll_create(MAX_EVENTS);
- if(epoll_fd==-1)
- {
- perror("epoll_create failed");
- exit(EXIT_FAILURE);
- }
- struct epoll_event ev;// epoll事件結構體
- struct epoll_event events[MAX_EVENTS];// 事件監聽隊列
- ev.events=EPOLLIN;
- ev.data.fd=server_sockfd;
- // 向epoll註冊server_sockfd監聽事件
- if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_sockfd,&ev)==-1)
- {
- perror("epll_ctl:server_sockfd register failed");
- exit(EXIT_FAILURE);
- }
- int nfds;// epoll監聽事件發生的個數
- // 循環接受客戶端請求
- while(1)
- {
- // 等待事件發生
- nfds=epoll_wait(epoll_fd,events,MAX_EVENTS,-1);
- if(nfds==-1)
- {
- perror("start epoll_wait failed");
- exit(EXIT_FAILURE);
- }
- int i;
- for(i=0;i<nfds;i++)
- {
- // 客戶端有新的鏈接請求
- if(events[i].data.fd==server_sockfd)
- {
- // 等待客戶端鏈接請求到達
- if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
- {
- perror("accept client_sockfd failed");
- exit(EXIT_FAILURE);
- }
- // 向epoll註冊client_sockfd監聽事件
- ev.events=EPOLLIN;
- ev.data.fd=client_sockfd;
- if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_sockfd,&ev)==-1)
- {
- perror("epoll_ctl:client_sockfd register failed");
- exit(EXIT_FAILURE);
- }
- printf("accept client %s/n",inet_ntoa(remote_addr.sin_addr));
- }
- // 客戶端有數據發送過來
- else
- {
- len=recv(client_sockfd,buf,BUFFER_SIZE,0);
- if(len<0)
- {
- perror("receive from client failed");
- exit(EXIT_FAILURE);
- }
- printf("receive from client:%s",buf);
- send(client_sockfd,"I have received your message.",30,0);
- }
- }
- }
- return 0;
- }
client.c
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <string.h>
- #include <stdlib.h>
- #define BUFFER_SIZE 40
- int main(int argc, char *argv[])
- {
- int client_sockfd;
- int len;
- struct sockaddr_in remote_addr; // 服務器端網絡地址結構體
- char buf[BUFFER_SIZE]; // 數據傳送的緩衝區
- memset(&remote_addr,0,sizeof(remote_addr)); // 數據初始化--清零
- remote_addr.sin_family=AF_INET; // 設置爲IP通訊
- remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");// 服務器IP地址
- remote_addr.sin_port=htons(8000); // 服務器端口號
- // 建立客戶端套接字--IPv4協議,面向鏈接通訊,TCP協議
- if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
- {
- perror("client socket creation failed");
- exit(EXIT_FAILURE);
- }
- // 將套接字綁定到服務器的網絡地址上
- if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)
- {
- perror("connect to server failed");
- exit(EXIT_FAILURE);
- }
- // 循環監聽服務器請求
- while(1)
- {
- printf("Please input the message:");
- scanf("%s",buf);
- // exit
- if(strcmp(buf,"exit")==0)
- {
- break;
- }
- send(client_sockfd,buf,BUFFER_SIZE,0);
- // 接收服務器端信息
- len=recv(client_sockfd,buf,BUFFER_SIZE,0);
- printf("receive from server:%s/n",buf);
- if(len<0)
- {
- perror("receive from server failed");
- exit(EXIT_FAILURE);
- }
- }
- close(client_sockfd);// 關閉套接字
- return 0;
- }
makefile
- #This is the makefile of EpollTest
- .PHONY:all
- all:server client
- server:
- gcc server.c -o server
- client:
- gcc client.c -o client
- clean:
- rm -f server client