1. select是做什么的?
系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视**多个文件句柄**的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。
2. select函数原型?
![这里写图片描述](https://img-blog.csdn.net/20170606093025905?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2F5aGVsbG9fd29ybGQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这里有几个参数。
1.参数nfds是需要监视的最大的文件描述符值+1。
2.参数rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。
如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
3.timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
这个参数有三种选项:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
select特点:
1、可监控的文件描述符个数取决与sizeof(fd_set)的值。本人服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,服务器上支持的最大文件描述符是4096(512*8)。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。
2、将fd加入select监控集的同时,还要使用一个数据结构array保存放到select监控集中的fd。
一是用于在select返回后,array作为源数据和fd_set进行FD_ISSET判断。
二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
3、select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。
select缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
(3)select支持的文件描述符数量太小了,默认是1024。
测试程序:
server端:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
int array_fds[1024];
int startup(char *ip,int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
exit(1);
}
int flg = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flg,sizeof(flg));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
perror("bind");
exit(3);
}
if(listen(sock,10)<0){
perror("listen");
exit(4);
}
return sock;
}
int main(int argc ,char* argv[])
{
if(argc != 3)
{
printf("usage: %s [ip] [port]\n",argv[0]);
return 1;
}
int maxfd = 0;
int listenSock = startup(argv[1],atoi(argv[2]));
fd_set rfds;
array_fds[0] = listenSock;
int array_size = sizeof(array_fds)/sizeof(array_fds[0]);
int i = 1;
for(;i<array_size;++i)
array_fds[i] = -1;
while(1)
{
struct timeval timeout = {0,0};
FD_ZERO(&rfds);
maxfd = -1;
for(i = 0;i<array_size;++i)
{
if(array_fds[i] > 0){
FD_SET(array_fds[i],&rfds);
if(array_fds[i] > maxfd)
maxfd = array_fds[i];
}
}
switch(select(maxfd+1,&rfds,NULL,NULL,NULL))//这里最后一个参数为timeout 则为等待一段时间后退出,若为NULL则表示阻塞等待
{
case 0:
printf("timeout...\n");
break;
case -1:
perror("select");
break;
default:
{
int j = 0;
for(;j<array_size;++j)
{
if(array_fds[j] < 0)
continue;
//j==0表示listensock响应
if(j == 0 && FD_ISSET(array_fds[j],&rfds)){
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_fd = accept(array_fds[j],\
(struct sockaddr*)&client,&len);
if(new_fd < 0){
perror("accept");
continue;
}else{
printf("get a new client:{%s:%d}\n",\
inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
int k = 1;
for(;k<array_size;++k){
if(array_fds[k] < 0){
array_fds[k] = new_fd;
break;
}
}
if(k == array_size){
close(new_fd);
}
}
}
//j!=0表示其他的响应
else if(j!=0 && FD_ISSET(array_fds[j],&rfds)){
char buf[10240];
ssize_t s = read(array_fds[j],\
buf,sizeof(buf)-1);
if(s > 0){
buf[s] = 0;
printf("client say :%s\n",buf);
}else if(s == 0){
printf("client quit~\n");
close(array_fds[j]);
array_fds[j] = -1;
}else{
perror("read");
close(array_fds[j]);
array_fds[j] = -1;
}
}
}
break;
}
}
}
}
实验结果: