【Netty】NIO详解:NIO三大组件概述

NIO详解:NIO三大组件概述

NIO(non-blocking io)是一种同步非阻塞IO,在Java中,实现NIO依赖于Channel、Buffer、Selector三大组件,在本篇文章中,我们先对这三大组件大致了解一下,在后续文章中还会对这三大组件的具体作用做出详细解释

1 Channel&Buffer

在Java中,使用NIO传输数据和存储数据依赖于Channel(通道)与Buffer(缓冲区)两个组件。若过需要在Java中使用 NIO ,就需要先获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区,然后操作缓冲区,对数据进行处理。

简而言之,Channel负责数据的传输,Buffer负责数据的存储

其中的Channel 有一点类似于 Stream,它是读写数据的双向通道,可以从 Channel 将数据读入 Buffer,也可以将 Buffer 的数据写入 Channel,而之前的 Stream 要么是输入,要么是输出,Channel 比 Stream 更为底层

channel
buffer

常见的Channel接口有:FileChannel(适用于文件传输)、DatagramChannel(适用于UDP通信)、SocketChannel(适用于TCP通信服务端/客户端)、ServerSocketChannel(适用于TCP通信服务端)。

常见的Buffer接口有:ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer,这些Buffer接口的区别在于其存储数据类型的不同,其中使用较多的是ByteBuffer,它有三个常用的实现类:MappedByteBuffer、DirectByteBuffer、HeapByteBuffer。ByteBuffer的继承关系如下:

在这里插入图片描述


2 Selector

Selector是用来处理socket连接的一种技术,在使用Selector之前,处理socket连接还有以下两种方法:

2.1 多线程设计

为个连接分别开辟一个线程,分别去处理对应的socket连接

多线程版
socket1
thread
socket2
thread
socket3
thread

这种方法存在以下几个问题

  • 内存占用高:每个线程都需要占用一定的内存,当连接较多时,会开辟大量线程,导致占用大量内存
  • 线程上下文切换成本高:线程数量再多,cpu在一段时间内能同时执行的线程数量也是有限的,带来的问题就是线程数量越多,线程上下文切换越频繁,服务器执行效率越低。

用大家常说的开餐厅的比方来解释:这种方式就好比,一个人开了一家餐厅,但是却不招募服务员,直到来客人了,老板再去招聘一个服务员来为这个客人服务,当客人点完菜之后,老板就会把为这个客人服务的服务员解雇,等下一个客人来了,老板再去招募一个新的服务员。听起来很荒唐,这其实也就是这种设计的不合理之处

因此多线程设计的方式只适合在连接数少的场景使用

2.2 线程池设计

使用线程池,让线程池中的线程去处理socket连接,当一个线程处理完socket连接后,这个线程会被收回到线程池中而不是直接销毁

线程池版
socket1
thread
socket2
thread
socket3
socket4

这种方法存在以下几个问题

  • 阻塞模式下,一个线程在一段时间内仅能处理一个连接
    • 线程池中的线程获取任务(task)后,只有当其执行完任务之后(断开连接后),才会去获取并执行下一个任务
    • 若socke连接一直未断开,则其对应的线程无法处理其他socke连接
  • 仅适合短连接场景
    • 短连接即建立连接发送请求并响应后就立即断开,使得线程池中的线程可以快速处理其他连接

依然用上面开餐厅的比方来解释:这种设计就好比一个人开了一座餐厅,并招募了一定数量的服务员,每来一个客人,老板就会派一个服务员去招待这个客人,当客人点完菜之后,服务员并不会被解雇,而是去招待下一个准备点菜的客人。这种设计比上一种设计要好很多,但是依然有问题:客人一进门,老板就会派一个服务员去处理这个客人的点菜需求,但是这个时候这个客人可能并没有想好要吃什么,服务员就已经在他旁边等着了,如果这个客人一直不确定点菜需求,那么服务员也必须一直等着,哪怕旁边已经有了一个想好要吃什么了的客人,服务员也不会去处理。这就是阻塞模式的弊端,一个任务没有执行完,线程就不会去执行下一个任务

2.3 selector设计

讲完了上面两种处理socket连接的方式,我们再来看看Selector是怎样处理socket连接的

selector 的作用是配合一个线程来管理多个 channel,获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,当一个channel中没有执行任务时,可以去执行其他channel中的任务。适合连接数多,但流量较少的场景

selector 版
selector
thread
channel
channel
channel

若事件未就绪,调用 selector 的 select() 方法会阻塞线程,直到 channel 发生了就绪事件。这些事件就绪后,select 方法就会返回这些事件交给 thread 来处理。

仍然用上面开餐厅的比方来解释,这种设计就好比一个人开了一座餐厅并招募了一个服务员,当有一个客人进入餐厅之后,服务员并不会直接去招待这位客人,而是由客人先想好吃什么,再叫服务员来处理,这种情况下就能保证服务员不会在一个客人身上浪费过多无用的时间而导致其他客人的点餐需求也无法被处理。效率上远优于以上两种设计。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值