您的快遞(高並發服務器之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> #includeView Code<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; }
程序中封裝了包裹函數,有需要的請評論留言。
二、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
總結:需要包裹函數、客戶端等程序的,歡迎留言
您的快遞(高並發服務器之poll和epoll)請簽收