select函数是系统调用函数,用于多路监控。当没有一个文件满足要求时,select将阻塞调用进程。在有些情况下,采用select函数可以大大简化程序结构。比如一个系统有10个输入设备,如果想实时读取这10个设备的输入数据,就比较困难,采用查询方式,显然达不到实时的目的;或者可以为每一个设备设计一个线程,每个线程实时对设备的输入进行读取,这样会使程序异常复杂,数据的交互也很混乱。有了select函数,这个问题就迎刃而解了。采用一个线程,并用一个selet函数同时对10个设备进行监控,这确实是一个好办法。
1、我们先来看一下select函数的原型,如下所示
int select(int maxfd,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
在这个函数中,
maxfd:文件描述符的范围,比待监控的最大文件描述符加1。
readfds:它是指向fd_set结构的指针,fd_set是一个描述符集合。这个集合中是要监控的读类型的文件的描述符。
writefds:他也是指向fd_set结构的指针,这个集合是要监控的写类型的文件的描述符。
errorfds:与上面两个参数类似,它是用来监视文件错误异常的文件描述符的集合。
timeout:它是select函数的超时时间,这个参数至关重要,它可以使select处于三种状态。
1,若将NULL以形参传入,即不传入时间结构,则select一直置于阻塞状态,直到监控到文件描述符集合中某个文件描述符发生变化为止;2,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;3,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后返回0。
函数的返回值,在正常情况下返回满足要求的文件描述符个数,时间超时返回0,出错或者select被某个信号中断返回-1。
2、下面我们来看一下几个select函数相关的宏
FD_CLR(inr fd, fd_set *fdset);用来清除描述符集合fdset中的描述符fd
FD_ISSET(int fd,f d_set *fdset);用来检测描述符集合fdset中的描述符fd是否发生了变化
FD_SET(int fd, fd_set *fdset);用来将描述符fd添加到描述符集合fdset中
FD_ZERO(fd_set *fdset);用来清除描述符集合fdset
3、实例分析
下面我们来看一个实例,这个实例是一个程序中的一个线程,它的作用是对2个CAN口和3个串口进行监控,当接口有数据发送来的时候把数据读到数组中,代码用c++编写,所有代码放到一个类中,并且这个类的start函数启动一个线程来执行上面说的监控功能。在类的构造函数中,初始化了接口,如下所示。
ReceiveUartData::ReceiveUartData()
{
//初始化CAN0
can0_fd=can0.CanInit(CAN0);
can1_fd=can1.CanInit(CAN1);
//初始化串口
uart1_fd = uart1.uartOpen(UART1,O_RDWR|O_NONBLOCK,BAUDRATE1);
uart2_fd = uart2.uartOpen(UART2,O_RDWR|O_NONBLOCK,BAUDRATE2);
uart3_fd = uart3.uartOpen(UART3,O_RDWR|O_NONBLOCK,BAUDRATE3);
}
初始化接口时,会返回各接口对应的文件描述符。
在线程的run函数中,通过一个while(1)循环对几个接口进行循环监控。
在执行select函数之前,首先要把相应的描述符填写到集合中,如下所示代码。
//清零描述符集合
FD_ZERO(&read_set);
max_fd=-1;
//装载描述符
for(int i=0;i<5;i++)
{
if(fd_array[i]>max_fd)
max_fd=fd_array[i];
FD_SET(fd_array[i],&read_set);
}
然后是,设置超时时间,并启动select监控,如下所示
//设置超时时间
timeout.tv_sec=3;
timeout.tv_usec=0;
ret = select(max_fd+1, &read_set, 0, 0, &timeout);
这里设置的超时时间是3秒,如果3秒内几个接口都没有数据,则select超时退出,如果某个接口有数据,则向下进行。下面的代码是对几个接口的数据接收。
if (ret == -1)
{
printf("select function failed!\n");
}
else if (ret == 0)
{
printf("select function time out!\n");
}
else
{
//判断是哪个设备来的数据并读取
if(FD_ISSET(can0_fd,&read_set))
{
struct can_frame can0_data = can0.readBus(can0_fd);
}
if(FD_ISSET(can1_fd,&read_set))
{
struct can_frame can1_data = can1.readBus(can1_fd);
}
if(FD_ISSET(uart1_fd,&read_set))
{
num1 = uart1.uartRead(uart1_fd,rbuff1,200);
}
if(FD_ISSET(uart2_fd,&read_set))
{
num2 = uart2.uartRead(uart2_fd,rbuff2,200);
}
if(FD_ISSET(uart3_fd,&read_set))
{
num3 = uart3.uartRead(uart3_fd,rbuff3,200);
}
}
在这段中,如果判断有数据接收,则会对每个接口依次进行判断,并对有数据的接口发生来的数据进行接收。
这个实例的源码可以由本文章的资源中进行下载,由于程序的运行需要依赖相关的环境,所以这个源码不能直接编译运行,只作为编程参考。