IO通信模型(三)多路复用IO

简介: 从非阻塞同步IO的介绍中可以发现,为每一个接入创建一个线程在请求很多的情况下不那么适用了,因为这会渐渐耗尽服务器的资源,人们也都意识到了这个 问题,因此终于有人发明了IO多路复用。最大的特点就是不需要开那么多的线程和进程。多路复用IO是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。

多路复用IO

非阻塞同步IO的介绍中可以发现,为每一个接入创建一个线程在请求很多的情况下不那么适用了,因为这会渐渐耗尽服务器的资源,人们也都意识到了这个 问题,因此终于有人发明了IO多路复用。最大的特点就是不需要开那么多的线程和进程

多路复用IO是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。

0.jpg

如图,这样在处理多个连接时,可以只需要一个线程监控就绪状态,对就绪的每个连接开一个线程处理就可以了,这样需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。

多路复用IO有几个比较重要的概念,下面一一讲解。

缓冲区Buffer

Buffer本质是可以写入可以读取的内存,这块内存被包装成了NIO的Buffer对象,然后为它提供一组用于访问的方法。Java则为java.nio.Buffer实现了基本数据类型的Buffer


27.png


所有的Buffer缓冲区都有4个属性,具体解释可以看表格。

属性 描述
Capacity 容量,可以容纳的最大数据量,不可变
Limit 上届,缓冲区当前数据量,Capacity=>Limit
Position 位置,下一个要被读取或者写入的元素的位置,Capacity>=Position
Mark 标记,调用mark()来设置mark=position,再调用reset()设置position=mark

这4个属性遵循大小关系: mark <= position <= limit <= capacity

Buffer的基本用法

使用Buffer读写数据一般遵循以下四个步骤:

写入数据到Buffer

调用flip()方法。

从Buffer中读取数据。

调用clear()方法或者compact()方法。

Buffer的测试代码

下面是对于Java中ByteBuffer的测试代码:

 
         

得到如下输出:

 
         

需要说明的是flip()方法将Buffer从写模式切换到读模式,clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。

Buffer的读写模式

注意读写模式切换时候几个标记位的变化。

28.png

通道Channel

通道Channel和流类似,不同的是通道的工作模式可以是全双工。也就是说既可以读取,也可以写入。同时也可以异步的进行读写。Channel连接着底层数据与缓冲区Buffer

同样的,Java中针对不同的情况实现了不同的Channel操作类。常用的有

  1. FileChannel 从文件中读写数据。
  2. DatagramChannel 能通过UDP读写网络中的数据。
  3. SocketChannel 能通过TCP读写网络中的数据。
  4. ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

下面是对于Java中Channel和Buffer的简单演示:

 
         

输出信息如下:

 
         

需要注意的是,在读取之前一定要调用flip()切换到读取模式。

选择器Selector

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。我们也可以称Selector为轮询代理器,事件订阅器或者channel容器管理器。

应用程序将向Selector对象注册需要它关注的Channel,以及具体的某一个Channel会对哪些IO事件感兴趣。Selector中也会维护一个“已经注册的Channel”的容器。

关于IO事件,我们可以在SelectionKey类中找到几个常用事件:

  1. OP_READ 可以读取
  2. OP_WRITE 可以写入
  3. OP_CONNECT 已经连接
  4. OP_ACCEPT 可以接受

值得注意的是,在程序中都是通过不断的轮训已经注册的Channel,根据检查注册时的感兴趣事件是否已经就绪来决定是否可以进行后续操作。同时Selector也有几个经常使用的方法。

  1. select() 阻塞到至少有一个通道在你注册的事件上就绪了。
  2. select(long timeout) 最长会阻塞timeout毫秒
  3. selectNow() 会阻塞,不管什么通道就绪都立刻返回
  4. selectedKeys() 返回就绪的通道

下面是一个对Java中Selector编写服务端的简单使用测试(客户端不在此编写了,如有需要,可以查看IO通信模型(一)同步阻塞模式BIO(Blocking IO)中的客户端代码):

 
         

Java NIO编程

到这里,已经对多路复用IO有了一个基本的认识了,可以结合上面的三个概念就行多路复用IO编程了,下面演示使用Java语言编写一个多路复用IO服务端。

NioSocketServer.java

 
         

文章代码已经上传GitHub:

https://github.com/niumoo/java-toolbox/

<完>

相关文章
|
24天前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
1月前
|
消息中间件 网络协议 Java
你不得不了解的网络IO模型知识
该文章主要讲述了网络I/O模型的相关知识,包括不同的I/O模型以及它们的特点和应用场景。
你不得不了解的网络IO模型知识
|
1月前
五种IO模型基本概念
正确选择与应用适合的I/O模型是提升程序性能,保证响应时间和处理能力的关键。选择时需要综合考虑程序的实际应用场景、性能要求以及开发和维护的复杂性。
27 1
|
2月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
119 1
|
2月前
|
存储 Java Unix
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。
|
2月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
128 1
|
3月前
|
Linux C++
c++高级篇(三) ——Linux下IO多路复用之poll模型
c++高级篇(三) ——Linux下IO多路复用之poll模型
|
2月前
stm32f407探索者开发板(十四)——IO引脚复用和映射
stm32f407探索者开发板(十四)——IO引脚复用和映射
|
1月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用