欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

有意思的select~

程序员文章站 2022-04-06 23:42:30
...

前言

最近在写一个小程序,也就是简单的系统调用,但是神奇的是,我用的这个系统调用刚好就阻塞了。如果你也写过应用程序,肯定也会遇到过这样的问题。

后来,发现了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操作。

有意思的select~

原来的代码

#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模型

理解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);
}

执行截图

有意思的select~

===========

有意思的select~

  

PS想加入技术群的同学,加了我好友后,就给我发「篮球的大肚子」这句话,有可能机器人打瞌睡,可以多发几次,不要发与技术无关的消息或者推广。

如果想获取学习资料,就在公众号后台回复「1024」,足够多的学习资料可以让你学习。