socket网络编程--epoll小结
http://www.cnblogs.com/wunaozai/p/3895860.html
以前使用的用于I/O多路復(fù)用為了方便就使用select函數(shù),但select這個(gè)函數(shù)是有缺陷的。因?yàn)樗С值牟l(fā)連接數(shù)是有限的(一般小于1024),因?yàn)橛脩籼幚淼臄?shù)組是使用硬編碼的。這個(gè)最大值為FD_SETSIZE,這是在<sys/select.h>中的一個(gè)常量,它說明了最大的描述符數(shù)。但是對(duì)于大多數(shù)應(yīng)用程序而言,這個(gè)數(shù)是夠用的,而且有可能還是太大的,多數(shù)應(yīng)用程序只使用3~10個(gè)描述符。而如今的網(wǎng)絡(luò)服務(wù)器小小的都有幾萬的連接,雖然可以使用多線程多進(jìn)程(也就有N*1024個(gè))。但是這樣處理起來既不方面,性能又低。
同時(shí)期有I/O多路復(fù)用的還有一個(gè)poll函數(shù),這個(gè)函數(shù)類似于select,但是其應(yīng)用程序接口有所不用。原型如下
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
//返回值: 準(zhǔn)備就緒的描述符數(shù),若超時(shí)則返回0,出錯(cuò)返回-1
如果只是考慮性能的話,poll()也是不合適的,盡管它可以支持較高的TCP并發(fā)連接數(shù),但是由于其采用“輪詢”機(jī)制(遍歷數(shù)組而已),但并發(fā)數(shù)較高時(shí),其運(yùn)行效率相當(dāng)?shù)?如果有10k個(gè)連接,單用于輪詢的時(shí)間就需要1~10ms了),同時(shí)還可能存在I/O事件分配不均,導(dǎo)致部分TCP連接上的I/O出現(xiàn)“饑餓”現(xiàn)象。基于種種原因在Linux 2.5.44版本后poll被epoll取代。
支持一個(gè)進(jìn)程打開最大數(shù)目的 socket 描述符(FD)。select 最不能忍受的是一個(gè)進(jìn)程所打開的FD 是有一定限制的,由 FD_SETSIZE 設(shè)置,默認(rèn)值是 2048。對(duì)于那些需要支持的上萬連接數(shù)目的 IM 服務(wù)器來說顯然太少了。這時(shí)候你一是可以選擇修改這個(gè)宏然后重新編譯內(nèi)核,不過資料也同時(shí)指出這樣會(huì)帶來網(wǎng)絡(luò)效率的下降,二是可以選擇多進(jìn)程的解決方案(傳統(tǒng)的 Apache 方案Process Per Connection,TPC方案 Thread Per Connection),不過雖然 linux 上面創(chuàng)建進(jìn)程的代價(jià)比較小,但仍舊是不可忽視的,加上進(jìn)程間數(shù)據(jù)同步遠(yuǎn)比不上線程間同步的高效,所以也不是一種完美的方案。不過 epoll 則沒有這個(gè)限制,它所支持的 FD 上限是最大可以打開文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于 2048,舉個(gè)例子,在 1GB 內(nèi)存的機(jī)器上大約是 10 萬左右,具體數(shù)目可以 cat /proc/sys/fs/file-max 察看,一般來說這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。
由于epoll這個(gè)函數(shù)是后增加上的,造成現(xiàn)在很少有資料提及到,我看了APUE,UNPv1等書都沒有找到相關(guān)的函數(shù)原型。所以我只能從網(wǎng)絡(luò)上抄一些函數(shù)原型過來了。
? epoll用到的所有函數(shù)都是在頭文件sys/epoll.h中聲明,有什么地方不明白或函數(shù)忘記了可以去看一下或者man epoll。epoll和select相比,最大不同在于:
epoll返回時(shí)已經(jīng)明確的知道哪個(gè)sokcet fd發(fā)生了事件,不用再一個(gè)個(gè)比對(duì)(輪詢)。這樣就提高了效率。select的FD_SETSIZE是有限制的,而epoll是沒有限制的只與系統(tǒng)資源有關(guān)。
epoll_create函數(shù)
1 /* Creates an epoll instance. Returns an fd for the new instance. 2 The "size" parameter is a hint specifying the number of file 3 descriptors to be associated with the new instance. The fd 4 returned by epoll_create() should be closed with close(). */ 5 extern int epoll_create (int __size) __THROW;該函數(shù)生成一個(gè)epoll專用的文件描述符。它其實(shí)是在內(nèi)核申請(qǐng)空間,用來存放你想關(guān)注的socket fd上是否發(fā)生以及發(fā)生了什么事件。size就是你在這個(gè)epoll fd上能關(guān)注的最大socket fd數(shù)。這個(gè)數(shù)沒有select的1024約束。
epoll_ctl函數(shù)
1 /* Manipulate an epoll instance "epfd". Returns 0 in case of success, 2 -1 in case of error ( the "errno" variable will contain the 3 specific error code ) The "op" parameter is one of the EPOLL_CTL_* 4 constants defined above. The "fd" parameter is the target of the 5 operation. The "event" parameter describes which events the caller 6 is interested in and any associated user data. */ 7 extern int epoll_ctl (int __epfd, int __op, int __fd, 8 struct epoll_event *__event) __THROW; 9 /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */ 10 #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */ 11 #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */ 12 #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */該函數(shù)用于控制某個(gè)epoll文件描述符上的事件,可以注冊(cè)事件,修改事件,刪除事件。?
參數(shù): epfd:由 epoll_create 生成的epoll專用的文件描述符;?
op:要進(jìn)行的操作例如注冊(cè)事件,可能的取值EPOLL_CTL_ADD 注冊(cè)、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 刪除
fd:關(guān)聯(lián)的文件描述符;?
event:指向epoll_event的指針;?
返回值:如果調(diào)用成功返回0,不成功返回-1
用到的數(shù)據(jù)結(jié)構(gòu)
1 typedef union epoll_data 2 { 3 void *ptr; 4 int fd; 5 uint32_t u32; 6 uint64_t u64; 7 } epoll_data_t; 8 9 struct epoll_event 10 { 11 uint32_t events; /* Epoll events */ 12 epoll_data_t data; /* User data variable */ 13 };設(shè)置實(shí)例
1 struct epoll_event ev; 2 //設(shè)置與要處理的事件相關(guān)的文件描述符 3 ev.data.fd=listenfd; 4 //設(shè)置要處理的事件類型 5 ev.events=EPOLLIN|EPOLLET; 6 //注冊(cè)epoll事件 7 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); 8 //常用的事件類型: 9 EPOLLIN :表示對(duì)應(yīng)的文件描述符可以讀; 10 EPOLLOUT:表示對(duì)應(yīng)的文件描述符可以寫; 11 EPOLLPRI:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀 12 EPOLLERR:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤; 13 EPOLLHUP:表示對(duì)應(yīng)的文件描述符被掛斷; 14 EPOLLET:表示對(duì)應(yīng)的文件描述符有事件發(fā)生; 15 //具體可以看<sys/epoll.h>epoll_wait函數(shù)
1 /* Wait for events on an epoll instance "epfd". Returns the number of 2 triggered events returned in "events" buffer. Or -1 in case of 3 error with the "errno" variable set to the specific error code. The 4 "events" parameter is a buffer that will contain triggered 5 events. The "maxevents" is the maximum number of events to be 6 returned ( usually size of "events" ). The "timeout" parameter 7 specifies the maximum wait time in milliseconds (-1 == infinite). 8 This function is a cancellation point and therefore not marked with 9 __THROW. */ 10 extern int epoll_wait (int __epfd, struct epoll_event *__events, 11 int __maxevents, int __timeout); 12 13 14 /* Same as epoll_wait, but the thread's signal mask is temporarily 15 and atomically replaced with the one provided as parameter. 16 This function is a cancellation point and therefore not marked with 17 __THROW. */ 18 extern int epoll_pwait (int __epfd, struct epoll_event *__events, 19 int __maxevents, int __timeout, 20 __const __sigset_t *__ss); 該函數(shù)用于輪詢I/O事件的發(fā)生;
參數(shù): epfd:由epoll_create 生成的epoll專用的文件描述符;
epoll_event:用于回傳代處理事件的數(shù)組;
maxevents:每次能處理的事件數(shù);
timeout:等待I/O事件發(fā)生的超時(shí)值(單位應(yīng)該是ms);-1相當(dāng)于阻塞,0相當(dāng)于非阻塞。一般用-1即可
返回值:返回發(fā)生事件數(shù)。如出錯(cuò)則返回-1。
? 下面給一個(gè)man手冊(cè)里面的例子
1 #define MAX_EVENTS 10 2 struct epoll_event ev, events[MAX_EVENTS]; 3 int listen_sock, conn_sock, nfds, epollfd; 4 5 /* Set up listening socket, 'listen_sock' (socket(), 6 bind(), listen()) */ 7 8 epollfd = epoll_create(10); 9 if (epollfd == -1) { 10 perror("epoll_create"); 11 exit(EXIT_FAILURE); 12 } 13 14 ev.events = EPOLLIN; 15 ev.data.fd = listen_sock; 16 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) { 17 perror("epoll_ctl: listen_sock"); 18 exit(EXIT_FAILURE); 19 } 20 21 for (;;) { 22 nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); 23 if (nfds == -1) { 24 perror("epoll_pwait"); 25 exit(EXIT_FAILURE); 26 } 27 28 for (n = 0; n < nfds; ++n) { 29 if (events[n].data.fd == listen_sock) { 30 conn_sock = accept(listen_sock, 31 (struct sockaddr *) &local, &addrlen); 32 if (conn_sock == -1) { 33 perror("accept"); 34 exit(EXIT_FAILURE); 35 } 36 setnonblocking(conn_sock); 37 ev.events = EPOLLIN | EPOLLET; 38 ev.data.fd = conn_sock; 39 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, 40 &ev) == -1) { 41 perror("epoll_ctl: conn_sock"); 42 exit(EXIT_FAILURE); 43 } 44 } else { 45 do_use_fd(events[n].data.fd); 46 } 47 } 48 }下面這個(gè)是引用mmz_xiaokong
epoll函數(shù)
常用模型的缺點(diǎn)
如果不擺出來其他模型的缺點(diǎn),怎么能對(duì)比出 Epoll 的優(yōu)點(diǎn)呢。
PPC/TPC 模型
這兩種模型思想類似,就是讓每一個(gè)到來的連接一邊自己做事去,別再來煩我 。只是 PPC 是為它開了一個(gè)進(jìn)程,而 TPC 開了一個(gè)線程。可是別煩我是有代價(jià)的,它要時(shí)間和空間啊,連接多了之后,那么多的進(jìn)程 / 線程切換,這開銷就上來了;因此這類模型能接受的最大連接數(shù)都不會(huì)高,一般在幾百個(gè)左右。
select 模型
1. 最大并發(fā)數(shù)限制,因?yàn)橐粋€(gè)進(jìn)程所打開的 FD (文件描述符)是有限制的,由 FD_SETSIZE 設(shè)置,默認(rèn)值是 1024/2048 ,因此 Select 模型的最大并發(fā)數(shù)就被相應(yīng)限制了。自己改改這個(gè) FD_SETSIZE ?想法雖好,可是先看看下面吧 …
2. 效率問題, select 每次調(diào)用都會(huì)線性掃描全部的 FD 集合,這樣效率就會(huì)呈現(xiàn)線性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢來,什么?都超時(shí)了??!!
3. 內(nèi)核 / 用戶空間 內(nèi)存拷貝問題,如何讓內(nèi)核把 FD 消息通知給用戶空間呢?在這個(gè)問題上 select 采取了內(nèi)存拷貝方法。
poll 模型
基本上效率和 select 是相同的, select 缺點(diǎn)的 2 和 3 它都沒有改掉。
Epoll 的提升
把其他模型逐個(gè)批判了一下,再來看看 Epoll 的改進(jìn)之處吧,其實(shí)把 select 的缺點(diǎn)反過來那就是 Epoll 的優(yōu)點(diǎn)了。
1. Epoll 沒有最大并發(fā)連接的限制,上限是最大可以打開文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于 2048, 一般來說這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大 ,具體數(shù)目可以 cat /proc/sys/fs/file-max 察看。
2. 效率提升, Epoll 最大的優(yōu)點(diǎn)就在于它只管你“活躍”的連接 ,而跟連接總數(shù)無關(guān),因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中, Epoll 的效率就會(huì)遠(yuǎn)遠(yuǎn)高于 select 和 poll 。
3. 內(nèi)存拷貝, Epoll 在這點(diǎn)上使用了“共享內(nèi)存 ”,這個(gè)內(nèi)存拷貝也省略了。
Epoll 為什么高效
Epoll 的高效和其數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)是密不可分的(以空間換時(shí)間),這個(gè)下面就會(huì)提到。
首先回憶一下 select 模型,當(dāng)有 I/O 事件到來時(shí), select 通知應(yīng)用程序有事件到了快去處理,而應(yīng)用程序必須輪詢所有的 FD 集合,測(cè)試每個(gè) FD 是否有事件發(fā)生,并處理事件;代碼像下面這樣:
Epoll 不僅會(huì)告訴應(yīng)用程序有I/0 事件到來,還會(huì)告訴應(yīng)用程序相關(guān)的信息,這些信息是應(yīng)用程序填充的,因此根據(jù)這些信息應(yīng)用程序就能直接定位到事件,而不必遍歷整個(gè)FD 集合。
1 int res = epoll_wait(epfd, events, 20, 120); 2 for (int i = 0; i < res;i++) 3 { 4 handleEvent(events[n]); 5 }? 下面用一個(gè)實(shí)例來說明
client.cpp
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <netinet/in.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netdb.h> 8 #include <unistd.h> 9 10 #define MAX_DATA_SIZE 4096 11 #define SERVER_PORT 12138 12 13 14 int main(int argc,char *argv[]) 15 { 16 int sockfd; 17 struct hostent * host; 18 struct sockaddr_in servAddr; 19 int pid; 20 char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE]; 21 int sendSize,recvSize; 22 23 host=gethostbyname(argv[1]); 24 if(host==NULL) 25 { 26 perror("get host error"); 27 exit(-1); 28 } 29 30 sockfd=socket(AF_INET,SOCK_STREAM,0); 31 if(sockfd==-1) 32 { 33 perror("創(chuàng)建socket失敗"); 34 exit(-1); 35 } 36 37 servAddr.sin_family=AF_INET; 38 servAddr.sin_port=htons(SERVER_PORT); 39 servAddr.sin_addr=*((struct in_addr *)host->h_addr); 40 bzero(&(servAddr.sin_zero),8); 41 42 if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))==-1) 43 { 44 perror("connect 失敗"); 45 exit(-1); 46 } 47 48 if((pid=fork())<0) 49 { 50 perror("fork error"); 51 } 52 else if(pid>0) 53 { 54 while(1) 55 { 56 fgets(sendBuf,MAX_DATA_SIZE,stdin); 57 sendSize=send(sockfd,sendBuf,MAX_DATA_SIZE,0); 58 if(sendSize<0) 59 perror("send error"); 60 memset(sendBuf,0,sizeof(sendBuf)); 61 } 62 } 63 else 64 { 65 while(1) 66 { 67 recvSize=recv(sockfd,recvBuf,MAX_DATA_SIZE,0); 68 if(recvSize<0) 69 perror("recv error"); 70 printf("接收到的信息:%s",recvBuf); 71 memset(recvBuf,0,sizeof(recvBuf)); 72 } 73 } 74 return 0; 75 }server.cpp
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/socket.h> 4 #include <sys/epoll.h> 5 #include <string.h> 6 #include <netinet/in.h> 7 #include <string.h> 8 #include <netdb.h> 9 #include <arpa/inet.h> 10 #include <unistd.h> 11 12 #define SERVER_PORT 12138 13 #define CON_QUEUE 20 14 #define MAX_DATA_SIZE 4096 15 #define MAX_EVENTS 500 16 17 void AcceptConn(int sockfd,int epollfd); 18 void Handle(int clientfd); 19 20 int main(int argc,char *argv[]) 21 { 22 struct sockaddr_in serverSockaddr; 23 int sockfd; 24 25 //創(chuàng)建socket 26 if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) 27 { 28 perror("創(chuàng)建socket失敗"); 29 exit(-1); 30 } 31 serverSockaddr.sin_family=AF_INET; 32 serverSockaddr.sin_port=htons(SERVER_PORT); 33 serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY); 34 bzero(&(serverSockaddr.sin_zero),8); 35 36 int on=0; 37 setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); 38 39 if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-1) 40 { 41 perror("綁定失敗"); 42 exit(-1); 43 } 44 45 if(listen(sockfd,CON_QUEUE)==-1) 46 { 47 perror("監(jiān)聽失敗"); 48 exit(-1); 49 } 50 51 //epoll初始化 52 int epollfd;//epoll描述符 53 struct epoll_event eventList[MAX_EVENTS]; 54 epollfd=epoll_create(MAX_EVENTS); 55 struct epoll_event event; 56 event.events=EPOLLIN|EPOLLET; 57 event.data.fd=sockfd;//把server socket fd封裝進(jìn)events里面 58 59 //epoll_ctl設(shè)置屬性,注冊(cè)事件 60 if(epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event)<0) 61 { 62 printf("epoll 加入失敗 fd:%d\n",sockfd); 63 exit(-1); 64 } 65 66 while(1) 67 { 68 int timeout=300;//設(shè)置超時(shí);在select中使用的是timeval結(jié)構(gòu)體 69 //epoll_wait epoll處理 70 //ret會(huì)返回在規(guī)定的時(shí)間內(nèi)獲取到IO數(shù)據(jù)的個(gè)數(shù),并把獲取到的event保存在eventList中,注意在每次執(zhí)行該函數(shù)時(shí)eventList都會(huì)清空,由epoll_wait函數(shù)填寫。 71 //而不清除已經(jīng)EPOLL_CTL_ADD到epollfd描述符的其他加入的文件描述符。這一點(diǎn)與select不同,select每次都要進(jìn)行FD_SET,具體可看我的select講解。 72 //epoll里面的文件描述符要手動(dòng)通過EPOLL_CTL_DEL進(jìn)行刪除。 73 int ret=epoll_wait(epollfd,eventList,MAX_EVENTS,timeout); 74 75 if(ret<0) 76 { 77 perror("epoll error\n"); 78 break; 79 } 80 else if(ret==0) 81 { 82 //超時(shí) 83 continue; 84 } 85 86 //直接獲取了事件數(shù)量,給出了活動(dòng)的流,這里就是跟selec,poll區(qū)別的關(guān)鍵 //select要用遍歷整個(gè)數(shù)組才知道是那個(gè)文件描述符有事件。而epoll直接就把有事件的文件描述符按順序保存在eventList中 87 for(int i=0;i<ret;i++) 88 { 89 //錯(cuò)誤輸出 90 if((eventList[i].events & EPOLLERR) || (eventList[i].events & EPOLLHUP) || !(eventList[i].events & EPOLLIN)) 91 { 92 printf("epoll error\n"); 93 close(eventList[i].data.fd); 94 exit(-1); 95 } 96 97 if(eventList[i].data.fd==sockfd) 98 { 99 //這個(gè)是判斷sockfd的,主要是用于接收客戶端的連接accept 100 AcceptConn(sockfd,epollfd); 101 } 102 else //里面可以通過判斷eventList[i].events&EPOLLIN 或者 eventList[i].events&EPOLLOUT 來區(qū)分當(dāng)前描述符的連接是對(duì)應(yīng)recv還是send 103 { 104 //其他所有與客戶端連接的clientfd文件描述符 105 //獲取數(shù)據(jù)等操作 106 //如需不接收客戶端發(fā)來的數(shù)據(jù),但是不關(guān)閉連接。 107 //epoll_ctl(epollfd, EPOLL_CTL_DEL,eventList[i].data.fd,eventList[i]); 108 //Handle對(duì)各個(gè)客戶端發(fā)送的數(shù)據(jù)進(jìn)行處理 109 Handle(eventList[i].data.fd); 110 } 111 } 112 } 113 114 close(epollfd); 115 close(sockfd); 116 return 0; 117 } 118 119 void AcceptConn(int sockfd,int epollfd) 120 { 121 struct sockaddr_in sin; 122 socklen_t len=sizeof(struct sockaddr_in); 123 bzero(&sin,len); 124 125 int confd=accept(sockfd,(struct sockaddr *)&sin,&len); 126 127 if(confd<0) 128 { 129 perror("connect error\n"); 130 exit(-1); 131 } 132 133 //把客戶端新建立的連接添加到EPOLL的監(jiān)聽中 134 struct epoll_event event; 135 event.data.fd=confd; 136 event.events=EPOLLIN|EPOLLET; 137 epoll_ctl(epollfd,EPOLL_CTL_ADD,confd,&event); 138 return ; 139 } 140 141 void Handle(int clientfd) 142 { 143 int recvLen=0; 144 char recvBuf[MAX_DATA_SIZE]; 145 memset(recvBuf,0,sizeof(recvBuf)); 146 recvLen=recv(clientfd,(char *)recvBuf,MAX_DATA_SIZE,0); 147 if(recvLen==0) 148 return ; 149 else if(recvLen<0) 150 { 151 perror("recv Error"); 152 exit(-1); 153 } 154 //各種處理 155 printf("接收到的數(shù)據(jù):%s \n",recvBuf); 156 return ; 157 }?
epoll參考資料
http://blog.csdn.net/mmz_xiaokong/article/details/8704988
http://blog.csdn.net/mmz_xiaokong/article/details/8704455
http://www.cppblog.com/converse/archive/2008/10/13/63928.html
http://blog.csdn.net/haoahua/article/details/2037704
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
epoll為什么這么快:?http://www.cppblog.com/converse/archive/2008/10/12/63836.html
?
本文地址:?http://www.cnblogs.com/wunaozai/p/3895860.html
總結(jié)
以上是生活随笔為你收集整理的socket网络编程--epoll小结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 输卵管堵塞疼艾草有什么用
- 下一篇: I/O多路转接之poll 函数