前言
最近在写一个小程序,也就是简单的系统调用,但是神奇的是,我用的这个系统调用刚好就阻塞了。如果你也写过应用程序,肯定也会遇到过这样的问题。
后来,发现了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>intmain(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. */elseprintf("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」,足够多的学习资料可以让你学习。