第二十篇:不为客户连接创建子进程的并发回射服务器(poll实现)

前言

  在上文中,我使用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函数。否则的话就随意了。

posted @ 2017-05-19 13:55  穆晨  阅读(275)  评论(0编辑  收藏  举报