第二十篇:不為客戶連線建立子程式的併發回射伺服器(poll實現)

穆晨發表於2017-05-19

前言

  在上文中,我使用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函式。否則的話就隨意了。

相關文章