一、select()函数功能
The select system call allows a program to wait for input to arrive (or output to complete) on a number
of low-level file descriptors at once.
select系统调用允许程序同时在多个底层文件表述符上,等待输入的到达或输出的完成。
二、函数意义
A server can deal with multiple clients by waiting for a request on
many open sockets at the same time.
一个服务器可以同时在多个打开的套接字等待请求到来的方法而处理多个客户。(只是具体应用的其中之一)
自己的理解,但凡是程序在执行过程中会遇到阻塞(不到条件发生就不往下执行)的情况,都是很浪费CPU资源的。因为程序占用了CPU的时间,却在等待一个事件的发生,干等着不干活,占着茅坑不拉屎……但是有了select()函数,就可以用一个进程同时监视很多个文件描述符到输入、输出、错误。select函数返回后,通过FD_ISSET()函数检测是哪个文件描述符状态发生了变化,在进行相应的操作,从而省去了对这么文件描述符的等待操作,提高了CPU的利用率。
三、相关的函数原型
#include <sys/types.h>
#include <sys/time.h>
void FD_ZERO(fd_set *fdset); 将fdset集合清零(初始化)
void FD_CLR(int fd, fd_set *fdset); 清除fd文件描述符
void FD_SET(int fd, fd_set *fdset); 添加fd文件描述符
int FD_ISSET(int fd, fd_set *fdset); 检测fd是否为fdset文件描述符集合中的元素,是返回非0值,否返回0;
其中:数据结构“fd_set”是由打开的文件描述符构成的集合。上面的四个函数用来控制这个集合。
#include <sys/types.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
参数:
nfds:要测试的描述符范围,0~(nfds-1)
返回值:
1.readfds集合中有描述符可读,writefds集合中有描述符可写,errorfds集合中有描述符遇到错误条件时select将返回1,失败返回-1;
2.如果三种情况都没有发生,select将在到达timeout超时时间后,返回0;
3.如果timeout为0,则select将一直阻塞,直到有情况发生。(之前说但凡是阻塞就是浪费CPU资源,但是此处的阻塞,也算是牺牲小我,成就大我了,因为一个阻塞同时监控着所有文件描述符的变化,跟每个描述符都阻塞相比,效率还是高高的……)
四、实例
例程1.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int main(void)
{
char buffer[128];
int result, nread;
fd_set inputs, testfds;
struct timeval timeout;
FD_ZERO(&inputs);
FD_SET(0,&inputs);
while(1)
{
testfds = inputs;
timeout.tv_sec = 2;
timeout.tv_usec = 500000;
result = select(FD_SETSIZE, &inputs, (fd_set *)NULL, (fd_set *)NULL, &timeout);
switch(result)
{
case 0:printf("timeout\n");break;
case -1:perror("select");exit(1);
default:
if(FD_ISSET(0,&inputs))
{
ioctl(0,FIONREAD,&nread);
if(nread == 0)
{
printf("keyboard done\n");
exit(0);
}
nread = read(0,buffer,nread);
buffer[nread] = 0;
printf("read %d from keyboard: %s", nread, buffer);
}
break;
}
}
}
这个程序读取键盘输入(标准输入-文件描述符为0),超时时间设为2.5s
例程2:
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/times.h>
#include <sys/ioctl.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int res;
fd_set readfds, testfds;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("192.168.0.21");
server_address.sin_port = htons(8080);
server_len = sizeof(server_address);
int on=1;
if((setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
{
perror("set sock opt");
exit(1);
}
bind(server_sockfd, (struct sockaddr*)&server_address, server_len);
listen(server_sockfd, 5);
FD_ZERO(&readfds);
FD_SET(server_sockfd, &readfds);
while(1)
{
char ch;
int fd;
int nread;
testfds = readfds;
printf("server is waiting!\n");
res = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0);
printf("res=%d\n",res);
if(res < 0)
{
perror("server");
exit(1);
}
for(fd = 0; fd<FD_SETSIZE;fd++)
{
if(FD_ISSET(fd, &testfds))
{
if(fd == server_sockfd)
{
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);
printf("adding client on fd %d\n",client_sockfd);
}
else
{
ioctl(fd, FIONREAD, &nread);
if(nread == 0)
{
close(fd);
FD_CLR(fd, &readfds);
printf("removing client on fd %d\n", fd);
}
else
{
read(fd, &ch ,1);
// sleep(5);
printf("serving client on fd %d\n", fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
}
client.c
/* Make the necessary includes and set up the variables. */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ch = 'A';
/* Create a socket for the client. */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* Name the socket, as agreed with the server. */
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("192.168.0.21");
address.sin_port = htons(8080);
len = sizeof(address);
/* Now connect our socket to the server's socket. */
result = connect(sockfd, (struct sockaddr *)&address, len);
if(result == -1) {
perror("oops: client3");
exit(1);
}
/* We can now read/write via sockfd. */
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
此程序用select系统调用实现了服务器服务于多客户,没有采用多进程。服务器可以让select同时检查 监听套接字 和 客户端的连接套接字。一旦select指示有活动发生,我们便可以用FD_ISSET()函数来遍历所有可能的文件描述符,以检查是哪个发生了活动。
如果是监听套接字可读,说明正有一个客户尝试连接,此时可以用accept接受连接请求,而不会阻塞;如果是某个客户连接套接字准备好,说明这个套接字的客户有情况需要我们处理,这时调用ioctl(),如果返回0,则客户进程已经结束,关闭该套接字并把它从描述符集合中删除即可。