日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

epoll的使用实例

發布時間:2025/3/8 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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,表示具有執行后關閉的特性

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端是一個回射服務器:

#include<sys/types.h> #include<sys/socket.h> #include<sys/select.h> #include<netinet/in.h> #include<arpa/inet.h> #include<poll.h> #include<stdlib.h> #include<stdio.h> #include<string.h> #include<errno.h> #include<unistd.h> #include <fcntl.h> #include<sys/epoll.h> #include<vector> #include<algorithm> // #include"comm.h" void setfdisblock(int fd, bool isblock) {int flags = fcntl(fd, F_GETFL);if(flags < 0)return;if(isblock) // 阻塞 {flags &= ~O_NONBLOCK;}else // 非阻塞 {flags |= O_NONBLOCK;} if(fcntl(fd, F_SETFL, flags)<0)perror("fcntl set"); } typedef std::vector<struct epoll_event> EventList; #define CLIENTCOUNT 2048 #define MAX_EVENTS 2048 int main(int argc, char **argv) {int listenfd = socket(AF_INET, SOCK_STREAM, 0);if(listenfd < 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(bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < 0){perror("bind");return -2;}if(listen(listenfd, 20) < 0){perror("listen");return -3;}struct sockaddr_in connaddr;socklen_t len = sizeof(connaddr);int i = 0, ret = 0;std::vector<int> clients; // 客戶端存儲的迭代器int epollfd = epoll_create1(EPOLL_CLOEXEC);//int epollfd = epoll_create(MAX_EVENTS);// 設置連接數struct epoll_event event;event.events = EPOLLIN|EPOLLET;event.data.fd = listenfd;if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0){perror("epoll_ctl");return -2;}EventList events(16);int count = 0;int nready = 0;char buf[1024] = {0};int conn = 0;while(1){nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);if(nready == -1){perror("epoll_wait");return -3;}if(nready == 0) // 肯定不會走到這里,因為上面沒設置超時時間 {continue;}if((size_t)nready == events.size()) // 對clients進行擴容events.resize(events.size() * 2);for(i = 0; i < nready; i++){if(events[i].data.fd == listenfd){conn = accept(listenfd, (struct sockaddr*)&connaddr, &len);if(conn < 0){perror("accept");return -4;}char strip[64] = {0};char *ip = inet_ntoa(connaddr.sin_addr);strcpy(strip, ip);printf("client connect, conn:%d,ip:%s, port:%d, count:%d\n", conn, strip,ntohs(connaddr.sin_port), ++count);clients.push_back(conn);// 設為非阻塞setfdisblock(conn, false);// add fd in eventsevent.data.fd = conn;// 這樣在epoll_wait返回時就可以直接用了event.events = EPOLLIN|EPOLLET;epoll_ctl(epollfd, EPOLL_CTL_ADD, conn, &event);}else if(events[i].events & EPOLLIN){conn = events[i].data.fd;if(conn < 0)continue;ret = read(conn, buf, sizeof(buf));if(ret == -1){perror("read");return -5;}else if(ret == 0){printf("client close remove:%d, count:%d\n", conn, --count);close(conn);event = events[i];epoll_ctl(epollfd, EPOLL_CTL_DEL, conn, &event);clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());}write(conn, buf, sizeof(buf));memset(buf, 0, sizeof(buf));}} }close(listenfd);return 0; }

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的使用实例的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。