网络通信I/O模型

  • 同步(synchronous)模型
    • BIO:阻塞IO
    • NIO:非阻塞IO
    • IO多路复用
  • 异步(asynchronous)模型
    • AIO:异步IO
  • I/O 多路复用
    • select / poll / epoll

1. 先验知识

  • Linux和Java
    • 本文讨论的是Linux环境下的IO模型。Java中的NIO与这里的NIO不同,Java中的NIO属于IO多路复用模型
  • IO的两个阶段
    • 用户进行IO的读写,基本上都会用到read&write两大系统调用。以read为例,会涉及到两个系统对象,经历两个阶段。
      • 1)等待数据准备,由操作系统内核kernal完成
      • 2)将数据从内核缓冲区拷贝到进程缓冲区
  • 缓冲区的作用
    • 缓冲区的目的,是为了减少频繁的系统IO调用。由于系统调用需要保存和恢复进程状态信息,为了减少这种损耗时间、也损耗性能的系统调用,于是出现了缓冲区
    • 在linux系统中,系统内核有个缓冲区叫做内核缓冲区
    • 每个进程有自己独立的缓冲区,叫做进程缓冲区

2. 四种模型

(1)同步阻塞IO(blocking IO)

  • 在linux中的Java进程中,默认情况下所有的socket都是blocking IO。实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的
  • blocking IO的特点是在IO两个阶段,用户线程都被阻塞了
    • 具体的说:从用户线程执行read()后,用户线程一直处于block状态,直到kernel返回结果后,用户线程才解除block的状态,重新运行起来
  • 例子,以read()为例
    • (IO一阶段)用户线程执行read()后,进入阻塞状态 ——> kernel开始准备数据 ——> kernel就要等待足够的数据到来(数据在一开始还没有到达,比如,还没有收到一个完整的Socket数据包)
    • (IO二阶段)kernel数据准备完成 ——> 将数据从kernel缓冲区复制到用户进程缓冲区(用户内存)——> kernel返回结果 ——> 用户线程得到结果,结束阻塞状态
  • 优缺点
    • 优点
      • 程序简单,用户线程挂起期间基本不占用CPU资源
    • 缺点
      • 一般情况下,改进后的BIO会用线程池给每个连接分配独立线程,根据并发量动态生成和销毁。但是当并发量大时,需要大量的线程来维护大量的网络连接,内存、线程切换开销巨大

(2)同步非阻塞NIO(non-blocking IO)

  • 在linux系统下,可以通过设置socket使其变为non-blocking
  • 阶段一不阻塞(轮询),阶段二用户线程阻塞
  • 例子,以read()为例
    • 用户线程调用read()
      • 如果kernal缓冲区的数据没准备好,则立即返回error,用户线程得到error后无需阻塞。
        • 用户线程可以过一段时间(或者立即)再执行read()。这个过程被称之为轮询
      • 如果kernal缓冲区的数据已经准备好,进入IO阶段二:缓冲区间复制
    • IO阶段二:内核缓冲区数据 ——> 用户缓冲区
      • 如果用户线程没收到error,kernal开始复制数据,此时用户线程处于block状态
      • kernal复制完成后,返回结果给用户线程,用户线程结束block状态
  • 优缺点
    • 优点
      • 每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好
    • 缺点
      • 如果不间断地调用read(),大幅度占用CPU资源
      • 如果每隔n秒调用read(),即每过一段时间才去轮询,任务完成的响应延迟增大了,数据吞吐量降低
  • 备注
    • Java NIO与这个没有关系,Java NIO属于IO多路复用模型。java的实际开发中,也不会涉及这种IO模型。

(3)IO多路复用(IO multiplexing)

  • 该模型也称 事件驱动IO(event driven IO)。目前支持IO多路复用的系统调用,有 select,epoll等等
    • select系统调用,是目前几乎在所有的操作系统上都有支持,具有良好跨平台特性
    • epoll是在linux 2.6内核中提出的,是select系统调用的linux增强版本
    • IO多路复用模型,就是通过一种新的系统调用,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核kernel能够通知程序进行相应的IO系统调用
  • 阶段一阻塞,阶段二也阻塞
  • 例子,以select()为例
    • 调用select()前需要将目标网络连接,提前注册到select/epoll的可查询socket列表中
    • 用户线程执行select()系统调用,查询可以读的连接,kernel会查询所有select的可查询socket列表
      • 当任何一个socket中的数据准备好了,select就会立即返回,IO阶段一结束,开始阶段二。
      • 如果没准备好,用户线程会一直阻塞。
    • IO阶段二开始,用户线程获得目标连接后,发起read()调用,用户线程阻塞
      • kernal复制完成后,返回结果,用户线程blcok结束
  • 补充
    • 整个流程需要两个系统调用: 一个select/epoll查询调用,一个是IO的read/write调用
    • 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,这样select/epoll查询调用会不断轮询
    • 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用(多线程 + BIO)的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
    • 虽然整个用户线程是一直被block的,但它是被select()给block,而不是被read()给block
  • 优缺点
    • 优点
      • 多路复用模型用单线程执行,不像(多线程+BIO),需要多个read()。占用资源少,不消耗太多 CPU。
    • 缺点
      • select()接口本身需要消耗大量时间去轮询所有句柄,因此linux提供了epoll作为增强,遗憾的是不同的操作系统特供的epoll接口有很大差异

(4)异步IO模型(asynchronous IO)

  • 该模型也称为信号驱动 IO
  • 用户线程两个阶段全程不阻塞
  • 例子
    • 用户线程执行前,一般需要注册回调函数
    • 用户线程调用read系统调用,立刻就可以开始去做其它的事,用户线程不阻塞
    • kernal将IO两个阶段(数据准备 + 数据复制)都执行好后,会返回给用户线程一个信号(signal),或者回调用户线程注册的回调接口
  • 优缺点
    • 优点
      • 全程不阻塞
        • Windows 系统下通过 IOCP 实现了真正的异步 I/O,但Windows很难作为高并发的服务器系统
    • 缺点
      • 需要完成事件的注册与传递,这里边需要底层操作系统提供大量的支持,去做大量的工作
      • 在 Linux 系统下,异步IO模型在2.6版本才引入,目前并不完善


3. I/O模型总结

(1)blocking和non-blocking的区别

  • 数据准备(IO阶段一)和数据复制(IO阶段二)两个阶段,只要有一个阶段被阻塞,都称为blocking。
  • 所以哪怕non-blocking通过轮询,让数据准备阶段不阻塞。在从kernal拷贝到进程缓冲区时,也会被阻塞。NIO指的是第一个阶段(数据准备)不阻塞
  • 除了AIO,剩下都会有block

(2)异步IO和同步IO

  • 除了AIO是异步IO,剩下的都是同步IO

4. I/O多路复用的函数

(1)select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • 调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符
  • 优点:良好跨平台支持:目前几乎在所有的平台上支持
  • 缺点:单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024

(2)poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};
  • 不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现
  • pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)
  • 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符
  • 缺点:
    • select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降

(3)epoll

  • epoll是在2.6内核中提出的,是之前的select和poll的增强版本
  • 相对于select和poll来说,epoll更加灵活,没有描述符限制
  • epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次
  • 与select/poll的区别:无需遍历文件描述符,通过Callback完成
    • 在 select/poll 中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描
    • epoll事先注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知
  • 优点:
    • 监视的描述符数量不受限制,远大于2048,在1GB内存的机器上大约是10万左右
    • 与select/poll不同,IO的效率不会随着监视fd的数量的增长而下降

Reference

强烈推荐:https://segmentfault.com/a/1190000003063859

https://www.cnblogs.com/cainingning/p/9556642.html

https://www.cnblogs.com/crazymakercircle/p/10225159.html

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值