標籤:%s string linux 函式宣告 iad 之一 添加 根據 write
1、基本知識
poll是Linux中的字元裝置驅動中的一個函數。Linux 2.5.44版本後,poll被epoll取代。和select實現的功能差不多,poll的作用是把當前的檔案指標掛到等待隊列。
poll的機制與select類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大檔案描述符數量的限制。poll和select同樣存在一個缺點就是,包含大量檔案描述符的數組被整體複製於使用者態和核心的地址空間之間,而不論這些檔案描述符是否就緒,它的開銷隨著檔案描述符數量的增加而線性增大。
poll優點
(1)poll() 不要求開發人員計算最大檔案描述符加一的大小。
(2)poll() 在應付大數目的檔案描述符的時候速度更快,相比於select。
(3)它沒有最大串連數的限制,原因是它是基於鏈表來儲存的。
(4)在調用函數時,只需要對參數進行一次設定就好了
poll缺點
(1)大量的fd的數組被整體複製於使用者態和核心地址空間之間,而不管這樣的複製是不是有意義。
(2)與select一樣,poll返回後,需要輪詢pollfd來擷取就緒的描述符,這樣會使效能下降
(3)同時串連的大量用戶端在一時刻可能只有很少的就緒狀態,因此隨著監視的描述符數量的增長,其效率也會線性下降
elect優點:
目前幾乎在所有的平台上支援,其良好跨平台支援也是它的一個優點
select缺點:
(1)每次調用 select(),都需要把 fd 集合從使用者態拷貝到核心態,這個開銷在 fd 很多時會很大,同時每次調用 select() 都需要在核心遍曆傳遞進來的所有 fd,這個開銷在 fd 很多時也很大。
(2)單個進程能夠監視的檔案描述符的數量存在最大限制,在 Linux 上一般為 1024,可以通過修改宏定義甚至重新編譯核心的方式提升這一限制,但是這樣也會造成效率的降低
(3)select函數在每次調用之前都要對參數進行重新設定,這樣做比較麻煩,而且會降低效能
2、poll函數
函數格式如下所示:
# include <poll.h>int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
pollfd結構體定義如下:
struct pollfd { int fd; /* 檔案描述符 */ short events; /* 等待的事件 */ short revents; /* 實際發生了的事件 */} ;
每一個pollfd結構體指定了一個被監視的檔案描述符,可以傳遞多個結構體,指示poll()監視多個檔案描述符。每個結構體的events域是監視該檔案描述符的事件掩碼,由使用者來設定這個域。revents域是檔案描述符的操作結果事件掩碼,核心在調用返回時設定這個域。events域中請求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN 有資料可讀。
POLLRDNORM 有普通資料可讀。
POLLRDBAND 有優先資料可讀。
POLLPRI 有緊迫資料可讀。
POLLOUT 寫資料不會導致阻塞。
POLLWRNORM 寫普通資料不會導致阻塞。
POLLWRBAND 寫優先資料不會導致阻塞。
POLLMSGSIGPOLL 訊息可用。
此外,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指定為負數值表示無限逾時,使poll()一直掛起直到一個指定事件發生;timeout為0指示poll調用立即返回並列出準備好I/O的檔案描述符,但並不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。
傳回值和錯誤碼
成功時,poll()返回結構體中revents域不為0的檔案描述符個數;如果在逾時前沒有任何事件發生,poll()返回0;失敗時,poll()返回-1,並設定errno為下列值之一:
EBADF 一個或多個結構體中指定的檔案描述符無效。
EFAULTfds 指標指向的地址超出進程的地址空間。
EINTR 請求的事件之前產生一個訊號,調用可以重新發起。
EINVALnfds 參數超出PLIMIT_NOFILE值。
ENOMEM 可用記憶體不足,無法完成請求。
3、測出程式
編寫一個echo server程式,功能是用戶端向伺服器發送資訊,伺服器接收輸出並原樣發送回給用戶端,用戶端接收到輸出到終端。
伺服器端程式如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <sys/socket.h>#include <poll.h>#include <unistd.h>#include <sys/types.h>#define IPADDRESS "127.0.0.1"#define PORT 8787#define MAXLINE 1024#define LISTENQ 5#define OPEN_MAX 1000#define INFTIM -1//函式宣告//建立通訊端並進行綁定static int socket_bind(const char* ip,int port);//IO多工pollstatic void do_poll(int listenfd);//處理多個串連static void handle_connection(struct pollfd *connfds,int num);int main(int argc,char *argv[]){ int listenfd,connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; listenfd = socket_bind(IPADDRESS,PORT); listen(listenfd,LISTENQ); do_poll(listenfd); return 0;}static int socket_bind(const char* ip,int port){ int listenfd; struct sockaddr_in servaddr; listenfd = socket(AF_INET,SOCK_STREAM,0); if (listenfd == -1) { perror("socket error:"); exit(1); } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET,ip,&servaddr.sin_addr); servaddr.sin_port = htons(port); if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) { perror("bind error: "); exit(1); } return listenfd;}static void do_poll(int listenfd){ int connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; struct pollfd clientfds[OPEN_MAX]; int maxi; int i; int nready; //添加監聽描述符 clientfds[0].fd = listenfd; clientfds[0].events = POLLIN; //初始化客戶串連描述符 for (i = 1;i < OPEN_MAX;i++) clientfds[i].fd = -1; maxi = 0; //迴圈處理 for ( ; ; ) { //擷取可用描述符的個數 nready = poll(clientfds,maxi+1,INFTIM); if (nready == -1) { perror("poll error:"); exit(1); } //測試監聽描述符是否準備好 if (clientfds[0].revents & POLLIN) { cliaddrlen = sizeof(cliaddr); //接受新的串連 if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1) { if (errno == EINTR) continue; else { perror("accept error:"); exit(1); } } fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //將新的串連描述符添加到數組中 for (i = 1;i < OPEN_MAX;i++) { if (clientfds[i].fd < 0) { clientfds[i].fd = connfd; break; } } if (i == OPEN_MAX) { fprintf(stderr,"too many clients.\n"); exit(1); } //將新的描述符添加到讀描述符集合中 clientfds[i].events = POLLIN; //記錄客戶串連通訊端的個數 maxi = (i > maxi ? i : maxi); if (--nready <= 0) continue; } //處理客戶串連 handle_connection(clientfds,maxi); }}static void handle_connection(struct pollfd *connfds,int num){ int i,n; char buf[MAXLINE]; memset(buf,0,MAXLINE); for (i = 1;i <= num;i++) { if (connfds[i].fd < 0) continue; //測試客戶描述符是否準備好 if (connfds[i].revents & POLLIN) { //接收用戶端發送的資訊 n = read(connfds[i].fd,buf,MAXLINE); if (n == 0) { close(connfds[i].fd); connfds[i].fd = -1; continue; } // printf("read msg is: "); write(STDOUT_FILENO,buf,n); //向用戶端發送buf write(connfds[i].fd,buf,n); } }}
用戶端代碼如下所示:
#include <netinet/in.h>#include <sys/socket.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <poll.h>#include <time.h>#include <unistd.h>#include <sys/types.h>#define MAXLINE 1024#define IPADDRESS "127.0.0.1"#define SERV_PORT 8787#define max(a,b) (a > b) ? a : bstatic void handle_connection(int sockfd);int main(int argc,char *argv[]){ int sockfd; struct sockaddr_in servaddr; sockfd = socket(AF_INET,SOCK_STREAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr); connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); //處理串連描述符 handle_connection(sockfd); return 0;}static void handle_connection(int sockfd){ char sendline[MAXLINE],recvline[MAXLINE]; int maxfdp,stdineof; struct pollfd pfds[2]; int n; //添加串連描述符 pfds[0].fd = sockfd; pfds[0].events = POLLIN; //添加標準輸入描述符 pfds[1].fd = STDIN_FILENO; pfds[1].events = POLLIN; for (; ;) { poll(pfds,2,-1); if (pfds[0].revents & POLLIN) { n = read(sockfd,recvline,MAXLINE); if (n == 0) { fprintf(stderr,"client: server is closed.\n"); close(sockfd); } write(STDOUT_FILENO,recvline,n); } //測試標準輸入是否準備好 if (pfds[1].revents & POLLIN) { n = read(STDIN_FILENO,sendline,MAXLINE); if (n == 0) { shutdown(sockfd,SHUT_WR); continue; } write(sockfd,sendline,n); } }}
4、程式測試結果
IO多工——poll