• (51)LINUX应用编程和网络编程之六Linux高级IO


    3.6.1.非阻塞IO
    3.6.1.1、阻塞与非阻塞
    阻塞:阻塞具有很多优势(是linux系统的默认设置),单路IO的时候使用阻塞式IO没有降低CPU的性能
    补充:阻塞/非阻塞, 它们是程序在等待消息(无所谓同步或者异步)时的状态.
    阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
    有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。
    对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
    非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
    3.6.1.2、为什么有阻塞式
    (1)常见的阻塞:wait(显式回收子进程)、pause、sleep等默认阻塞函数;read或write某些文件时也是默认阻塞式的
    (2)阻塞式的好处:当前线程被挂起等待条件满足才返回结果
    3.6.1.3、非阻塞
    (1)为什么要实现非阻塞
    (2)如何实现【非阻塞IO访问】:
    1)打开文件时加入O_NONBLOCK
    2)fcntl函数,对文件描述符(文件已经打开)进行操作
     
     
    单路IO就用阻塞式比较好;
    多路IO最好是用非阻塞式的。避免资源被一个占有不放,让其他IO有资源可抢。
    从CPU的利用角度来看,单路IO就是一对一;多路IO就是一对多,即一个CPU对应多个资源抢占通道。前者单路效率更高,后者需要兼顾分配。单路IO模型只需要监听一个IO流,多路IO模型可以同时监听(内部轮循)多个IO流,CPU要不停去查看。
     
     
    3.6.2.阻塞式IO的困境
    3.6.2.1、程序中读取键盘
    3.6.2.2、程序中读取鼠标        cat         /dev/input/mouse1
    3.6.2.3、程序中同时读取键盘和鼠标
    3.6.2.4、问题分析
    并不是所有的情况下都适阻塞式io的。
    代码示例:
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>   
    /*
    程序中读取键盘和鼠标
    */
    #define NAME     "/dev/input/mouse1"   
    char buff[100]={0};
    char buf[100]={0};
    int main(int argc,char **argv)
    {
        int ssize_t=-1;
        int fd=-1;
     
        fd=open(NAME,O_RDWR);
        if(-1==fd)
        {
            perror("open");
            _exit(-1);
        }
        printf("打开成功!fd=%d ",fd);
        while(1){
             ssize_t=read(fd,buff,sizeof(buff));
        if(-1==ssize_t)
        {
            perror("read");
            _exit(-1);
        }
    printf("读到的鼠标字符数为%d ",ssize_t);
    printf("读到的鼠标字符是[%s] ",buff);       
    read(0,&buf,sizeof(buf));
    printf("读到的鼠标字符是[%s] ",buf);         
    sleep(1);
        }
         return 0;
    3.6.3.并发式IO的3种解决方案【并发式IO】
    3.6.3.1、非阻塞式IO:性能不够好,有点类似于轮询的方式,CPU不停的去查看
    代码示例:
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
     
    #define NAME     "/dev/input/mouse1"   
    char buff[100]={0}; //键盘
    char buf[100]={0};//鼠标
    int main(void)
    {
        int ssize_t=-1;
        int fd=-1;
        int flag=-1;
        int ret=-1;
        fd=open(NAME,O_RDWR | O_NONBLOCK );
        if(-1==fd)
        {
            perror("open");
            _exit(-1);
        }
     
        //把标准输入文件描述符0通过fcntl函数变成非阻塞式子
     
        // 把0号文件描述符(stdin)变成非阻塞式的
        flag = fcntl(0, F_GETFL);        // 先获取原来的flag
        flag |= O_NONBLOCK;                // 添加非阻塞属性
        fcntl(0, F_SETFL, flag);        // 更新flag
        // 这3步之后,0就变成了非阻塞式的了
     
            while (1)
        {
            // 读鼠标
            memset(buf, 0, sizeof(buf));
            //printf("before 鼠标 read. ");
            ret = read(fd, buf, 50);
            if (ret > 0)
            {
                printf("鼠标读出的内容是:[%s]. ", buf);
            }
     
            // 读键盘
            memset(buff, 0, sizeof(buff));
            //printf("before 键盘 read. ");
            ret = read(0, buff, 5);
            if (ret > 0)
            {
                printf("键盘读出的内容是:[%s]. ", buff);
            }
        }
     
    return 0;
    }
    3.6.3.2、多路复用IO :性能相对比较好,解决并发性IO的解决
     
     
     
    3.6.4.IO多路复用原理
    3.6.4.1、何为IO多路复用   【说白了,多路复用其实就是一个管多个】
    (1)IO           multiplexing
    (2)用在什么地方?多路非阻塞式IO(多路及时响应)。
    (3)select和poll两个函数:(poll出现的比较晚一点。其性能也要比select函数的性能更好一些)
    (4)外部阻塞式(select和poll两个函数本身在调用的时候是阻塞式的,对外表现为阻塞式),内部非阻塞式(
    两个函数内部实现的时候,也就是当把要监听的东西(比如A和B两个阻塞式IO)放在这个函数的监听范围)【自动】轮询【这两个多路阻塞式IO】               (轮询的意思就是CPU不停的去查看)
     
    补充:
    可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要
    等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
    3.6.4.2、select函数介绍:
    #include <sys/select.h>
    #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);     //nfds表示文件描述符的个数 nfds is the highest-numbered file descriptor in any of the //three sets, plus 1,是指集合中所有文件描述符的范围,【即所有文件描述符的最大值加1】这一点要注意,所有文件描述符的最大值加1,比如一个文件描述符是0,一个文件描述符是1,则为1+1
    相关函数:
    (1)void FD_ZERO(fd_set *set);       //把文件描述符集合清零
    (2)void FD_SET(int fd, fd_set *set);   //把某个文件描述符添加到这个集合中去
    (3)int  FD_ISSET(int fd, fd_set *set);
    //检查集合中指定的文件描述符是否可以读写 FD_ISSET() tests to see if a file descriptor is part of the set; this is useful after select() returns.
    (4)void FD_CLR(int fd, fd_set *set);   //把一个给定的文件描述符从集合中删除
    相关结构体:
     (1)struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor)。
    (2)     
            struct timeval {
             long    tv_sec;         /* seconds */
             long    tv_usec;        /* microseconds */
            };                 
    struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
    linux内部代码示例:
    #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);   //把0文件描述符添加到这个集合中去
     
        /* 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. ");
                   /* FD_ISSET(0, &rfds) will be true. */
               else
                   printf("No data within five seconds. ");
               exit(EXIT_SUCCESS);
    }
     
    3.6.4.3、poll函数介绍
    #include <poll.h>
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    #define _GNU_SOURCE         /* See feature_test_macros(7) */
    结构体:
               struct pollfd {
                   int   fd;         /* file descriptor */
                   short events;     /* requested events */
                   short revents;    /* returned events */
               };
     
    代码示例:
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <poll.h>
    /*
    #include <poll.h>
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    */
    #define NAME  "/dev/input/mouse1"
     
    int main(void)
    {
         char buf[100]={0};                 //注意这里的定义的buf不能够使用全局的
        //int poll(struct pollfd *fds, nfds_t nfds, int timeout);
        struct pollfd a[2]={0};
        int fd= open(NAME,O_RDWR);   //定义鼠标文件描述符
        if(fd<0)
        {
            perror("open mouse");
            _exit(-1);
        }
        //实例化结构体
        a[0].fd=fd;               //鼠标
        a[0].events=POLLIN;
        a[1].fd=0;                 //键盘
        a[1].events=POLLIN;
        int ret=poll(a,fd+1,10000);
        if(ret<0)
       {
           perror("poll");
           _exit(-1);
        }   
        if(ret==0)
        {
            printf("超时了");
        }
        else     //判断是谁发生了IO
        {
            if(a[0].events==a[0].revents)        //鼠标
            {
                memset(buf,0,sizeof(buf));
                read(fd,buf,10);
                printf("读出来的鼠标内容是:[%s] ",buf);
            }
     
                if(a[1].events==a[1].revents)        //键盘
            {
                memset(buf,0,sizeof(buf));
                read(0,buf,50);
                printf("读出来的键盘内容是:[%s] ",buf);
           }
        }       
            return 0;
    }               
     
    3.6.5.IO多路复用实践
    3.6.5.1、用select函数实现同时读取键盘鼠标
    select()函数实现IO多路复用的步骤
    (1)清空描述符集合
    (2)建立需要监视的描述符与描述符集合的关系
    (3)调用select函数
    (4)检查监视的描述符判断是否已经准备好
    (5)对已经准备好的描述符进程IO操作
    代码示例:
    /*
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
     
    #define NAME     "/dev/input/mouse1"   
    char buf[100]={0};
    int main(void)
    {
        int fd=-1;
        fd_set readfds=-1;
        struct timeval time;
        int ret=-1;
        fd=open(NAME,O_RDWR);
        printf("fd=%d ",fd);
        if(-1==fd)
        {
            perror("open");
            _exit(-1);
        }
            FD_ZERO(&readfds);    //清除文件描述符集合
            FD_SET(fd,&readfds);
            FD_SET(0,&readfds);
            time.tv_sec=5;            //设置时间为5秒
            time.tv_usec=0;
            ret=select(3,&readfds,NULL,NULL,&time);           //调用select函数 ,注意第一个参数
           if (ret < 0)                      //表示出错
        {
            perror("select: ");
            return -1;
        }
        else if (ret == 0)              //表示超过了规定的时间限制
        {
            printf("超时了 ");
        }
        else
        {
            // 等到了一路IO,然后去监测到底是哪个IO到了,处理之
            if (FD_ISSET(0, &readfds))
            {
                // 这里处理键盘
                memset(buf, 0, sizeof(buf));
                read(0, buf, 5);
                printf("键盘读出的内容是:[%s]. ", buf);
            }
     
            if (FD_ISSET(fd, &readfds))
            {
                // 这里处理鼠标
                memset(buf, 0, sizeof(buf));
                read(fd, buf, 50);
                printf("鼠标读出的内容是:[%s]. ", buf);
            }
        }
    return 0;
    }
    3.6.5.2、用poll函数实现同时读取键盘鼠标
    poll函数介绍:
    #include <poll.h>
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    第一个参数 pollfd 结构体定义如下:
    引用
    /* Data structure describing a polling request.  */
    struct pollfd
      {
        int fd;                         /* poll 的文件描述符.  */
        short int events;           /* fd 上感兴趣的事件(等待的事件或者说是监视的事件).  */
        short int revents;          /* fd 上实际发生的事件.  */
      };
    fd 成员表示感兴趣的,且打开了的文件描述符;
    events  成员是位掩码,用于指定针对这个文件描述符感兴趣的事件;
    revents  成员是位掩码,用于指定当 poll 返回时,在该文件描述符上已经发生了哪些事情。
     
    events 和 revents 结合下列常数值(宏)指定即将唤醒的事件或调查已结束的 poll() 函数被唤醒的原因,这些宏常数如下:
    POLLIN
    events 中使用该宏常数,能够在折本文件的可读情况下,结束 poll() 函数。相反,revents 上使用该宏常数,在检查 poll() 函数结束后,可依此判断设备文件是否处于可读状态(即使消息长度是 0)。
     
    POLLPRI
    在 events 域中使用该宏常数,能够在设备文件的高优先级数据读取状态下,结束 poll() 函数。相反,revents 上使用该宏常数,在检查 poll() 函数结束后,可依此判断设备文件是否处于可读高优先级数据的状态(即使消息长度是 0)。该宏常数用于处理网络信息包(packet) 的数据传递。
     
    POLLOUT
    在 events 域中使用该宏常数,能够在设备文件的写入状态下,结束 poll() 函数。相反,revents 域上使用该宏常数,在检查 poll() 结束后,可依此判断设备文件是否处于可写状态。
     
    POLLERR
    在 events 域中使用该宏常数,能够在设备文件上发生错误时,结束 poll() 函数。相反,revents 域上使用该宏函数,在检查 poll() 函数结束后,可依此判断设备文件是否出错。
     
    POLLHUP
    在 events 域中使用该宏常数,能够在设备文件中发生 hungup 时,结束 poll() 函数 。相反,在检查 poll() 结束后,可依此判断设备文件是否发生 hungup 。
     
    POLLNVAL
    在 events 域中使用该宏函数,能够在文件描述符的值无效时,结束 poll() 。相反,在 revents 域上使用该宏函数时,在检查 poll() 函数后,文件描述符是否有效。可用于处理网络信息时,检查 socket handler 是否已经无效。
     
     
    最后一个参数 timeout 指定 poll() 将在超时前等待一个事件多长事件。这里有 3 种情况:
     
    1) timeout 为 -1
    这会造成 poll 永远等待。poll() 只有在一个描述符就绪时返回,或者在调用进程捕捉到信号时返回(在这里,poll 返回 -1),并且设置 errno 值为 EINTR 。-1 可以用宏定义常量 INFTIM 来代替(在 pth.h 中有定义) 。
     
    2) timeout 等于0
    在这种情况下,测试所有的描述符,并且 poll() 立刻返回。这允许在 poll 中没有阻塞的情况下找出多个文件描述符的状态。
     
    3) time > 0
    这将以毫秒为单位指定 timeout 的超时周期。poll() 只有在超时到期时返回,除非一个描述符变为就绪,在这种情况下,它立刻返回。如果超时周期到齐,poll() 返回 0。这里也可能会因为某个信号而中断该等待。
     
    和 select 一样,文件描述符是否阻塞对 poll 是否阻塞没有任何影响。
     
    代码示例:
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <poll.h>
     
    #define NAME  "/dev/input/mouse1"
     
    int main(void)
    {
         char buf[100]={0};                 //注意这里的定义的buf不能够使用全局的
        //int poll(struct pollfd *fds, nfds_t nfds, int timeout);
        struct pollfd a[2]={0};
        int fd= open(NAME,O_RDWR);   //定义鼠标文件描述符
        if(fd<0)
        {
            perror("open mouse");
            _exit(-1);
        }
        //实例化结构体
        a[0].fd=fd;               //鼠标
        a[0].events=POLLIN;
        a[1].fd=0;                 //键盘
        a[1].events=POLLIN;
        int ret=poll(a,fd+1,10000);
        if(ret<0)
       {
           perror("poll");
           _exit(-1);
        }   
        if(ret==0)
        {
            printf("超时了");
        }
        else     //判断是谁发生了IO
        {
            if(a[0].events==a[0].revents)        //鼠标
            {
                memset(buf,0,sizeof(buf));
                read(fd,buf,10);
                printf("读出来的鼠标内容是:[%s] ",buf);
            }
     
                if(a[1].events==a[1].revents)        //键盘
            {
                memset(buf,0,sizeof(buf));
                read(0,buf,50);
                printf("读出来的键盘内容是:[%s] ",buf);
           }
        }       
            return 0;
    }
     
    3.6.6.异步IO
    3.6.6.1、何为异步IO
    (1)几乎可以认为:异步IO就是操作系统用软件实现的一套中断响应系统(有点类似与硬件中断)。
    有两种类型的文件IO同步:同步文件IO和异步文件IO。异步文件IO也就是重叠IO。【在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。】
    如果IO请求需要大量时间执行的话,异步文件IO方式可以显著提高效率,因为在线程等待的这段时间内,CPU将会调度其他线程进行执行,如果没有其他线程需要执行的话,这段时间将会浪费掉(可能会调度操作系统的零页线程)。如果IO请求操作很快,用异步IO方式反而还低效,还不如用同步IO方式。
    同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读写操作。重叠IO允许一个或多个线程同时发出IO请求。
    异步IO在请求完成时,通过将文件句柄设为有信号状态来通知应用程序,或者应用程序通过GetOverlappedResult察看IO请求是否完成,也可以通过一个事件对象来通知应用程序。
    简单的说“同步在编程里,一般是指某个IO操作执行完后,才可以执行后面的操作。异步则是,将某个操作给系统,主线程去忙别的事情,等内核完成操作后通知主线程异步操作已经完成。”
    (2)异步IO的工作方法是:我们当前进程向内核注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,内核就去帮你完成或者说是检测你希望的事件是否发生,当异步事件发生后当前进程会收到内核传来的一个SIGIO信号(类似于中断信号)从而执行绑定的处理函数去处理这个异步事件。
    3.6.6.2、涉及的函数:
    (1)fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN)    设置异步通知
    (2)signal或者sigaction函数(SIGIO)
    3.6.3.代码实践
     
     
    3.6.7.存储映射IO
    存储映射IO使一个磁盘文件(物理地址)与存储空间(内存地址)中的一个缓冲区相映射。于是当从缓冲区取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应字节就自动地写入文件。这样就可以在不使用read和write的情况下执行IO。为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中,这是由mmap函数实现的。
    3.6.7.1、mmap函数:把一个磁盘文件和一个内存映射起来    内存映射函数
    3.6.7.2、LCD显示和IPC之共享内存
    3.6.7.3、存储映射IO的特点
    (1)共享而不是复制,减少内存操作
    (2)处理大文件时效率高,小文件不划算(视频用到的也比较多)
     
     
    函数原型:
    void *mmap(void *addr, size_t length, int prot, int flags,
                      int fd, off_t offset);
    addr参数用于指定映射存储区的起始地址,通常将其设置为0,这表示由系统选择该映射区的起始地址,此函数的返回地址是该映射区的起始地址。
    fd指定要被映射文件的描述符,在映射该文件到一个地址空间之前,先要打开该文件。
    length是映射的字节数。
    offset是要映射字节在文件中的起始偏移量。
    prot参数说明对映射存储区的保护要求,但不能超过文件open模式访问权限,prot可选值如下:
    PROT_EXEC Pages may be executed.
    PROT_READ Pages may be read.
    PROT_WRITE Pages may be written.
    PROT_NONE Pages may not be accessed.
    flags参数影响映射存储区的多种属性,其中MAP_SHARED和MAP_PRIVATE两者必须选择其一,还有许多其它的MAP_XXX是可选的。
     
    【还有一种就是多进程下,父进程fork创建一个子进程来处理不同的事情】。
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
  • 相关阅读:
    List
    集合
    layui树状组件tree回显如果子节点选中,当前主节点下所有元素都会选中问题修复
    layui富文本编辑器提交时无法获取到值
    thinkphp6+layui富文本编辑器页面回显显示HTML标签
    layui获取树形菜单所有选中的值
    php7将二维数组转为树状数组
    jq处理img标签找不到图片,显示指定图片
    thinkphp6根据访问设备不同访问不同模块
    layui怎么进入页面监听select的值然后重新渲染页面
  • 原文地址:https://www.cnblogs.com/wycBlog/p/7610921.html
Copyright © 2020-2023  润新知