前言
在上文中,我使用select函式實現了不為客戶連線建立子程式的併發回射伺服器( 點此進入 )。但其中有個細節確實有點麻煩,那就是還得設定一個client陣列用來標記select監聽描述符集中被設定為監聽位的位。
有沒有方法簡化這個處理呢?
有!在《UNIX網路程式設計》第六章最後介紹了一種類似select的函式:poll函式,用它來實現IO複用使程式碼簡化了不少( 起碼併發回射伺服器的例子是的 )。
poll函式介紹
1. 包含標頭檔案:<poll.h>
2. 函式原型:int poll ( struct pollfd * fdarray, unsigned long nfds, int timeout);
1 // 監聽描述陣列的元素 2 struct pollfd { 3 int fd; // 監聽描述符 4 short events; // 測試事件 5 short revents; // 測試結果 6 };
3. 說明:相比於select函式,poll函式使用了結構體陣列fdarray來表示監聽描述符集合,該陣列元素型別如上程式碼所示。當我們檢索這個陣列,我們可以知道有哪些描述符被監聽,監聽測試事件是什麼,測試的結果又是什麼。(而在select函式的fdset描述符集合中,無法獲知某位是不是被監聽的描述符,這也就是下面的程式碼並不需要使用client陣列的原因)。
程式碼實現
1 #include "unp.h" 2 // 下標頭檔案包含巨集定義OPEN_MAX 3 #include <limits.h> 4 5 int 6 main(int argc, char **argv) 7 { 8 int i, maxi, listenfd, connfd, sockfd; 9 int nready; 10 ssize_t n; 11 char buf[MAXLINE]; 12 socklen_t clilen; 13 struct pollfd client[OPEN_MAX]; 14 struct sockaddr_in cliaddr, servaddr; 15 16 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 17 18 bzero(&servaddr, sizeof(servaddr)); 19 servaddr.sin_family = AF_INET; 20 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 21 servaddr.sin_port = htons(SERV_PORT); 22 23 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); 24 25 Listen(listenfd, LISTENQ); 26 27 client[0].fd = listenfd; 28 client[0].events = POLLRDNORM; 29 // 清空監聽描述符陣列 30 for (i = 1; i < OPEN_MAX; i++) 31 client[i].fd = -1; 32 // 該變數表示已連線套接字描述符的最大數量( 曾經 ) 33 maxi = 0; 34 35 for ( ; ; ) { 36 nready = Poll(client, maxi+1, INFTIM); 37 38 // 如果有監聽描述符檢測到可讀資料的訊號 39 if (client[0].revents & POLLRDNORM) { 40 clilen = sizeof(cliaddr); 41 connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); 42 #ifdef NOTDEF 43 printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen)); 44 #endif 45 46 // 登記剛剛獲取到的已連線套接字描述符 47 // OPEN_MAX表示最多監聽的描述符個數 48 for (i = 1; i < OPEN_MAX; i++) 49 if (client[i].fd < 0) { 50 client[i].fd = connfd; 51 break; 52 } 53 if (i == OPEN_MAX) 54 err_quit("too many clients"); 55 56 // 設定監聽測試事件 57 client[i].events = POLLRDNORM; 58 if (i > maxi) 59 maxi = i; 60 61 // 如果訊號已經處理完畢則自動進入下一次迴圈 62 if (--nready <= 0) 63 continue; 64 } 65 66 for (i = 1; i <= maxi; i++) { 67 if ( (sockfd = client[i].fd) < 0) 68 continue; 69 // 如果檢測到可讀或者發生錯誤的訊號 70 if (client[i].revents & (POLLRDNORM | POLLERR)) { 71 if ( (n = read(sockfd, buf, MAXLINE)) < 0) { 72 if (errno == ECONNRESET) { 73 #ifdef NOTDEF 74 printf("client[%d] aborted connection\n", i); 75 #endif 76 Close(sockfd); 77 client[i].fd = -1; 78 } else 79 err_sys("read error"); 80 } else if (n == 0) { 81 #ifdef NOTDEF 82 printf("client[%d] closed connection\n", i); 83 #endif 84 Close(sockfd); 85 client[i].fd = -1; 86 } else 87 Writen(sockfd, buf, n); 88 89 // 如果檢測到可讀或者發生錯誤的訊號 90 if (--nready <= 0) 91 break; 92 } 93 } 94 } 95 }
說明
如果從可移植性方面考慮或者有處理訊號阻塞方面的需求(要知道select還有個作為其升級版的pselect函式能夠妥善處理訊號阻塞),就還是得用select函式而不是poll函式。否則的話就隨意了。