select和poll没有本质区别,poll不限制客户端的个数,两个函数的用法不一样
select用到的是
FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空 处理,所以结果是不可知的。
FD_SET(fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd,fd_set *fdset);用于测试指定的文件描述符是否在该集合中。
需要两个变量来辅助,一个用来存储标识符 allset(因为每次select之后,存储的标识符都会变),一个(client[])用来标记连接符的位置
1 for ( ; ; ) 2 { 3 rset = allset; /* 每次循环时都从新设置select监控信号集 */ 4 nready = select(maxfd+1, &rset, NULL, NULL, NULL); 5 6 if (nready < 0) 7 perr_exit("select error"); 8 if (FD_ISSET(listenfd, &rset)) { /* new client connection */ 9 cliaddr_len = sizeof(cliaddr); 10 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); 11 printf("received from %s at PORT %d\n", 12 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), 13 ntohs(cliaddr.sin_port)); 14 for (i = 0; i < FD_SETSIZE; i++) { 15 if (client[i] < 0) { 16 client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */ 17 break; 18 } 19 } 20 /* 达到select能监控的文件个数上限 1024 */ 21 if (i == FD_SETSIZE) { 22 fputs("too many clients\n", stderr); 23 exit(1); 24 } 25 26 FD_SET(connfd, &allset); /* 添加一个新的文件描述符到监控信号集里 */ 27 if (connfd > maxfd) 28 maxfd = connfd; /* select第一个参数需要 */ 29 if (i > maxi) 30 maxi = i; /* 更新client[]最大下标值 */ 31 32 if (--nready == 0) 33 continue; /* 如果没有更多的就绪文件描述符继续回到上面select阻塞监听, 34 负责处理未处理完的就绪文件描述符 */ 35 } 36 for (i = 0; i <= maxi; i++) { /* 检测哪个clients 有数据就绪 */ 37 if ( (sockfd = client[i]) < 0) 38 continue; 39 if (FD_ISSET(sockfd, &rset)) { 40 if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { 41 Close(sockfd); /* 当client关闭链接时,服务器端也关闭对应链接 */ 42 FD_CLR(sockfd, &allset); /* 解除select监控此文件描述符 */ 43 client[i] = -1; 44 } else { 45 int j; 46 for (j = 0; j < n; j++) 47 buf[j] = toupper(buf[j]); 48 Write(sockfd, buf, n); 49 } 50 if (--nready == 0) 51 break; 52 } 53 } 54 }
poll不需要额外的增加辅助变量来提高效率
struct pollfd { int fd; /* 文件描述符 */ short events; /* 监控的事件 */ short revents; /* 监控事件中满足条件返回的事件 */ }; POLLIN 普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND POLLRDNORM 数据可读 POLLRDBAND 优先级带数据可读 POLLPRI 高优先级可读数据 POLLOUT 普通或带外数据可写 POLLWRNORM 数据可写 POLLWRBAND 优先级带数据可写 POLLERR 发生错误 POLLHUP 发生挂起 POLLNVAL 描述字不是一个打开的文件
for (i = 1; i < OPEN_MAX; i++) client[i].fd = -1; /* 用-1初始化client[]里剩下元素 */ maxi = 0; /* client[]数组有效元素中最大元素下标 */ for ( ; ; ) { nready = poll(client, maxi+1, -1); /* 阻塞 */ if (client[0].revents & POLLRDNORM) { /* 有客户端链接请求 */ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (i = 1; i < OPEN_MAX; i++) { if (client[i].fd < 0) { client[i].fd = connfd; /* 找到client[]中空闲的位置,存放accept返回的connfd */ break; } } if (i == OPEN_MAX) perr_exit("too many clients"); client[i].events = POLLRDNORM; /* 设置刚刚返回的connfd,监控读事件 */ if (i > maxi) maxi = i; /* 更新client[]中最大元素下标 */ if (--nready <= 0) continue; /* 没有更多就绪事件时,继续回到poll阻塞 */ } for (i = 1; i <= maxi; i++) { /* 检测client[] */ if ((sockfd = client[i].fd) < 0) continue; if (client[i].revents & (POLLRDNORM | POLLERR)) { if ((n = Read(sockfd, buf, MAXLINE)) < 0) { if (errno == ECONNRESET) { /* 当收到 RST标志时 */ /* connection reset by client */ printf("client[%d] aborted connection\n", i); Close(sockfd); client[i].fd = -1; } else { perr_exit("read error"); } } else if (n == 0) { /* connection closed by client */ printf("client[%d] closed connection\n", i); Close(sockfd); client[i].fd = -1; } else { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Writen(sockfd, buf, n); } if (--nready <= 0) break; /* no more readable descriptors */ } } }
每次select和poll的时候都会把标识符数组从用户态复制到内核态,输出结果再从内核态转到用户态,两次复制效率太低,所以引入了epoll,只需要复制一次,然后从内核态调用内核函数,提高了效率
epoll (参考:http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html)写的很详细
allset