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

2020-07-31 11:27:42 浏览数 (1)

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

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

0 人点赞