select && poll 函数

select 和 poll 是在 Linux 下进行 I/O 复用时所使用的技术,当然现在有更高级的 epoll。I/O 复用典型使用场合如下:

当客户端处理多个描述符时,必须使用 I/O 复用。一个客户同时处理多个套接字是可能的,不过比较少见。如果一个 TCP 服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用 I/O 复用。如果一个服务器既要处理 TCP,又要处理 UDP,一般就使用 I/O 复用。如果一个服务器要处理多个服务或者多个协议,一般就要使用 I/O 复用。

1. select 函数介绍

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

其中第一个参数表示 select 每次轮询的时候需要检查多少个描述符,也就是你需要监听的所有描述符中值最大的再加上1(描述符从0开始的);第二个,第三个,第四个参数分别表示需要监听的读事件,写事件,异常事件,且这三个参数都是 [值-结果] 型的,也就是说在调用过程中会更改,最后结果保存在这三个参数中,第五个参数表示等待的时间,有三种可能:永远等待下去,等待一段固定的时间,不等待。

2. select 例子 一个 C/S 简单程序

========================================client===========================void str_cli(FILE *fp, int sockfd){int maxfdp1, stdineof;fd_set rset;char buf[MAXLINE];intn;stdineof = 0;FD_ZERO(&rset); //清空 resetfor( ; ; ) //无限循环{if(stdineof == 0) ?//如果客户端没有关闭才把 fp 加入到监测集合中FD_SET(fileno(fp), &rset);FD_SET(sockfd, &rset); ?//把 sockfd 加入到监测中maxfdp1 = max(fileno(fp), sockfd) + 1;select(maxfdp1, &rset, NULL, NULL, NULL); ?//进行 selectif(FD_ISSET(sockfd, &rset)) // socket is readable{if( (n = read(sockfd, buf, MAXLINE)) == 0){if(stdineof == 1)return ; ? ? /* normal termination */elseerr_quit(“str_cli: server terminated prematurely”);}//fputs(recvline, stdout);write(fileno(stdout), buf, n);}if(FD_ISSET(fileno(fp), &rset)) /* input is readable */{if( (n = read(fileno(fp), buf, MAXLINE)) == 0){stdineof = 1;shutdown(sockfd, SHUT_WR); ?/* send FIN */FD_CLR(fileno(fp), &rset);continue;}write(sockfd, buf, n);}}}int main(int argc, char *argv[]){int i, sockfd[5];struct sockaddr_in servaddr;if(argc != 2)err_quit(“usage: tcpcli <IPaddress>”);

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 函数

int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);struct pollfd{int fd; ? ? ? ?/* descriptor to check */short events; ? ?/* events of interest on fd */short revents;/* events that occurred on fd */

第一个参数是 struct pollfd 型的数组,第二个参数表示你需要监听第一个参数中的前多少个元素,第三个参数表示你愿意等待多久。下面是把上面用 select 实现的服务器用 poll 来实现一次,大致思路一致,我们已经不需要专门的 client 数组来保存连接的描述符了。

#define OPEN_MAX 1024int main(int argc, char *argv[]){int ?i, maxi, listenfd, connfd, sockfd;int ?nready;ssize_t n;char buf[MAXLINE];socklen_t clilen;struct pollfd client[OPEN_MAX];struct sockaddr_in cliaddr, servaddr;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);

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 网络编程 卷一 第六章

select && poll 函数

相关文章:

你感兴趣的文章:

标签云: