阻塞式IO 和非阻塞式IO

之前看别人的文章,大概就是用在服务器客户端之间的通信,因为一个服务器对很多个客户端,要排队,但是为了节省资源提高效率所以就很多用poll来帮助实现的。

废话在前面在我的理解里面,阻塞式就是运行到那个位置之后就会一直等待到合适的信号才会继续进行,不然就会一直在那里等待等待,除非强制退出。
然后这个等待呢不是就一直卡死的,是相当于把时间给其他有需要的程序,它自个在那里等待,不会一直耗着CPU不放。

阻塞式的情况在只需要一路阻塞的时候还是挺有用的,但是如果有多路IO阻塞的话,这时候就需要排队解决,上一个阻塞的话下一个就不能运行,这就出现了困境。

非阻塞式就是相对应与阻塞式的,就是不会等待的。
比如你open一个文件的时候,加上O_NONBLOCK这个flag的话就不会阻塞了。

或者是使用fcntl这个API来通过读取fd的flag,改fd的flag,再赋给fd赋新的flag来加上O_NONBLOCK这个flag。

标准输入

键盘的输入
默认的时候 fd = 0就是标准输入,键盘的输入。
鼠标呢?
在linux环境下
在目录 /dev/input里面,有很多个mouse,有mouse0 mouse1 mouse2
在这里插入图片描述
我这里是mouse0是我这个鼠标。
通过cat mouse0 ,然后会阻塞,这时候再移动鼠标,如果出现一堆这些二进制乱码,就证明这个输入mouse0是当前的输入了。

一些相关的API

fcntl

SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>

       int fcntl(int fd, int cmd, ... /* arg */ );

这里只取其中的一个用法,使用File status flags里面的F_GETFL 和F_SETFL两个flag

	flag = fcntl(0, F_GETFL);		// 先获取原来的flag
	flag |= O_NONBLOCK;				// 添加非阻塞属性
	fcntl(0, F_SETFL, flag);		// 更新flag

就类似这样,先用F_GETFL获取原来的状态flag,然后添加非阻塞属性,最后再把新的flag通过F_SETFL重新更新fd = 0这个文件描述符的flag。

select

select的原型如下,在man手册里面复制过来的。

SYNOPSIS
       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

Description:

	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

select和pselect会允许一个进程去监视多个文件描述符,一直阻塞等待,等到一个或者多个文件描述符因为一些IO操作而变成就绪状态,这时候才取消等待,或者说是取消阻塞吧

nfds: 手册里面说nfds要设置成为最大的文件夹秒描述符再plus 1的值,
readfds: 代表要传进去一个可读的参数
writefds: 要传进去一个可写的
exceptfds: 代表一些额外的情况的。(在poll里面有讲。)
fd_set * 这是一个什么类型?
刚开始看到这里的时候是懵逼的,后面发现在手册最后有个例子:

EXAMPLE
       #include <stdio.h>
       #include <stdlib.h>
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.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);
       }


看开头,它是定义了一个fd_set类型的一个变量,然后再把这个变量传进去原型里面

	 void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

这些函数里面进行使用的

 	   Four macros are provided to manipulate the sets.  FD_ZERO()  clears  a
       set.   FD_SET()  and FD_CLR() respectively add and remove a given file
       descriptor from a set.  FD_ISSET() tests to see if a  file  descriptor
       is part of the set; this is useful after select() returns.

原来这4个是宏命令来的,macros(宏命令)
FD_ZERO():用来清零一个“set”的
FD_SET() and FD_CLR() :用来把你的文件描述符放进去或者移除出这个“set”
FD_ISSET() :判断一个fd是不是已经是在这个“set”里面

这个“set”是什么呢,按照我的理解就是你的fd想被select所使用必须加入这个“set”里面,这个“set”是fd_set *类型的指针变量,所以后面使用上面的宏命令的时候要记得用&取地址再传进去。

最后struct timeval* timeout:
设置一个超时时间,超过这个时间的话select就不等了,不阻塞了。具体使用看上面man手册给的例子。

select 的实战代码

读键盘和读鼠标
一读到就

#include <stdio.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main(int argc,char *argv[])
{
int fd = -1;
	fd = open("/dev/input/mouse0",O_RDONLY);
	if (-1 == fd)
	{
		perror("open");
	}

	char buf[100]; //定义一个buff,准备用来存要读的数据
	int ret = -1;
	fd_set rfds;
	struct timeval tv;
	FD_ZERO(&rfds);
	FD_SET(0,&rfds);//键盘的input
	FD_SET(fd,&rfds);//鼠标的input
	tv.tv_sec = 5;
	tv.tv_usec = 0;
	
	int mouse = 0,keyboard = 0;

	ret = select(fd+1,&rfds,NULL,NULL,&tv);
	mouse = FD_ISSET(fd,&rfds);
	keyboard = FD_ISSET(0,&rfds);
	if (-1 == ret)
	{
		perror("select");
	}
	else if(ret)
	{
		if(0 != mouse)
		{
			memset(buf,0,sizeof(buf));
			read(0,buf,sizeof(buf));
			printf(" mouse :what i read is [%s]\n",buf);
		}
	
		if(0 != keyboard)
		{
			memset(buf,0,sizeof(buf));
			read(0,buf,sizeof(buf));
			printf("k :what i read is [%s]\n",buf);
		}
			
	}
	else
		printf("No data within five seconds.\n");

	return 0;
}

从select之后,先通过select的返回值ret0判断select是否成功,然后通过FD_ISSET来判断你的IO使得哪个fd变成“ready”了,最终显示出来。

poll

相比较于select ,我觉得poll用起来还比较简单。

SYNOPSIS
       #include <poll.h>

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);

 	struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

fds是一个指针变量,指向一个struct pollfd结构体,这里面要填的你的fd的具体信息。
nfds跟select里面的是一样的。
timeout的话是以毫秒为单位的,5秒的话就设置成5000。

刚开始不看课程会不怎么懂这个 event和revent的使用。
这两个东西跟select里面的FD_ISSET类似,FD_ISSSET是用来判断这个fd是不是“ready”了,而这里是通过比较event和revent是否相等来判断这个fd是否“ready”了。

poll的实现代码

#include <stdio.h>
#include <unistd.h> 
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>


int main(void)
{
	//获取鼠标的fd
	int fd = -1, ret = -1;
	char buf[500];
	struct pollfd myfds[2] = {0};
	int i = 0;

	fd = open("/dev/input/mouse0", O_RDONLY);
	if (fd < 0)
	{
		perror("open");
		return -1;
	}

	myfds[0].fd = 0;	//键盘
	myfds[0].events = POLLIN;  //       man手册里面讲 POLLIN There is data to read.

	
	myfds[1].fd = fd;	//鼠标
	myfds[1].events = POLLIN; 
	
	ret = poll(myfds, fd+1, 5000); 


	if (ret < 0)
	{
		perror("poll");
		return -1;
	}
	else if (ret == 0)
	{
		printf("超时了\n");
	}
	else
	{
	
	for (i=0; i<2; i++)
	{
		if (myfds[i].events == myfds[i].revents)
		{
			switch (i)
			{
				case 0:
					printf("keyboard\n");
					break;
				case 1:
					printf("mouse\n");
					break;
			}
			
			return 0;
		}
	}
		/* 
		if (myfds[0].events == myfds[0].revents)
		{
			
			printf("keyboard\n");
			memset(buf, 0, sizeof(buf));
			read(0, buf, 50);
			printf(" keyboard :what i read is [%s]\n",buf);
			return 0;
		}
		
		 if(myfds[1].events == myfds[1].revents)
		{
			
			printf("mouse\n");
			memset(buf, 0, sizeof(buf));
			read(fd, buf, 50);
			printf(" mouse :what i read is [%s]\n",buf);
			return 0;
		}  */
	}
	
	return 0;
}

这里都比较好理解,但是之前自己写的时候如果设置的buff小于105的话,一运行就直接结束了,我也没搞懂,可能105是一个魔法数字。。。。
最后面注释的两个if,之前是用if来实现的,后面试着用for循环,因为结构都一样,如果想read的话可以在case里面把read的代码加上去。

发布了38 篇原创文章 · 获赞 1 · 访问量 1024

猜你喜欢

转载自blog.csdn.net/qq_40897531/article/details/104862684