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/

<完>

相关文章
|
5月前
|
Java Linux API
IO模型
BIO、NIO、AIO是Java中处理网络I/O的三种模型。BIO为阻塞式,每个连接需单独线程,高并发下性能受限;NIO通过非阻塞与多路复用提升并发能力,少量线程可处理大量请求;AIO进一步实现异步非阻塞,数据复制时线程可释放,由回调机制处理后续操作。三者适用于不同场景,BIO易用但低效,NIO高效但复杂,AIO理论性能更优但目前在Linux上仍依赖多路复用实现。Java 21引入虚拟线程后,BIO也可兼具高性能与易编写特性。
185 2
|
7月前
|
监控 应用服务中间件 Linux
掌握并发模型:深度揭露网络IO复用并发模型的原理。
总结,网络 I/O 复用并发模型通过实现非阻塞 I/O、引入 I/O 复用技术如 select、poll 和 epoll,以及采用 Reactor 模式等技巧,为多任务并发提供了有效的解决方案。这样的模型有效提高了系统资源利用率,以及保证了并发任务的高效执行。在现实中,这种模型在许多网络应用程序和分布式系统中都取得了很好的应用成果。
239 35
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
663 1
Linux C/C++之IO多路复用(aio)
|
Ubuntu 网络协议 Unix
02理解网络IO:实现服务与客户端通信
网络IO指客户端与服务端通过网络进行数据收发的过程,常见于微信、QQ等应用。本文详解如何用C语言实现一个支持多客户端连接的TCP服务端,涉及socket编程、线程处理及通信流程,并分析“一消息一线程”模式的优缺点。
335 0
|
网络协议 前端开发 Java
网络协议与IO模型
网络协议与IO模型
473 4
网络协议与IO模型
|
开发者
什么是面向网络的IO模型?
【10月更文挑战第6天】什么是面向网络的IO模型?
122 3
|
数据挖掘 开发者
网络IO模型
【10月更文挑战第6天】网络IO模型
491 3
|
数据挖掘 开发者
网络IO模型如何选择?
网络IO模型如何选择?【10月更文挑战第5天】
167 2
|
数据库
同步IO模型是一种常见的编程模型
【10月更文挑战第5天】同步IO模型是一种常见的编程模型
157 2
|
缓存 Java Linux
硬核图解网络IO模型!
硬核图解网络IO模型!
209 1