今天分享的是几种实现并发式IO的方法。什么是并发式IO呢?可以简单理解为比如要同时读取几个文件的数据,但是这些文件什么时候可以读取是不确定的,要实现当某个文件可以读取的时候就立马去读取,这就是并发式。
首先提出一个问题:如果我们需要读取键盘和鼠标的信息,当键盘有按下的时候把按下的内容读取出来并且打印到屏幕上,当鼠标有动静的时候也把鼠标的设备文件读取出来,该怎么实现呢?
首先想到的就是在主函数里写个while(1)挨个去读就行了,伪代码如下:
代码语言:javascript复制//伪代码
while(1)
{
read(keyboard);
printf("keyboard...");
read(mouse);
printf("mouse...");
}
这样的程序确实可以读取键盘和鼠标的内容并且打印出来,但是必须老老实实按照代码里的,先读键盘,再读鼠标这样往复,如果用户想要先读鼠标,再读键盘,抱歉,它会卡在前面这个read这里,因为read函数是阻塞式的,没有读到东西它就一直卡在那里。这显然不是我们希望的,我们希望像按键盘就按键盘,想动鼠标就动鼠标,并且它都能打印出来。
于是,有了以下几种方法来解决这个问题。
一、以非阻塞的方式来打开文件
在使用open函数的时候,加上O_NONBLOCK属性,变为非阻塞,而标准输入一开始就打开了,对应文件描述符为0,所以不能用上面的方法,应该用fcntl函数来添加。变为非阻塞式的好处就是当read没有读到什么东西的时候会立马返回,不会卡在那里。代码如下:
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define pathname "/dev/input/mice"
int main()
{
int fd;
int ret;
char buf[100]={0};
fd=open(pathname,O_RDWR | O_NONBLOCK);
if(fd<0)
{
perror("open failed");
return 0;
}
flag = fcntl(0, F_GETFL); // 先获取原来的flag
flag |= O_NONBLOCK; // 添加非阻塞属性
fcntl(0, F_SETFL, flag); // 更新flag
while(1)
{
memset(buf,0,sizeof(buf));
ret= read(fd,buf,50); //读鼠标
// if(ret<0)
// {
// printf("read mouse ret=%dn",ret);
// perror("read mouse failed");
// // return 0;
// }
if(ret>0)
{
printf("读出的鼠标内容是:[%s]n",buf);
}
memset(buf,0,sizeof(buf));
ret= read(0,buf,5); //读键盘
// if(ret<0)
// {
// perror("read keyboard failed");
// printf("read keyboard ret=%dn",ret);
// // return 0;
// }
if(ret>0)
{
printf("读出的键盘内容是:[%s]n",buf);
}
}
return 0;
}
上面的代码其实就实现了不管是按下键盘,还是点击鼠标,都能及时反应,打印出数据。但是还有更好的方法,使用系统里带的select函数或者是poll函数来监听IO的状况。
二、使用select函数或者poll函数
select函数和poll函数功能上差不多,是Unix两个不同的派系衍生出来的函数,后来linux把它们都吸收了。select函数在上一节使用到了,可以回顾一下:Linux笔记(11)| 网络编程之自己动手写一个服务器和客户端
select函数首先把把要监听的文件描述符fd添加到一个集合里面,然后调用select函数去监听,通过返回值可以判断监听的fd的状态,比如已经可写了、或者是可读了。代码如下:
代码语言:javascript复制#include <stdio.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define pathname "/dev/input/mice"
int main(void)
{
int ret;
int fd;
char buf[200];
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
fd_set myset;
fd=open(pathname,O_RDONLY);
if(fd<0)
{
perror("open mice failed");
return 0;
}
while(1)
{
FD_ZERO(&myset);
FD_SET(0, &myset);
FD_SET(fd, &myset);
ret=select(fd 1,&myset,NULL,NULL,NULL);
if(ret<0)
{
perror("select");
return 0;
}
else if(ret==0)
{
printf("time outn");
sleep(2);
}
else
{
if( FD_ISSET(fd,&myset))
{
memset(buf,0,sizeof(buf));
read(fd,buf,5);
printf("读鼠标:[%s]n",buf);
}
if(FD_ISSET(0,&myset))
{
memset(buf,0,sizeof(buf));
read(0,buf,5);
printf("读键盘:[%s]n",buf);
}
}
}
return 0;
}
poll函数实现的功能差不多,只是用法上有些不一样,这里直接把代码贴上:
代码语言:javascript复制#include <stdio.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define pathname "/dev/input/mice"
int main(void)
{
int fd;
int ret;
char buf[100];
struct pollfd mypoll[2]={0};
fd=open(pathname,O_RDONLY);
if(fd<0)
{
perror("open failed");
return 0;
}
while(1)
{
mypoll[0].fd=0;
mypoll[0]. events=POLLIN;
mypoll[1].fd=fd;
mypoll[1]. events=POLLIN;
ret=poll(mypoll,fd 1,10000);
if(ret<0)
{
perror("poll");
return 0;
}
else if(ret==0)
{
printf("time outn");
}
else
{
// printf("mypoll.revents=%dn",mypoll.revents);
// printf("mypoll.events=%dn",mypoll.events);
if(mypoll[0].revents==mypoll[0].events)
{
memset(buf,0,sizeof(buf));
ret=read(0,buf,10);
if(ret<0)
{
perror("read keyboard failed ");
return 0;
}
printf("read keyboard:[%s]",buf);
}
if(mypoll[1].revents==mypoll[1].events)
{
memset(buf,0,sizeof(buf));
ret=read(fd,buf,2);
if(ret<0)
{
perror("read mouse failed ");
return 0;
}
printf("read mouse:[%s]n",buf); //这里没加换行就不会及时打印
}
}
}
return 0;
}
三、使用异步IO
第三种方法就是使用异步IO,这种方法类似于中断,就是在主函数里来处理鼠标(或者键盘也一样),然后注册一个异步IO事件,当有键盘按下的时候,产生一个异步IO信号,这个信号就会触发一个注册号的函数来处理它。
代码如下:
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
#define pathname "/dev/input/mice"
void handler(int sig);
char buf[200];
int fd;
int main(void)
{
int flag;
int ret;
fd=open(pathname,O_RDONLY);
if(fd<0)
{
perror("open failed");
return 0;
}
// 把鼠标的文件描述符设置为可以接受异步IO
flag=fcntl(fd,F_GETFL);
flag|=O_ASYNC;
fcntl(fd,F_SETFL,flag);
// 把异步IO事件的接收进程设置为当前进程
fcntl(fd,F_SETOWN,getpid());
// 注册当前进程的SIGIO信号捕获函数
signal(SIGIO,handler);
while(1)
{
memset(buf,0,sizeof(buf));
ret=read(0,buf,10);
// if(ret<0)
// {
// perror("read failed");
// return 0;
// }
if(ret>0)
printf("read keyboard :[%s]n",buf);
//sleep(2);
}
return 0;
}
void handler(int sig)
{
int ret;
if(sig!=SIGIO)
return;
memset(buf,0,sizeof(buf));
ret=read(fd,buf,5);
if(ret<0)
{
perror("read failed");
return ;
}
printf("read mouse :[%s]n",buf);
}
以上是今天分享的几种方法,实际上还可以用多进程或者多线程的方法,这在上一节里也有涉及,这里就不多说了。