有意思的select~
前言
最近在写一个小程序,也就是简单的系统调用,但是神奇的是,我用的这个系统调用刚好就阻塞了。如果你也写过应用程序,肯定也会遇到过这样的问题。
后来,发现了select这个好东西,可以用来监听文件描述。
select的作用
如果我们在read一个文件,如果文件马上有东西返回,那是非常愉快的事情,但是经常遇到一些情况,read不能马上返回数据,这时候,会造成我们的线程阻塞,就卡在那里不动。如果是ui界面,那情况就显得很尴尬,你的ui卡主了,作为一个计算机用户,那是一件非常崩溃的事情的。
人们为了解决这个问题,select就出现了。
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without blocking.
select 和 pselect 允许程序监听文件描述符,文件描述符是打开文件的时候返回从一个整数,这个整数代表了一个文件,大家都叫他做文件描述符。直到文件描述符准备好了IO操作。
原来的代码
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/ioctl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#define RETRY_TIMES (20)
#define COM_STR "ezsp ver"
int main(int argc, char * const argv[])
{
int fd_in,fd_out,size;
int retry_times=0;
int ret=0;
//char s[ ]="info\n",buffer[1024];
char s[ ]="version\n",buffer[1024];
printf("=== weiqifa ===Zigbee test start ...\n");
printf("argc:%d\n",argc);
printf("argv[0]:%s\n",argv[0]);
/*打开写管道文件*/
fd_in=open("/dev/gateway_in",O_RDWR);
if(fd_in<0){
printf("===weiqifa=== open error:%d\n",fd_in);
return(0);
}
/*打开读管道文件*/
fd_out=open("/dev/gateway_out",O_RDWR);
if(fd_out<0){
printf("===weiqifa=== open error:%d\n",fd_out);
return(0);
}
/*循环读写*/
do
{
ret = write(fd_in,s,sizeof(s));
size= read(fd_out,buffer,sizeof(buffer));
printf("%s",buffer);
if(strncmp(COM_STR,buffer,strlen(COM_STR) -1) == 0){
break;
}
usleep(3000);
}while(retry_times++ <=RETRY_TIMES);
if(retry_times>= RETRY_TIMES)
{
printf("\nfail\n");
return (-1);
}
/*关闭管道*/
close(fd_out);
close(fd_in);
printf("\nsuccess\n");
return (0);
}
修改后的代码
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/ioctl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#define RETRY_TIMES (20)
#define COM_STR "ezsp ver"
int main(int argc, char * const argv[])
{
int fd_in,fd_out,size;
int retry_times=0;
int ret=0;
struct timeval tv;
fd_set rdfds;
/*清空rdfds*/
FD_ZERO(&rdfds);
//char s[ ]="info\n",buffer[1024];
char s[ ]="version\n",buffer[1024];
printf("=== weiqifa ===Zigbee test start ...\n");
printf("argc:%d\n",argc);
printf("argv[0]:%s\n",argv[0]);
/*打开写管道文件*/
fd_in=open("/dev/gateway_in",O_RDWR);
if(fd_in<0){
printf("===weiqifa=== open error:%d\n",fd_in);
return(0);
}
/*打开读管道文件*/
fd_out=open("/dev/gateway_out",O_RDWR);
if(fd_out<0){
printf("===weiqifa=== open error:%d\n",fd_out);
return(0);
}
/*循环读写*/
do
{
ret = write(fd_in,s,sizeof(s));
tv.tv_sec = 1; /*秒*/
tv.tv_usec = 500; /*微秒*/
/*添加监听的设备描述符*/
FD_ZERO(&rdfds);
FD_SET(fd_out,&rdfds);
/*监听fd_out*/
ret = select(fd_out+1,&rdfds,NULL,NULL,&tv);
if(ret<0){
printf("selcet error\r\n");
retry_times = RETRY_TIMES;
break;
}else if(ret == 0){ /*超时*/
printf("timeout \r\n");
retry_times = RETRY_TIMES;
break;
}else{
printf("ret = %d \r\n",ret);
}
size= read(fd_out,buffer,sizeof(buffer));
printf("%s",buffer);
if(strncmp(COM_STR,buffer,strlen(COM_STR) -1) == 0){
break;
}
usleep(3000);
}while(retry_times++ <=RETRY_TIMES);
if(retry_times>= RETRY_TIMES)
{
printf("\nfail\n");
return (-1);
}
/*关闭管道*/
close(fd_out);
close(fd_in);
printf("\nsuccess\n");
return (0);
}
select代码的小例子
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
int
main(void)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Data is available now.\n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.\n");
exit(EXIT_SUCCESS);
}
执行 第一次执行的时候,我没有输入任何内容,这时候,select就一直监听标准输入,因为没有输入就一直等,等到了超时时间,程序就退出了。
第二次执行的时候,我给标准输入输入东西了,select马上就返回,并打印了数据是有效的。
深入理解select模型
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
执行fd_set set; FD_ZERO(&set); 则set用位表示是0000,0000。
若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
若再加入fd=2,fd=1,则set变为0001,0011
执行select(6,&set,0,0,0)阻塞等待
若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
最后举个例子
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/ioctl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
int main(int argc, char * const argv[])
{
int fd_out,size;
int ret=0;
struct timeval tv;
fd_set rdfds;
char buffer[1024];
printf("=== weiqifa === test start ...\n");
printf("argc:%d\n",argc);
printf("argv[0]:%s\n",argv[0]);
/*打开读管道文件*/
fd_out=open("./test",O_RDWR);
if(fd_out<0){
printf("===weiqifa=== open error:%d\n",fd_out);
return(0);
}
/*清空rdfds*/
FD_ZERO(&rdfds);
/*添加监听的设备描述符*/
FD_SET(fd_out,&rdfds);
tv.tv_sec = 10; /*秒*/
tv.tv_usec = 500; /*微秒*/
/*监听fd_out*/
ret = select(fd_out+1,&rdfds,NULL,NULL,&tv);
if(ret<0){
printf("selcet error\r\n");
goto exit;
}else if(ret == 0){ /*超时*/
printf("timeout1 \r\n");
goto exit;
}else{
printf("ret = %d \r\n",ret);
}
size= read(fd_out,buffer,sizeof(buffer));
printf("%s",buffer);
exit:
/*关闭管道*/
close(fd_out);
printf("\nsuccess\n");
return (0);
}
执行截图
===========
PS:想加入技术群的同学,加了我好友后,就给我发「篮球的大肚子」这句话,有可能机器人打瞌睡,可以多发几次,不要发与技术无关的消息或者推广。
如果想获取学习资料,就在公众号后台回复「1024」,足够多的学习资料可以让你学习。