您的快遞(高併發伺服器之poll和epoll)請簽收
前言
之前已經介紹過select函式,請參考這篇部落格:https://www.cnblogs.com/liudw-0215/p/9661583.html,原理都是類似的,有時間先閱讀下那篇部落格,以便於理解這篇部落格。
一、poll函式
1、函式說明
原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
引數說明:
引數fds:
struct pollfd {
int fd; /* 檔案描述符 */
short events; /* 監控的事件 */
short revents; /* 監控事件中滿足條件返回的事件 */
};
POLLIN 普通或帶外優先資料可讀 , 即 POLLRDNORM | POLLRDBAND
POLLRDNORM 資料可讀
POLLRDBAND 優先順序帶資料可讀
POLLPRI 高優先順序可讀資料
POLLOUT 普通或帶外資料可寫
POLLWRNORM 資料可寫
POLLWRBAND 優先順序帶資料可寫
POLLERR 發生錯誤
POLLHUP 發生掛起
POLLNVAL 描述字不是一個開啟的檔案
nfds: 監控陣列中有多少檔案描述符需要被監控
timeout: 毫秒級等待
-1 :阻塞等, #define INFTIM -1 Linux 中沒有定義此巨集
0 :立即返回,不阻塞程序
>0 :等待指定毫秒數,如當前系統時間精度不夠毫秒,向上取值
如果不再監控某個檔案描述符時,可以把 pollfd 中, fd 設定為 -1 , poll 不再監控此 pollfd ,下次返回時,把 revents 設定為 0 。
2、程式示例
理解select之後,再解poll就很簡單了,服務端程式碼如下:
/* server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <poll.h> #include <errno.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc, char *argv[]) { int i, j, maxi, listenfd, connfd, sockfd; int nready; ssize_t n; char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen; struct pollfd client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); client[0].fd = listenfd; client[0].events = POLLRDNORM;/* listenfd監聽普通讀事件 */ for (i = 1; i < OPEN_MAX; i++) client[i].fd = -1;/* 用-1初始化client[]裡剩下元素 */ maxi = 0;/* client[]陣列有效元素中最大元素下標 */ for ( ; ; ) { nready = poll(client, maxi+1, -1);/* 阻塞 */ if (client[0].revents & POLLRDNORM) {/* 有客戶端連結請求 */ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 1; i < OPEN_MAX; i++) { if (client[i].fd < 0) { client[i].fd = connfd;/* 找到client[]中空閒的位置,存放accept返回的connfd */ break; } } if (i == OPEN_MAX) perr_exit("too many clients"); client[i].events = POLLRDNORM;/* 設定剛剛返回的connfd,監控讀事件 */ if (i > maxi) maxi = i;/* 更新client[]中最大元素下標 */ if (--nready <= 0) continue;/* 沒有更多就緒事件時,繼續回到poll阻塞 */ } for (i = 1; i <= maxi; i++) {/* 檢測client[] */ if ((sockfd = client[i].fd) < 0) continue; if (client[i].revents & (POLLRDNORM | POLLERR)) { if ((n = Read(sockfd, buf, MAXLINE)) < 0) { if (errno == ECONNRESET) { /* 當收到 RST標誌時 */ /* connection reset by client */ printf("client[%d] aborted connection\n", i); Close(sockfd); client[i].fd = -1; } else { perr_exit("read error"); } } else if (n == 0) { /* connection closed by client */ printf("client[%d] closed connection\n", i); Close(sockfd); client[i].fd = -1; } else { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Writen(sockfd, buf, n); } if (--nready <= 0) break;/* no more readable descriptors */ } } } return 0; } View Code
程式中封裝了包裹函式,有需要的請評論留言。
二、epoll函式
1、介紹
epoll 是 Linux 下多路複用 IO 介面 select/poll 的增強版本,它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統 CPU 利用率,因為它會複用檔案描述符集合來傳遞結果而不用迫使開發者每次等待事件之前都必須重新準備要被偵聽的檔案描述符集合,另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被核心 IO 事件非同步喚醒而加入 Ready 佇列的描述符集合就行了。
目前 epell 是 linux 大規模併發網路程式中的熱門首選模型。
epoll 除了提供 select/poll 那種 IO 事件的電平觸發( Level Triggered )外,還提供了邊沿觸發( Edge Triggered ),這就使得使用者空間程式有可能快取 IO 狀態,減少 epoll_wait/epoll_pwait 的呼叫,提高應用程式效率。
2、函式說明
跟select和poll不一樣,epoll不是一個函式,需要三個函式一起來實現,分別為 epoll_create、epoll_ctl和epoll_wait,下面分別來說明這三個函式。
(1)epoll_create函式
功能: 建立一個 epoll ,引數 size 用來告訴核心監聽的檔案描述符的個數,跟記憶體大小有關。
原型: int epoll_create(int size)
又到了上圖時間了,如下圖:PS:依舊是全部落格園最醜圖,不接受反駁。
epoll_create返回的epfd,其實建立了紅黑樹,是它的根節點。
(2)epoll_ctl函式
功能: 控制某個 epoll 監控的檔案描述符上的事件:註冊、修改、刪除。
原型: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
引數說明:
epfd : 為 epoll_creat 的
op : 表示動作,用 3 個巨集來表示:
EPOLL_CTL_ADD ( 註冊新的 fd 到 epfd) ,
EPOLL_CTL_MOD ( 修改已經註冊的 fd 的監聽事件 ) ,
EPOLL_CTL_DEL ( 從 epfd 刪除一個 fd) ;
event : 告訴核心需要監聽的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
EPOLLIN : 表示對應的檔案描述符可以讀(包括對端 SOCKET 正常關閉)
EPOLLOUT : 表示對應的檔案描述符可以寫
EPOLLPRI : 表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來)
EPOLLERR : 表示對應的檔案描述符發生錯誤
EPOLLHUP : 表示對應的檔案描述符被結束通話;
EPOLLET : 將 EPOLL 設為邊緣觸發 (Edge Triggered) 模式,這是相對於水平觸發 (Level Triggered) 而言的
EPOLLONESHOT :只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個 socket 的話,需要再次把這個 socket 加入到 EPOLL 佇列裡
(3)epoll_wait函式
功能:等待所監控檔案描述符上有事件的產生
原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
引數說明:
events : 用來存核心得到事件的集合,
maxevents : 告之核心這個 events 有多大,這個 maxevents 的值不能大於建立 epoll_create() 時的 size ,
timeout : 是超時時間
-1 : 阻塞
0 : 立即返回,非阻塞
>0 : 指定毫秒
返回值: 成功返回有多少檔案描述符就緒,時間到時返回 0 ,出錯返回 -1
3、示例程式
服務端程式如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <errno.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc, char *argv[]) { int i, j, maxi, listenfd, connfd, sockfd; int nready, efd, res; ssize_t n; char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen; int client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; struct epoll_event tep, ep[OPEN_MAX]; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); Listen(listenfd, 20); for (i = 0; i < OPEN_MAX; i++) client[i] = -1; maxi = -1; efd = epoll_create(OPEN_MAX); if (efd == -1) perr_exit("epoll_create"); tep.events = EPOLLIN; tep.data.fd = listenfd; res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); if (res == -1) perr_exit("epoll_ctl"); while (1) { nready = epoll_wait(efd, ep, OPEN_MAX, -1); /* 阻塞監聽 */ if (nready == -1) perr_exit("epoll_wait"); for (i = 0; i < nready; i++) { if (!(ep[i].events & EPOLLIN)) continue; if (ep[i].data.fd == listenfd) { clilen = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (j = 0; j < OPEN_MAX; j++) { if (client[j] < 0) { client[j] = connfd; /* save descriptor */ break; } } if (j == OPEN_MAX) perr_exit("too many clients"); if (j > maxi) maxi = j;/* max index in client[] array */ tep.events = EPOLLIN; tep.data.fd = connfd; res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); if (res == -1) perr_exit("epoll_ctl"); } else { sockfd = ep[i].data.fd; n = Read(sockfd, buf, MAXLINE); if (n == 0) { for (j = 0; j <= maxi; j++) { if (client[j] == sockfd) { client[j] = -1; break; } } res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); if (res == -1) perr_exit("epoll_ctl"); Close(sockfd); printf("client[%d] closed connection\n", j); } else { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Writen(sockfd, buf, n); } } } } close(listenfd); close(efd); return 0; } View Code
總結:需要包裹函式、客戶端等程式的,歡迎留言