IO多路转接之select:
函数功能:监视描述符集合中的描述符状态变化。程序会在select函数等待,直到有描述符就绪。
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数:
nfds:当前描述符集合最大描述符+1
readfds:监控可读事件的描述符集合
writefds:监控可写事件的描述符集合
execptfds:监控异常事件的描述符集合
timeout:用来设置监控时间,如果指定时间没有描述符就绪,则超时返回。NULL:表示一直等待,直到有描述符事件就绪。
关于fd_set这个结构体,我们来看一下它的定义:
typedef struct
{
/*XPG4.2requiresthismembername.Otherwiseavoidthename
fromtheglobalnamespace.*/
#ifdef__USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->__fds_bits)
#endif
}fd_set;
typedef long int __fd_mask;
将上述代码简化,我们其实可以得到,fd_set就是一个长整型数组 long int fds_bits[32];而我们用位图来表示描述符,这样我们可以表示的描述符最多有1024个(描述符集合)
fd_set的接口,用来方便操作位图:
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd的位是否为真 存在则返回非零,不存在返回零。
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
接下来我们通过代码来详细了解fd_set。
程序运行结果如下:
可以看到fds_bits[0]变为1024,这是因为,我们执行函数FD_SET(10,&fds)后,将fd_set(即fds_bits[0])第十个比特位设置为1
即 00000000 00000000 00000010 00000000 大小为1024。每一个数组元素有32位,即可以表示32个描述符。那么我们将上述程序改为执行FD_SET(32,&fds)程序运行结果会怎样呢?
运行结果如下:
可以看出来,fds_bits[1]变为1,这是因为我们设置第32位的时候,该位置在数组的第二个元素中(即fds_bits[1]),
00000000 00000000 00000000 00000001 大小为1 。
接下来我们再来看timeval结构:
struct timeval {
long tv_sec; /* seconds 秒*/
long tv_usec; /* microseconds 百万分之一秒,微秒*/
};
我们可以利用timeout这个参数来设定select函数等待的时间,最多可以精确到微秒,这也是select函数的优点之一,一旦超过这个等待时间且没有发生描述符时间就绪,select函数则超时返回0。也可以将timeout设置为NULL,意为一直等待,直到有描述符时间就绪。
函数返回值:函数执行成功则返回就绪描述符个数,返回0代表等待超时。返回非零代表出错,错误原因存于errno。可能的错误: * EBADF ⽂件描述词为⽆效的或该⽂件已关闭 * EINTR 此调⽤被信号所中断。
函数执行过程:函数执行时并不会告诉我们哪个描述符事件就绪了,需要我自己处理,同时函数会将没有就绪的描述符置零。
将fd加⼊select监控集的同时,还要再使⽤⼀个数据结构array保存放到select监控集中的描述符,
⼀是⽤于在select 返回后,array作为源数据和fds进⾏FD_ISSET判断。
⼆是select返回后会把以前加⼊的但并⽆事件发⽣的描述符清空(置零),则每次开始select前都要重新从array取
得描述符逐⼀加⼊(先FD_ZERO),扫描array的同时取得描述符的最大值,用于函数的第一个参数
优缺点:
1.select能够支持监控的描述符个数是有限的,由FD_SETSIZE来确定(1024)
2.select的原理是轮询判断就绪,描述符越多循环越多,因此性能会随和描述符增多而下降
3.select并不会直接告诉用户哪个描述符就绪,因此我们需要遍历所有的描述符看哪一个就绪,因此编码复杂。
4.select每次有就绪事件发生都会修改描述符集合(把没有就绪的剔除出去),因此我们要监控的描述符每次循环都需要重新添加
5.每次都需要将用户态的监控集合拷贝到内核态
1.监控的超时时间比较精细
2.select可以跨平台 (win也有)
对于tcp服务器,读事件就绪有两种情况:
1.有客户端新的连接到来——监听描述符读事件就绪
2.有客户端数据到来——客户端描述符读事件就绪
接下来用select编写tcp服务器,实现单线程处理多个客户端连接和数据传输。
#include<stdio.h>
#include<stdlib.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
int main(int argc,char* argv[])
{
int i,j;
if(argc!=2)
{
printf("./name port!");
return -1;
}
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(atoi(argv[1]));
server_addr.sin_addr.s_addr=INADDR_ANY;
int listen_sockfd=socket(AF_INET,SOCK_STREAM,0);
if(listen_sockfd<0)
{
perror("create socket error!");
return 1;
}
if(bind(listen_sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr))<0)
{
perror("bind error!");
return 1;
}
if(listen(listen_sockfd,5)<0)
{
perror("listen error!");
return 1;
}
fd_set read_fds;
int fd_list[1024];
int max_fd;
for(i=0;i<1024;i++)
{
fd_list[i]=-1;
}
fd_list[0]=listen_sockfd;
while(1)
{
max_fd=listen_sockfd;
FD_ZERO(&read_fds);
for(i=0;i<1024;i++)
{
if(fd_list[i]!=-1)
{
FD_SET(fd_list[i],&read_fds);
max_fd=max_fd>fd_list[i]?max_fd:fd_list[i];
}
}
struct timeval tv;
tv.tv_sec=3;
tv.tv_usec=0;
int ret=select(max_fd+1,&read_fds,NULL,NULL,&tv);
if(ret<0)
{
perror("select error!");
continue;
}
else if(ret==0)
{
printf("no sockfd ready\n");
continue;
}
for(i=0;i<max_fd+1;i++)
{
if(FD_ISSET(i,&read_fds))
{
if(i==listen_sockfd)
{
struct sockaddr_in client_addr;
socklen_t len =sizeof(client_addr);
int new_fd=accept(listen_sockfd,(struct sockaddr*)&client_addr,&len);
if(new_fd<0)
{
perror("accepr error!");
continue;
}
printf("a new client connect!\n");
for(j=0;j<1024;j++)
{
if(fd_list[j]==-1)
{
fd_list[j]=new_fd;
max_fd=max_fd>new_fd?max_fd:new_fd;
break;
}
}
}
else
{
char buff[1024]={0};
int rlen=recv(i,buff,1023,0);
if(rlen<=0)
{
if(errno==EAGAIN||errno==EINTR)
{
continue;
}
perror("recv error!");
close(i);
for(j=0;j<1024;j++)
{
if(i==fd_list[j])
{
fd_list[j]=-1;
}
}
}
printf("client: %s\n",buff);
}
}
}
}
close(listen_sockfd);
return 0;
}