select 和 poll 是在 Linux 下进行 I/O 复用时所使用的技术,当然现在有更高级的 epoll。I/O 复用典型使用场合如下:
当客户端处理多个描述符时,必须使用 I/O 复用。一个客户同时处理多个套接字是可能的,不过比较少见。如果一个 TCP 服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用 I/O 复用。如果一个服务器既要处理 TCP,又要处理 UDP,一般就使用 I/O 复用。如果一个服务器要处理多个服务或者多个协议,一般就要使用 I/O 复用。
1. select 函数介绍
其中第一个参数表示 select 每次轮询的时候需要检查多少个描述符,也就是你需要监听的所有描述符中值最大的再加上1(描述符从0开始的);第二个,第三个,第四个参数分别表示需要监听的读事件,写事件,异常事件,且这三个参数都是 [值-结果] 型的,也就是说在调用过程中会更改,最后结果保存在这三个参数中,第五个参数表示等待的时间,有三种可能:永远等待下去,等待一段固定的时间,不等待。
2. select 例子 一个 C/S 简单程序
for(i=0; i<5; ++i)//这里可以忽律为什么是5,这个是我做其他测试用的,实际上只需要一个就行{sockfd[i] = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
connect(sockfd[i], (struct sockaddr *)&servaddr, sizeof(servaddr));}str_cli(stdin, sockfd[0]);exit(0);}
==========================server===========================int main(int argc, char *argv[]){int ?i, maxi, maxfd, listenfd, connfd, sockfd;int ?nready, client[FD_SETSIZE];ssize_t n;fd_set rset, allset;char buf[MAXLINE];socklen_t clilen;struct sockaddr_in cliaddr, servaddr;server
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
maxfd = listenfd;maxi = -1;for(i=0; i<FD_SETSIZE; ++i)client[i] = -1;FD_ZERO(&allset);FD_SET(listenfd, &allset);
for( ; ; ){rset = allset;nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if(FD_ISSET(listenfd, &rset)) ?/* new client connection */{clilen = sizeof(cliaddr);connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
for(i=0; i<FD_SETSIZE; ++i)if(client[i] < 0){client[i] = connfd;break;}if(i == FD_SETSIZE)err_quit(“too many clients”);FD_SET(connfd, &allset); /* add new descriptor to set */if(connfd > maxfd)maxfd = connfd; ? ? /* for select */if(i > maxi)maxi = i; ? ? ? ? ? ? /* max index in client[] array */if(–nready <= 0)continue; ? ? ? ? ? ? /* no more readable descriptors */}for(i=0; i<= maxi; ++i){if( (sockfd = client[i]) < 0)continue;if(FD_ISSET(sockfd, &rset)){if( (n = read(sockfd, buf, MAXLINE)) == 0){/* connection closed by client */close(sockfd);printf(“closed by client \n”);FD_CLR(sockfd, &allset);client[i] = -1;}else{writen(sockfd, buf, n);}if(–nready <= 0)break; ? ? ? ? ? ? /* no more readable descriptors */}}}}
上面的程序能够基本说明 select 的大致运用,一定要注意的一点是 select 每次的 readset,writeset,exceptset 都会被 select 修改,所以每次都需要自己重新进行设定。
3. poll 函数
第一个参数是 struct pollfd 型的数组,第二个参数表示你需要监听第一个参数中的前多少个元素,第三个参数表示你愿意等待多久。下面是把上面用 select 实现的服务器用 poll 来实现一次,大致思路一致,我们已经不需要专门的 client 数组来保存连接的描述符了。
bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
client[0].fd = listenfd;client[0].events = POLLRDNORM;for(i=1; i<OPEN_MAX; ++i)client[i].fd = -1; ? ? ? ? /* -1 indicates available entry */maxi = 0;
for( ; ; ){nready = poll(client, maxi+1, INFTIM);
printf(“nready:%d\n”, nready);if(client[0].revents & POLLRDNORM){clilen = sizeof(cliaddr);connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
for(i=1; i<OPEN_MAX; ++i)if(client[i].fd < 0){client[i].fd = connfd;break;}
if(i == OPEN_MAX)err_quit(“too many clients”);
client[i].events = POLLRDNORM;if(i > maxi)maxi = i;
if(–nready <= 0)continue;}
for(i=1; i<=maxi; ++i){if( (sockfd = client[i].fd) < 0)continue;if(client[i].revents & (POLLRDNORM | POLLERR)){if( (n = read(sockfd, buf, MAXLINE)) < 0){if(errno == ECONNRESET){close(sockfd);client[i].fd = -1;}elseerr_sys(“read error”);}else if(0 == n){close(sockfd);client[i].fd = -1;}elsewriten(sockfd, buf, n);
if(–nready <= 0)break;}}}}
Reference
Unix 网络编程 卷一 第六章