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

穆晨發表於2017-05-19

前言

       在此前,我已經介紹了一種併發回射伺服器實現。它通過呼叫fork函式為每個客戶請求建立一個子程式。同時,我還為此伺服器新增了自動消除殭屍子程式的機制。現在請想想,在客戶量非常大的情況下,這種為每個客戶請求都建立子程式的做法是不是太費資源了?我們可不可以在不為每個客戶請求都建立子程式的前提下實現併發回射伺服器

       答案自然是肯定的,這也是此文要講述的方法。

實現思路

       伺服器建立好監聽套接字之後,進入以下迴圈中:

1. 呼叫select函式,先使之阻塞於監聽套接字描述符(之後阻塞於監聽套接字描述符和已連線套接字描述符);

2. 當監聽套接字描述符被啟用,就呼叫connect函式建議一個客戶連線,並將返回的已連線套接字描述符也登記到select函式的監聽描述符集中。(這裡我們需要再設立一個client陣列記錄下所有的已連線套接字描述符在select函式的監聽描述符集中的位置);

3. 遍歷這個client陣列,一旦發現有已連線的監聽套接字被啟用,則做出相應處理。

程式碼實現

 1 #include    "unp.h"
 2 
 3 int
 4 main(int argc, char **argv)
 5 {
 6     int                    i, maxi, maxfd, listenfd, connfd, sockfd;
 7     int                    nready, client[FD_SETSIZE];
 8     ssize_t                n;
 9     fd_set                rset, allset;
10     char                buf[MAXLINE];
11     socklen_t            clilen;
12     struct sockaddr_in    cliaddr, servaddr;
13 
14     listenfd = Socket(AF_INET, SOCK_STREAM, 0);
15 
16     bzero(&servaddr, sizeof(servaddr));
17     servaddr.sin_family      = AF_INET;
18     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
19     servaddr.sin_port        = htons(SERV_PORT);
20 
21     Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
22 
23     Listen(listenfd, LISTENQ);
24 
25     // maxfd 表示select函式的監聽描述符集中的最大值( 作為第一個引數傳遞給select )。
26     maxfd = listenfd;            
27     // maxi 表示client陣列的邊界
28     maxi = -1;                
29     for (i = 0; i < FD_SETSIZE; i++)
30         client[i] = -1;            
31     FD_ZERO(&allset);
32     FD_SET(listenfd, &allset);
33 
34     for ( ; ; ) {
35         rset = allset;        
36         nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
37 
38         if (FD_ISSET(listenfd, &rset)) {    
39             clilen = sizeof(cliaddr);
40             // 和客戶建立連線
41             connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
42 #ifdef    NOTDEF
43             printf("new client: %s, port %d\n",
44                     Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
45                     ntohs(cliaddr.sin_port));
46 #endif
47             // 將已連線套接字描述符登記進client陣列
48             for (i = 0; i < FD_SETSIZE; i++)
49                 if (client[i] < 0) {
50                     client[i] = connfd;    
51                     break;
52                 }
53             if (i == FD_SETSIZE)
54                 err_quit("too many clients");
55 
56             // 將已連線套接字描述符登記進select函式的監聽套接字描述符集中
57             FD_SET(connfd, &allset);
58 
59             /*
60              * 下面兩個if語句在獲取到已連線描述符後更新maxfd和maxi。
61             */
62             if (connfd > maxfd)
63                 maxfd = connfd;            
64             if (i > maxi)
65                 maxi = i;            
66 
67             // 如果所有被監聽的訊號已經處理完畢了,則立即啟動下一次迴圈。
68             if (--nready <= 0)
69                 continue;                
70         }
71 
72         // 檢查select監聽描述符集中所有的已連線套接字描述符是否有被啟用的,有則做出相應處理。
73         for (i = 0; i <= maxi; i++) {
74             if ( (sockfd = client[i]) < 0)
75                 continue;
76             if (FD_ISSET(sockfd, &rset)) {
77                 if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
78                     Close(sockfd);
79                     FD_CLR(sockfd, &allset);
80                     client[i] = -1;
81                 } else
82                     Writen(sockfd, buf, n);
83 
84             // 如果所有被監聽的訊號已經處理完畢了,則立即啟動下一次迴圈。
85                 if (--nready <= 0)
86                     break;                
87             }
88         }
89     }
90 }

說明

1. 設定client陣列是必須的,它用來標記select的監聽描述符集中哪些位處於被監聽狀態;

2. 使用這種技術的程式容易收到拒絕服務攻擊。

相關文章