poll模型详解
1.poll模型原理
poll模型是基于select最大文件描述符限制提出的,跟select一樣,只是將select使用的三個基于位的文件描述符改為使用一個數組的形式,對于各種可能的事件進行了一個包裝
#include <sys/poll.h> int poll (struct pollfd *fds, unsigned int nfds, int timeout);
參數說明:
第一個參數fds為一個pollfd結構數組,用來保存文件描述符
第二個參數nfds為pollfd結構體數組+1
第三個參數timeout為poll等待時間
返回值:
正常返回值為輪詢文件描述符結構有事件發送的個數,-1返回失敗
和select()不一樣,poll()沒有使用低效的三個基于位的文件描述符set,而是采用了一個單獨的結構體pollfd數組,由fds指針指向這個組。pollfd結構體定義如下:
#include <sys/poll.h>struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ }; 每一個pollfd結構體指定了一個被監視的文件描述符,可以傳遞多個結構體,指示poll()監視多個文件描述符。每個結構體的events域是監視該文件描述符的事件掩碼,由用戶來設置這個域。revents域是文件描述符的操作結果事件掩碼。內核在調用返回時設置這個域。events域中請求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN 有數據可讀。
POLLRDNORM 有普通數據可讀。
POLLRDBAND 有優先數據可讀。
POLLPRI 有緊迫數據可讀。
POLLOUT 寫數據不會導致阻塞。
POLLWRNORM 寫普通數據不會導致阻塞。
POLLWRBAND 寫優先數據不會導致阻塞。
POLLMSG SIGPOLL消息可用。
此外,revents域中還可能返回下列事件:
POLLER 指定的文件描述符發生錯誤。
POLLHUP 指定的文件描述符掛起事件。
POLLNVAL 指定的文件描述符非法。
這些事件在events域中無意義,因為它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價于select()的讀事件,POLLOUT | POLLWRBAND等價于select()的寫事件。POLLIN等價于POLLRDNORM | POLLRDBAND,而POLLOUT則等價于POLLWRNORM。
例如,要同時監視一個文件描述符是否可讀和可寫,我們可以設置events為POLLIN | POLLOUT。在poll返回時,我們可以檢查revents中的標志,對應于文件描述符請求的events結構體。如果POLLIN事件被設置,則文件描述符可以被讀取而不阻塞。如果POLLOUT被設置,則文件描述符可以寫入而不導致阻塞。這些標志并不是互斥的:它們可能被同時設置,表示這個文件描述符的讀取和寫入操作都會正常返回而不阻塞。
timeout參數指定等待的毫秒數,無論I/O是否準備好,poll都會返回。timeout指定為負數值表示無限超時;timeout為0指示poll調用立即返回并列出準備好I/O的文件描述符,但并不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。
返回值和錯誤代碼
成功時,poll()返回結構體中revents域不為0的文件描述符個數;如果在超時前沒有任何事件發生,poll()返回0;失敗時,poll()返回-1,并設置errno為下列值之一:
EBADF 一個或多個結構體中指定的文件描述符無效。
EFAULT fds指針指向的地址超出進程的地址空間。
EINTR 請求的事件之前產生一個信號,調用可以重新發起。
EINVAL nfds參數超出PLIMIT_NOFILE值。
ENOMEM 可用內存不足,無法完成請求。
2.poll工作流程
1.定義一個客戶端連接數組
struct pollfd clients[OPEN_MAX];
nready = poll(clients, maxi + 1, -1);
3.使用實例
引用一個簡單的poll服務器例子
#include <unistd.h> #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definitions */ #include <netinet/in.h> /* sockaddr_in{} and other Internet defns */ #include <arpa/inet.h> /* inet(3) functions */ #include <stdlib.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <poll.h> /* poll function */ #include <limits.h> #define MAXLINE 10240 #ifndef OPEN_MAX #define OPEN_MAX 40960 #endif void handle(struct pollfd* clients, int maxClient, int readyClient); int main(int argc, char **argv) { int servPort = 6888; int listenq = 1024; int listenfd, connfd; struct pollfd clients[OPEN_MAX]; int maxi; socklen_t socklen = sizeof(struct sockaddr_in); struct sockaddr_in cliaddr, servaddr; char buf[MAXLINE]; int nready; bzero(&servaddr, socklen); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(servPort); listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { perror("socket error"); } int opt = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt error"); } if(bind(listenfd, (struct sockaddr *) &servaddr, socklen) == -1) { perror("bind error"); exit(-1); } if (listen(listenfd, listenq) < 0) { perror("listen error"); } clients[0].fd = listenfd; clients[0].events = POLLIN; int i; for (i = 1; i< OPEN_MAX; i++) clients[i].fd = -1; maxi = listenfd + 1; printf("pollechoserver startup, listen on port:%d\n", servPort); printf("max connection is %d\n", OPEN_MAX); for ( ; ; ) { nready = poll(clients, maxi + 1, -1); //printf("nready is %d\n", nready); if (nready == -1) { perror("poll error"); } if (clients[0].revents & POLLIN) { connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &socklen); sprintf(buf, "accept form %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port); printf(buf, ""); for (i = 0; i < OPEN_MAX; i++) { if (clients[i].fd == -1) { clients[i].fd = connfd; clients[i].events = POLLIN; break; } } if (i == OPEN_MAX) { fprintf(stderr, "too many connection, more than %d\n", OPEN_MAX); close(connfd); continue; } if (i > maxi) maxi = i; --nready; } handle(clients, maxi, nready); } } void handle(struct pollfd* clients, int maxClient, int nready) { int connfd; int i, nread; char buf[MAXLINE]; if (nready == 0) return; for (i = 1; i< maxClient; i++) { connfd = clients[i].fd; if (connfd == -1) continue; if (clients[i].revents & (POLLIN | POLLERR)) { nread = read(connfd, buf, MAXLINE);//讀取客戶端socket流 if (nread < 0) { perror("read error"); close(connfd); clients[i].fd = -1; continue; } if (nread == 0) { printf("client close the connection"); close(connfd); clients[i].fd = -1; continue; } write(connfd, buf, nread);//響應客戶端 if (--nready <= 0)//沒有連接需要處理,退出循環 break; } } }4.總結討論
1.poll與select,epoll比較
poll主要是解決select的最大文件描述符限制提出的,與select一樣都是輪詢文件描述符,所以效率方便也無法與epoll相比,另外poll不具備移植性,只有在linux系統上有實現,在windows系統沒有poll模型的實現
2.poll模型效率
poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項并繼續遍歷,如果遍歷完所有fd后沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。
它沒有最大連接數的限制,原因是它是基于鏈表來存儲的,但是同樣有如下缺點:
1.大量的fd的數組被整體復制于用戶態和內核地址空間之間,而不管這樣的復制是不是有意義。? ?
2.poll還有一個特點是“水平觸發”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。
總結
- 上一篇: epoll模型详解
- 下一篇: 解决ubuntu无法修改分辨率为1920