目录

Linux网络高级IO一五种IO模型同步通信异步通信非阻塞IO详细讲解

[Linux][网络][高级IO][一][五种IO模型][同步通信][异步通信][非阻塞IO]详细讲解

目录


0.预备知识 && 思考问题

  • 网络通信本质:IO
  • IO效率问题:效率一定是非常低下的
  • IO为什么低效?
    • 在read/recv时,如果底层缓冲区没有数据,recd/recv会怎么办?
      • 阻塞 –> 等
    • 在read/recv时,如果底层缓冲区有数据,read/recv会怎么办?
      • 拷贝数据
    • 综上:IO = 等 + 数据拷贝
  • 何为低效的IO?
    • 单位时间内,大部分时间IO类接口都处于等的状态
  • 如何提高IO效率,使之高效?
    • 单位时间内,让等的比重变得越低,IO的效率就越高

1.五种IO模型

0.形象理解五种模型

  • 将五种模型抽象为钓鱼中的五种模式
    • **阻塞IO:**一直盯着鱼竿,直到鱼上钩
    • **非阻塞IO:**没事看两眼,看到有鱼就拉杆,其余时间做自己的事情
    • **信号驱动IO:**鱼竿上放个铃铛,若有鱼上钩,则铃铛会响,听到铃铛响则拉杆,其余时间做自己的事情
    • **多路转接:**一次性布置100个钓竿,同时吊,哪里上钩则到哪里拉杆
    • **异步IO:**让别人帮自己钓鱼,自己直接拿别人的成果
  • 其中 效率最高 的是 多路转接
    • 单位时间内,等的比重是非常低的
  • **同步IO和异步IO区别:**有没有参与IO细节 –> [参与等/参与拷贝/同时都参与]
    • 除了异步IO,其余的都是同步IO

1.阻塞IO

  • 在内核将数据准备好之前,系统调用会一直等待,所有的套接字,默认都是阻塞方式
  • 阻塞IO是最常见的IO模型

https://i-blog.csdnimg.cn/blog_migrate/d030fe77a6219cca1c1eee7977387da3.png

2.非阻塞IO

  • 如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码
  • 非阻塞IO往往需要程序员以循环的方式反复尝试读写文件描述符,这个过程称为 轮询
    • 这对CPU来说是较大的浪费,一般只有特定场景下才使用

      https://i-blog.csdnimg.cn/blog_migrate/018420cb1227e9176e55a52305a10228.png

3.信号驱动IO

  • 内核将数据准备好的时候,使用 SIGIO信号 通知应用程序进行IO操作

    https://i-blog.csdnimg.cn/blog_migrate/32932b714ed2b12a9096d3c3ad7f91e4.png

4.多路转接/多路复用

  • 虽然从流程图上看起来和阻塞IO类似, 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态

    https://i-blog.csdnimg.cn/blog_migrate/f820dd97825b29b254a8f63754c4fb4e.png

5.异步IO

  • 由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)

    https://i-blog.csdnimg.cn/blog_migrate/c2ffdf3e19f7ed52d71a016ae7d2a1e8.png


2.高级IO重要概念

1.同步通信 vs 异步通信

  • 同步和异步 关注的是消息通信机制
    • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回,但是一旦调用返回,就得到返回值了
      • 换句话说,就是由调用者主动等待这个调用的结果
    • 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果
      • 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果
      • 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用
  • 另外,在讲多进程多线程的时候,也提到同步和互斥,这里的同步通信和进程之间的同步是完全不相干的概念
    • 进程/线程同步也是进程/线程之间直接的制约关系
    • 是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、 传递信息所产生的制约关系,尤其是在访问临界资源的时候

2.阻塞 vs 非阻塞

  • 阻塞和非阻塞 关注的是程序在等待调用结果(消息,返回值)时的状态
    • 阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回
    • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

3.非阻塞IO

1.fcntl()

  • **功能:**控制一个文件描述符,默认都是阻塞IO
  • 原型:int fcntl(int fd, int cmd, … / arg / );
  • 参数:
    • **fd:**要设置的文件描述符
    • **cmd:**要做什么
    • **arg:**要设置什么状态
  • 返回值:
    • 具体返回值取决于命令操作
    • 失败返回-1,同时errno被设置
  • fcntl()有5种功能
复制一个现有的描述符cmd=F_DUPFD
获得/设置文件描述符标记cmd=F_GETFD或F_SETFD
获得/设置文件状态标记cmd=F_GETFL或F_SETFL
获得/设置异步I/O所有权cmd=F_GETOWN或F_SETOWN
获得/设置记录锁cmd=F_GETLK,F_SETLK或F_SETLKW
  • 此处只使用第三种功能,获取/设置文件状态标记,就可以 将一个文件描述符设置为非阻塞
    • 以后对所有的fd,都可以统一用这个接口来更改阻塞/非阻塞,更为统一且方便

2.实现SetNonBlock

  • 基于fcntl,实现SetNonBlock(),将文件描述符设置为非阻塞
    • 使用 F_GETFL 将当前的文件描述符的属性取出来(这是一个位图)
    • 然后再使用 F_SETFL 将文件描述符设置回去,设置回去的同时,加上一个 O_NONBLOCK 参数
bool SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL); // 在底层获取当前fd对应的属性
    if(fl < 0)
    {
        return false;
    }

    // 设置非阻塞
    fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 在老的标志位的前提下新增内容
    return true;
}