epoll的使用实例
在網絡編程中通常需要處理很多個連接,可以用select和poll來處理多個連接。但是select都受進程能打開的最大文件描述符個數的限制。并且select和poll效率會隨著監聽fd的數目增多而下降。
解決方法就是用epoll
1.epoll
是Linux內核為處理大批量文件描述符而做了改進的poll,是Linux下多路復用IO接口select/poll的增強版本。
2.epoll、select、poll的區別:
1)相比于select和poll,epoll最大的好處在于不會隨著監聽fd數目的增加而降低效率
2)內核中的select與poll的實現是采用輪詢來處理的,輪詢數目越多耗時就越多
3)epoll的實現是基于回調的,如果fd有期望的事件發生就會通過回調函數將其加入epoll就緒隊列中。也就是說它只關心“活躍”的fd,與fd數目無關。
4)內核空間用戶空間數據拷貝問題,如何讓內核把fd消息通知給用戶空間?select和poll采取了內存拷貝的方法,而epoll采用的是共享內存的方式。速度快
5)epoll不僅會告訴應用程序有I/o事件的到來,還會告訴應用程序相關的信息,這寫信息是應用程序填充的,因此根據這寫信息應用程序就能直接定位到事件,而不必遍歷整個fd集合。
6)poll和select受進程能打開的最大文件描述符的限制。select還受FD_SETSIZE的限制。但是epoll的限制的最大可以打開文件的數目(cat /proc/sys/fs/file-max進行查看)。
3.epoll的工作模式
有下面兩種工作模式,默認是水平觸發。
EPOLLLT:水平觸發(Level Triggered),事件沒有處理完也會觸發。完全靠kernel epoll驅動,應用程序只需要處理從epoll_wait返回的fds。這些fds我們認為他們處于就緒狀態。
LT模式支持阻塞fd和非阻塞fd。
EPOLLET:邊沿觸發(Edge Triggered)。只有空閑->就緒才會觸發。應用程序需要維護一個就緒隊列。
此模式下,系統僅僅通知應用程序哪些fds變成了就緒狀態,一旦fd變成了就緒狀態,epoll將不再關注這個fd的任何狀態信息(從epoll隊列移除)。
直到應用程序通過讀寫操作觸發EAGAIN狀態,epoll認為這個fd又變成空閑狀態,那么epoll又重新關注這個fd的狀態變化(重新加入epoll隊列中)。
隨著epoll_wait的返回,隊列中的fds是減少的,所以在大并發的系統中,EPOLLET更有優勢。但是對程序員的要求也更高。
ET模式只支持non-block socket,以避免由于一個文件句柄的阻塞讀/阻塞寫把處理多個文件描述符的任務餓死。
4.如何使用
主要是下面幾個函數和結構體。
?#include <sys/epoll.h>
?????? int epoll_create(int size);
?????? int epoll_create1(int flags);
?????? int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
?????? int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
?????? The struct epoll_event is defined as :
?????????? typedef union epoll_data {
?????????????? void????*ptr;
?????????????? int??????fd;
?????????????? uint32_t u32;
?????????????? uint64_t u64;
?????????? } epoll_data_t;
?????????? struct epoll_event {
?????????????? uint32_t???? events;????/* Epoll events */
?????????????? epoll_data_t data;??????/* User data variable */
?????????? };
1)int epoll_create(int size);
創建一個epoll的句柄,
size:告訴內核這個句柄可以監聽的數目一共多大。
注意這里返回一個句柄,也是一個文件描述符,后面還是要關閉的。
2)int epoll_create1(int flags);
上面那個的加強版,flags可以是EPOLL_CLOEXEC,表示具有執行后關閉的特性
??????????????Set the close-on-exec (FD_CLOEXEC) flag on the new file descrip‐
??????????????tor.??See the description of the O_CLOEXEC flag in??open(2)??for
??????????????reasons why this may be useful
?
3)?int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
注冊函數,用來對創建的epollfd進行操作的。
epfd:create出來的fd
op:執行的動作,由3個宏來表示
EPOLL_CTL_ADD:注冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
fd:要監聽的fd
event:表示需要監聽的事件
結構見上面。
events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
4)??int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
用來等待事件的產生
epfd:create出來的epollfd
events:從內核得到事件的集合。一般的一個數組
maxevents:用來告訴內核events有多大,不能大于epoll_create()時的size。
timeout是超時時間:單位的毫秒,為0表示立即返回,-1表示阻塞。
返回:0表示超時,>0表示需要處理的事件數目。<0表示出錯
5.實例:
server端是一個回射服務器:
client:連接服務器,通過終端發送數據給server,并接收server發來的數據。
#include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/select.h> #include<stdlib.h> #include<stdio.h> #include<string.h> void select_test(int conn) {int ret = 0;fd_set rset;FD_ZERO(&rset);int nready;int maxfd = conn;int fd_stdin = fileno(stdin);if(fd_stdin > maxfd){maxfd = fd_stdin;}int len = 0;char readbuf[1024] = {0};char writebuf[1024] = {0};while(1){FD_ZERO(&rset);FD_SET(fd_stdin, &rset);FD_SET(conn, &rset);nready = select(maxfd+1, &rset, NULL, NULL, NULL);if(nready == -1){perror("select");exit(0);}else if(nready == 0){continue; }if(FD_ISSET(conn, &rset)){ret = read(conn, readbuf, sizeof(readbuf));if(ret == 0){printf("server close1\n");break;}else if(-1 == ret){perror("read1");break;} fputs(readbuf, stdout);memset(readbuf, 0, sizeof(readbuf));} if(FD_ISSET(fd_stdin, &rset)){ read(fd_stdin, writebuf, sizeof(writebuf)); len = strlen(writebuf);ret = write(conn, writebuf, len);if(ret == 0){printf("server close3\n");break;}else if(-1 == ret){perror("write");break;}memset(writebuf, 0, sizeof(writebuf)); }} } int sockfd = 0; int main(int argc, char **argv) {sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket");return -1;}unsigned short sport = 8080;if(argc == 2){sport = atoi(argv[1]);}struct sockaddr_in addr;addr.sin_family = AF_INET;printf("port = %d\n", sport);addr.sin_port = htons(sport);addr.sin_addr.s_addr = inet_addr("127.0.0.1");if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0){perror("connect");return -2;}struct sockaddr_in addr2;socklen_t len = sizeof(addr2);if(getpeername(sockfd, (struct sockaddr*)&addr2, &len) < 0){perror("getsockname");return -3;}printf("Server: port:%d, ip:%s\n", ntohs(addr2.sin_port), inet_ntoa(addr2.sin_addr));select_test(sockfd);close(sockfd);return 0; }還可以進行暴力連接測試。可以參考:?http://www.cnblogs.com/xcywt/p/8120166.html 這個例子的客戶端就是暴力連接測試的。
測試結果可以和select實現的server進行比較,發現在虛擬機上epollserver好像沒有快多少(我也暫時沒找到原因)。
但是在服務器上,還是有很快的,效率提升了很多。
總結
以上是生活随笔為你收集整理的epoll的使用实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于mac注册机core keygen在
- 下一篇: etcd集群部署与遇到的坑