首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux笔记(12)| 几种并发式IO的实现方法

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

作者头像
飞哥
发布2020-07-31 11:27:42
6350
发布2020-07-31 11:27:42
举报

今天分享的是几种实现并发式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);
}

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档