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