From: http://blog.csdn.net/piaojun_pj/article/details/6103709
?
epoll的優(yōu)點: 1.支持一個進程打開大數(shù)目的socket描述符(FD) ??? select 最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設(shè)置,默認值是2048。對于那些需要支持的上萬連接數(shù)目的IM服務(wù)器來說顯然太少了。這時候你一是可以選擇修改這個宏然后重新編譯內(nèi)核,不過資料也同時指出這樣會帶來網(wǎng)絡(luò)效率的下降,二是可以選擇多進程的解決方案(傳統(tǒng)的 Apache方案),不過雖然linux上面創(chuàng)建進程的代價比較小,但仍舊是不可忽視的,加上進程間數(shù)據(jù)同步遠比不上線程間同步的高效,所以也不是一種完美的方案。不過 epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠大于2048,舉個例子,在1GB內(nèi)存的機器上大約是10萬左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。
2.IO效率不隨FD數(shù)目增加而線性下降 ??? 傳統(tǒng)的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由于網(wǎng)絡(luò)延時,任一時間只有部分的socket是"活躍"的,但是select/poll每次調(diào)用都會線性掃描全部的集合,導致效率呈現(xiàn)線性下降。但是epoll不存在這個問題,它只會對"活躍"的socket進行操作---這是因為在內(nèi)核實現(xiàn)中epoll是根據(jù)每個fd上面的callback函數(shù)實現(xiàn)的。那么,只有"活躍"的socket才會主動的去調(diào)用 callback函數(shù),其他idle狀態(tài)socket則不會,在這點上,epoll實現(xiàn)了一個"偽"AIO,因為這時候推動力在os內(nèi)核。在一些 benchmark中,如果所有的socket基本上都是活躍的---比如一個高速LAN環(huán)境,epoll并不比select/poll有什么效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環(huán)境,epoll的效率就遠在select/poll之上了。
3.使用mmap加速內(nèi)核與用戶空間的消息傳遞。 ??? 這點實際上涉及到epoll的具體實現(xiàn)了。無論是select,poll還是epoll都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝就很重要,在這點上,epoll是通過內(nèi)核于用戶空間mmap同一塊內(nèi)存實現(xiàn)的。而如果你想我一樣從2.5內(nèi)核就關(guān)注epoll的話,一定不會忘記手工 mmap這一步的。
4.內(nèi)核微調(diào) ??? 這一點其實不算epoll的優(yōu)點了,而是整個linux平臺的優(yōu)點。也許你可以懷疑linux平臺,但是你無法回避linux平臺賦予你微調(diào)內(nèi)核的能力。比如,內(nèi)核TCP/IP協(xié)議棧使用內(nèi)存池管理sk_buff結(jié)構(gòu),那么可以在運行時期動態(tài)調(diào)整這個內(nèi)存pool(skb_head_pool)的大小--- 通過echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函數(shù)的第2個參數(shù)(TCP完成3次握手的數(shù)據(jù)包隊列長度),也可以根據(jù)你平臺內(nèi)存大小動態(tài)調(diào)整。更甚至在一個數(shù)據(jù)包面數(shù)目巨大但同時每個數(shù)據(jù)包本身大小卻很小的特殊系統(tǒng)上嘗試最新的NAPI網(wǎng)卡驅(qū)動架構(gòu)。
epoll簡介
在linux的網(wǎng)絡(luò)編程中,很長的時間都在使用select來做事件觸發(fā)。在linux新的內(nèi)核中,有了一種替換它的機制,就是epoll。 相比于select,epoll最大的好處在于它不會隨著監(jiān)聽fd數(shù)目的增長而降低效率。因為在內(nèi)核中的select實現(xiàn)中,它是采用輪詢來處理的,輪詢的fd數(shù)目越多,自然耗時越多。并且,在linux/posix_types.h頭文件有這樣的聲明: #define __FD_SETSIZE??? 1024 表示select最多同時監(jiān)聽1024個fd,當然,可以通過修改頭文件再重編譯內(nèi)核來擴大這個數(shù)目,但這似乎并不治本。
epoll的接口非常簡單,一共就三個函數(shù):
1. int epoll_create(int size); 創(chuàng)建一個epoll的句柄,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大。這個參數(shù)不同于select()中的第一個參數(shù),給出最大監(jiān)聽的fd+1的值。需要注意的是,當創(chuàng)建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調(diào)用close()關(guān)閉,否則可能導致fd被耗盡。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epoll的事件注冊函數(shù),它不同與select()是在監(jiān)聽事件時告訴內(nèi)核要監(jiān)聽什么類型的事件,而是在這里先注冊要監(jiān)聽的事件類型。第一個參數(shù)是epoll_create()的返回值,第二個參數(shù)表示動作,用三個宏來表示: EPOLL_CTL_ADD:注冊新的fd到epfd中; EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件; EPOLL_CTL_DEL:從epfd中刪除一個fd; 第三個參數(shù)是需要監(jiān)聽的fd,第四個參數(shù)是告訴內(nèi)核需要監(jiān)聽什么事,struct epoll_event結(jié)構(gòu)如下: struct epoll_event { ? __uint32_t events;? /* Epoll events */ ? epoll_data_t data;? /* User data variable */ };
events可以是以下幾個宏的集合: EPOLLIN :表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉); EPOLLOUT:表示對應(yīng)的文件描述符可以寫; EPOLLPRI:表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來); EPOLLERR:表示對應(yīng)的文件描述符發(fā)生錯誤; EPOLLHUP:表示對應(yīng)的文件描述符被掛斷; EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)來說的。 EPOLLONESHOT:只監(jiān)聽一次事件,當監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 等待事件的產(chǎn)生,類似于select()調(diào)用。參數(shù)events用來從內(nèi)核得到事件的集合,maxevents告之內(nèi)核這個events有多大,這個maxevents的值不能大于創(chuàng)建epoll_create()時的size,參數(shù)timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數(shù)返回需要處理的事件數(shù)目,如返回0表示已超時。
?
下面是我在redhat9上用epoll實現(xiàn)的簡單的C/S通信,已經(jīng)運行通過了。
server.c
[c-sharp] view plaincopyprint?
#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;??? ????my_addr.sin_addr.s_addr=INADDR_ANY;?? ????my_addr.sin_port=htons(8000);??? ???? ?? ????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;????? ????}????? ?????? ????listen(server_sockfd,5);????? ????sin_size=sizeof (struct ?sockaddr_in);??? ?????? ????int ?epoll_fd;?? ????epoll_fd=epoll_create(MAX_EVENTS);?? ????if (epoll_fd==-1)?? ????{?? ????????perror("epoll_create?failed" );?? ????????exit(EXIT_FAILURE);?? ????}?? ????struct ?epoll_event?ev;?? ????struct ?epoll_event?events[MAX_EVENTS];?? ????ev.events=EPOLLIN;?? ????ev.data.fd=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;?? ?????? ????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);?? ????????????????}?? ?????????????????? ????????????????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;????? }????
#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 10int main(int argc, char * argv[])
{int server_sockfd;// 服務(wù)器端套接字 int client_sockfd;// 客戶端套接字 int len; struct sockaddr_in my_addr; // 服務(wù)器網(wǎng)絡(luò)地址結(jié)構(gòu)體 struct sockaddr_in remote_addr; // 客戶端網(wǎng)絡(luò)地址結(jié)構(gòu)體 int sin_size; char buf[BUFFER_SIZE]; // 數(shù)據(jù)傳送的緩沖區(qū) memset(&my_addr,0,sizeof(my_addr)); // 數(shù)據(jù)初始化--清零 my_addr.sin_family=AF_INET; // 設(shè)置為IP通信 my_addr.sin_addr.s_addr=INADDR_ANY;// 服務(wù)器IP地址--允許連接到所有本地地址上 my_addr.sin_port=htons(8000); // 服務(wù)器端口號 // 創(chuàng)建服務(wù)器端套接字--IPv4協(xié)議,面向連接通信,TCP協(xié)議if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) { perror("socket"); return 1; } // 將套接字綁定到服務(wù)器的網(wǎng)絡(luò)地址上if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) { perror("bind"); return 1; } // 監(jiān)聽連接請求--監(jiān)聽隊列長度為5 listen(server_sockfd,5); sin_size=sizeof(struct sockaddr_in); // 創(chuàng)建一個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事件結(jié)構(gòu)體struct epoll_event events[MAX_EVENTS];// 事件監(jiān)聽隊列ev.events=EPOLLIN;ev.data.fd=server_sockfd;// 向epoll注冊server_sockfd監(jiān)聽事件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監(jiān)聽事件發(fā)生的個數(shù)// 循環(huán)接受客戶端請求 while(1){// 等待事件發(fā)生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監(jiān)聽事件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));}// 客戶端有數(shù)據(jù)發(fā)送過來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
[c-sharp] view plaincopyprint?
#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;??? ????remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1" );?? ????remote_addr.sin_port=htons(8000);??? ?????? ????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);?? ?????????? ????????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;?? }??
#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 40int main(int argc, char *argv[])
{ int client_sockfd; int len; struct sockaddr_in remote_addr; // 服務(wù)器端網(wǎng)絡(luò)地址結(jié)構(gòu)體 char buf[BUFFER_SIZE]; // 數(shù)據(jù)傳送的緩沖區(qū) memset(&remote_addr,0,sizeof(remote_addr)); // 數(shù)據(jù)初始化--清零 remote_addr.sin_family=AF_INET; // 設(shè)置為IP通信 remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");// 服務(wù)器IP地址 remote_addr.sin_port=htons(8000); // 服務(wù)器端口號 // 創(chuàng)建客戶端套接字--IPv4協(xié)議,面向連接通信,TCP協(xié)議 if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) { perror("client socket creation failed"); exit(EXIT_FAILURE);} // 將套接字綁定到服務(wù)器的網(wǎng)絡(luò)地址上 if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0) { perror("connect to server failed"); exit(EXIT_FAILURE);} // 循環(huán)監(jiān)聽服務(wù)器請求 while(1){printf("Please input the message:");scanf("%s",buf);// exitif(strcmp(buf,"exit")==0){break;}send(client_sockfd,buf,BUFFER_SIZE,0);// 接收服務(wù)器端信息 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);// 關(guān)閉套接字 return 0;
}
?
makefile
[c-sharp] view plaincopyprint?
#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??
總結(jié)
以上是生活随笔 為你收集整理的linux下Epoll实现简单的C/S通信 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。