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); //进行 select
if(FD_ISSET(sockfd, rset)) // socket is readable
{
if( (n = read(sockfd, buf, MAXLINE)) == 0)
{
if(stdineof == 1)
return ; / normal termination /
else
err_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 servaddrif(argc != 2)
err_quit(“usage: tcpcli );

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, servaddrserver

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 1024
int 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, servaddrlistenfd = 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
}
else
err_sys(“read error”);
}
else if(0 == n)
{
close(sockfd);
client[i].fd = -1
}
else
writen(sockfd, buf, n);

if(nready <= 0)
break
}
}
}
}


Reference

Unix 网络编程 卷一 第六章

Comments