专栏首页电子技术研习社Linux笔记(12)| 几种并发式IO的实现方法

Linux笔记(12)| 几种并发式IO的实现方法

今天分享的是几种实现并发式IO的方法。什么是并发式IO呢?可以简单理解为比如要同时读取几个文件的数据,但是这些文件什么时候可以读取是不确定的,要实现当某个文件可以读取的时候就立马去读取,这就是并发式。

首先提出一个问题:如果我们需要读取键盘和鼠标的信息,当键盘有按下的时候把按下的内容读取出来并且打印到屏幕上,当鼠标有动静的时候也把鼠标的设备文件读取出来,该怎么实现呢?

首先想到的就是在主函数里写个while(1)挨个去读就行了,伪代码如下:

//伪代码
while(1)
{
  read(keyboard);
  printf("keyboard...");
  read(mouse);
  printf("mouse...");
}

这样的程序确实可以读取键盘和鼠标的内容并且打印出来,但是必须老老实实按照代码里的,先读键盘,再读鼠标这样往复,如果用户想要先读鼠标,再读键盘,抱歉,它会卡在前面这个read这里,因为read函数是阻塞式的,没有读到东西它就一直卡在那里。这显然不是我们希望的,我们希望像按键盘就按键盘,想动鼠标就动鼠标,并且它都能打印出来。

于是,有了以下几种方法来解决这个问题。

一、以非阻塞的方式来打开文件

在使用open函数的时候,加上O_NONBLOCK属性,变为非阻塞,而标准输入一开始就打开了,对应文件描述符为0,所以不能用上面的方法,应该用fcntl函数来添加。变为非阻塞式的好处就是当read没有读到什么东西的时候会立马返回,不会卡在那里。代码如下:

#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=%d\n",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=%d\n",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的状态,比如已经可写了、或者是可读了。代码如下:

#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 out\n");
            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函数实现的功能差不多,只是用法上有些不一样,这里直接把代码贴上:

#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 out\n");
        }
        else
        {
                // printf("mypoll.revents=%d\n",mypoll.revents);
                // printf("mypoll.events=%d\n",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信号,这个信号就会触发一个注册号的函数来处理它。

代码如下:

#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);
}

以上是今天分享的几种方法,实际上还可以用多进程或者多线程的方法,这在上一节里也有涉及,这里就不多说了。

本文分享自微信公众号 - 电子技术研习社(zjf18770701843),作者:小小飞飞哥

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux笔记(1)| 常用命令

    从今天开始将会陆续更新一些有关Linux的笔记,从arm裸机到uboot的移植、Linux应用编程和网络编程再到Linux驱动开发。同时也会夹杂更新stm32对...

    飞哥
  • 小实验 | 利用信号量实现跑马灯

    上一次我们说到了uCOS中的信号量,信号量具有同步的作用,今天做一个小实验来说明这个。

    飞哥
  • Linux笔记(5)| 文件IO操作(续)

    上一次我们说到了文件的常规操作,打开,读,写,关闭这些,重点在于打开是以什么样的方式来打开,包括文件的权限,内容是否清空,打开不存在的文件等等情形。今天继续说一...

    飞哥
  • Nature机器学习子刊被指开历史倒车,Jeff Dean等数百名学者联名抵制

    新智元
  • 【双11钜惠】腾讯爆款安全产品特惠5折起

    腾讯云安全会场双十一大促来了! 全场促销活动低至2折, 更有爆款产品五折限量秒杀! 下拉了解更多优惠详情~ ? ? ? ➤推荐阅读 干货!金融、政务、互联网,...

    腾讯云安全
  • js中(function(){})()的写法用处

    后来查了下资料,js中(function(){…})()立即执行函数写法理解,终于了解了。

    帅的一麻皮
  • 宿主

        ASP.NET Core应用程序需要在宿主中执行.宿主必须实现IWebHost接口,这个接口暴露了功能和服务的集合,以及Start方法。宿主通常使用We...

    莫问今朝
  • 目标检测近年综述(文末有下载论文包)

    发展论述:对于Overfeat、R-CNN方法,只是利用卷积神经网络进行特征提取,并没有改变搜索框提取目标区域的策略,算法的在速度上仍存在瓶颈。

    计算机视觉研究院
  • 腾讯安全月度十大要闻 | 2019年8月

    腾讯安全
  • HDOJ 1006

    Tick and Tick Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/327...

    用户1154259

扫码关注云+社区

领取腾讯云代金券