• C语言-IO模型


    IO模型

    在UNIX/Linux下主要有4种I/O模型

    • 阻塞I/O(最常用)
    • 非阻塞I/O(可防止进程阻塞在I/O操作上,需要轮询)
    • I/O多路复用(允许同时对多个I/O进行控制)
    • 信号驱动I/O(一种异步通讯模型)

    阻塞I/O模式

    • 阻塞I/O模式是最普遍使用的I/O模式,大部分程序使用的都是阻塞模式的I/O
    • 缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式
    • 很多读写函数在调用过程中会发生阻塞
      • 读操作-read、recv、recvfrom
      • 写操作-write、send
      • 其他操作-accept、connect

    读阻塞-这里以read函数为例

    • 进程调用read函数从套接字上读取数据,当套接字的接受缓冲区中还没有数据可读,函数read将会发生阻塞
    • 它会一直阻塞下去,等待套接字的接受缓冲区中有数据可读
    • 经过一段时间后,缓冲区内接受到数据,于是内核便去唤醒该进程,通过read访问这些数据
    • 如果在进程阻塞的过程中,对方发生故障,那这个进程将永远阻塞下去

    写阻塞

    • 在写操作时发生阻塞的情况要比读操作少,主要发生要写入的缓冲区的大小小于要写入的数据量的情况下
    • 这时,写操作不进行任何拷贝工作,将发生阻塞
    • 一旦发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区
    • UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区慢的情况;也就是说UDP套接字上执行的写操作永远不会阻塞

    非阻塞I/O模型

    • 当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我
    • 当一个应用程序使用了非阻塞的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称作polling)
    • 应用程序不停的polling 内核来检查是否I/O操作已经就绪。这是一个极浪费CPU资源的操作
    • 这种模式使用中不普遍

    非阻塞模式的实现-fcntl() 函数

    • 当你一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞I/O模式
    • 可以使用fcntl()设置一个套接字的标志位O_NONBLOCK 来实现非阻塞
    • 代码实现
    #include <fcntl.h>
    
    int flag = 0;
    // 获取到当前的设置
    flag = fcntl(sockfd,F_SETFL,0);
    flag |=O_NONBLOCK;
    // 设置回去
    fcntl(sockfd,F_SETFL,flag);

    或者使用 ioctl()函数

    #include <sys/ioctl.h>
    
    int b_on = 1;
    ioctl(sockfd, FIONBIO, &b_on);

    多路复用

    • 应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的
    • 若采用非阻塞模式,对多个输入输出进行轮询,但又太浪费CPU时间
    • 若设置多进程,分别处理一条数据通路,将新产生进程的同步于通讯问题,使程序变得更加复杂
    • 比较好的方式是使用I/O多路复用,基本思想是
      • 先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个以及准备好进行I/O时函数才返回。
      • 函数返回时告诉进程那个描述符已就绪,可以进行I/O操作
    #include <sys/select.h>
    
    int fd_num = -1; // 一般使用一个计数器计算集合中的文件描述符数量
    
    void FD_ZERO(fd_set *fdset); // 将集合清零
    
    void FD_SET(int fd, fd_set *fdset); // 将关心的文件描述符加入到集合中
    
    void FD_CLR(int fd, fd_set *fdset); // 将某个文件描述符从集合中清除
    
    int FD_ISSET(int fd, fd_set *fdset); // 判断fd是否在set集合中
    
    int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
                   fd_set *restrict errorfds, struct timeval *restrict timeout);
    // nfds 传入 fd_num+1 表示最大集合数
    // 这三个集合如果需要传入 不需要则可以传NULL
    // readfds 读集合
    // writefds 写集合
    // errorfds 异常集合
    // timeout 超时等待
    // struct timeval 
    /*
    _STRUCT_TIMEVAL
    { 
        __darwin_time_t         tv_sec;         /* seconds */ 秒
        __darwin_suseconds_t    tv_usec;        /* and microseconds */ 毫秒
    };
    */

    Demo:多路复用模型

    #include <sys/select.h>
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        fd_set rset; // 创建读集合
        int max = -1;
        int fd = -1; // 句柄
        struct timeval timeout;
        // socket 连接省略...
        // bind 省略...
        // listen 省略...
    // 如果循环是要一直执行如下操作
        FD_ZERO(&rset); // 将集合清零
        FD_SET(fd, &rset); // 将创建好的fd加入到读集合中
        max++; // 计数器+1
        // 依次将其他连接的fd加入... 省略
        // 设置超时时间 为5s
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        // 使用select监控
        select(max + 1, &rset, NULL, NULL, &timeout);
        // 依次判断select监控后的rset
        // 例如:fd
        // 现在的rset集合中存放的是已经就绪的描述符 所以需要依次判断 手上的描述符是否存在于集合中
        if (FD_ISSET(fd, &rset)) {
            // 已经准备就绪 做一些该做的操作
        }
        // if (FD_ISSET(fd2,&rset)){
        //        // 已经准备就绪 做一些该做的操作
        //    }
        // ...
    }
    Songzhibin
  • 相关阅读:
    0180 定时器 之 setInterval() :开启定时器,京东倒计时案例,停止定时器,发送短信倒计时案例
    0179 定时器 之 setTimeout() :开启定时器,5秒后关闭广告案例,停止定时器
    json常用的注解
    Base64 编码与解码详解
    API 交互中怎么做好图片验证码?
    CSS中cursor 鼠标指针光标样式(形状)
    本地数据存储解决方案以及cookie的坑
    base64原理浅析
    Web前端十种常用的技术
    FreeMarker网页静态化
  • 原文地址:https://www.cnblogs.com/binHome/p/12864446.html
Copyright © 2020-2023  润新知