为什么使用select函数以及如何使用select函数
为何使用select函数
多进程服务器端的缺点和解决方法
为了构建并发服务器,只要有客户端连接请求就会创建进程。这的确是实际操作中采用的一种方案,但并非十全十美,因为创建进程时需要付出极大代价,这需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)。
那有何解决方案呢?能否在不创建进程的同时向多个客户端提供服务? 答案是肯定能,I/O复用就是解决方法之一(还可以通过线程池解决,之后会讲解线程池)
如何使用select函数
运用select函数是最具代表性的实现复用服务器端的方法,而且他具有很好的移植性(Linux与Windows之间有同名函数),接下来以Linux为例。
select函数具体参数以及返回值
#incldue <sys/select.h>
#include <sys/time.h>
int select (int maxfd,fd_set* readset,fd_set* writeset,fd_set*exceptset,const struct timeval* timeout);
maxfd:监视对象文件描述符数量(一般是最大的文件描述符值加1)
readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。
wirteset:将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值。
exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值。
timeout:调用select函数后,为防止无线阻塞的状态,传递超时(time-out)信息。
返回值:错误返回-1,超时返回0。因发生关注的时间返回时,返回大于0的值,该值是发生时间的文件描述符数。
设置文件表描述符
利用select函数可以同时监视多个文件描述符。当然,监视文件描述符可以视为监视套接字。此时首先需要将要坚实的文件描述符集中到一起。集中时也要按照监视项(接收、传输、异常)进行区分,即按照上述3中监视项分成3类。
对fd_set变量中注册或更改值的操作都有下列宏完成。
1、FD_ZERO(fd_set* fdset):将fd_set变量的所有位初始化位0。
2、FD_SET(int fd,fd_set* fdset):在参数fdset指向的变量中注册文件描述符fd的信息。
3、FD_CLR(int fd,fd_set* fdset):从参数fdset指向的变量中清除文件描述符fd的信息。
4、FD_ISSET(int fd,fd_set* fdset):若参数fdset指向的变量中包含文件描述符fd的信息。则返回“真”。(用于验证select函数的调用结果)
操作 | fd0 | fd1 | fd2 |
---|---|---|---|
初始值 | 0 | 1 | 0 |
FD_ZERO(&set) | 0 | 0 | 0 |
FD_SET(1,&set) | 0 | 1 | 0 |
FD_SET(2,&set) | 0 | 1 | 1 |
FD_CLR(2,&set) | 0 | 1 | 0 |
调用select函数后查看结构
若select函数的返回值如果是大于0的整数,说明相应数量的文件描述符发生变化。
Example:
调用select函数前
fd0 | fd1 | fd2 |
---|---|---|
0 | 1 | 1 |
调用select函数后(fd1变化后)
fd0 | fd1 | fd2 |
---|---|---|
0 | 1 | 0 |
由上面两张表格可知,select函数调用完成后,向其传递的fd_set变量中将发生变化。原来为1的所有位均变为0,但发生变化的文件描述符对应为除外。因此,可以认为值仍为1的位置上的文件描述符发生了变化
select函数调用示例
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30
int main(int argc,char* argv[])
{
fd_set reads,temps;
int result,str_len;
char buf[BUF_SIZE];
struct timeval timeout;
/* 看似复杂,实则简单。首先初始化fd_set变量,然后将文件描述符0对应的位设置为1。
换言之,需要见识标准输入的变化*/
FD_ZERO(&reads);
FD_SET(0,&reads);
while(1)
{
/* 将准备好的fd_set变量reads的内容复制到temps变量,因为之前讲过,调用select函数后,
出发生变化的文件描述符对应位外,剩下的所有位将初始化为0。因此,为了记住初始值,
必须经过这种复制过程。这是使用select函数的通用方法。*/
temps = reads;
/* 将初始化timeval结构体的代码插入循环后,每次调用selece函数前都会初始化新值*/
timeout.tv_sec = 5;
timeout.tv_usec = 0;
// 调用select函数。如果有控制台输入数据,则返回大于0的整数;如果没有输入数据而引
发超时,则返回0。
result = select(1,&temps,0,0,&timeout);
if(-1 == result)
{
puts("select() error!");
break;
}
else if(0 == result)
{
puts("Time-out!");
}
else
{
/* select函数返回大于0的值时运行的区域。炎症发生变化的文件描述符是否为标准输入。
若是,则从标准输入读取数据并发控制台输出。*/
if(FD_SET(0,&temps))
{
str_len = read(0,buf,BUF_SIZE);
buf[str_len] = 0;
printf("message from console:%s",buf);
}
}
}
return 0;
}
总结
本文旨在用通俗易懂的方式来帮助初学者了解select,如有任何疑问或是错误,欢迎评论。