跳到内容
Caiden's Blog
返回

NIO介绍及API使用

主要是对NIO各个组成部分进行介绍和简单使用

概述

Java NIO(New IO 或 Non Blocking IO) 是从Java1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更高效的方式进行文件的读写操作。

阻塞IO(BIO)

阻塞IO也就是同步IO,如果进行读写操作,代码会一直阻塞,直到读取完成或写入完成,传统的解决方式是使用多线程来处理,但是及其消耗服务器资源,而且线程池线程数也是有限的,如果只能开100个线程,试想一种场景,100个客户端都在下载一个大文件,然而第101个请求来了,只请求一个几十kb的网页,但是也得等有空闲线程才可以,所以也是无法处理这个请求的

使用BIO实现简单服务器
使用多个telnet 127.0.0.1 8888
ctrl + ] > send message进行发送消息
单线程测试:一次只能处理一个请求,只有那个连接关闭,才可以处理下一个连接请求
多线程测试:可以处理多个,但是线程资源有限,处理请求数依然不乐观

非阻塞IO(NIO)TODO

可以用一个线程,处理多个客户端的连接

NIO组成部分

由以下几个核心部分组成

这三个构成了核心API,还有其他组件如Pipe和FileLock,只不过是三个核心组件共同使用的工具类

Channel

可以翻译成通道,可以和IO中的Stream流对比着理解,传统IO中的流是单向的,但是Channel是双向的,既可以读也可以写。

NIO中的Channel主要实现有FileChannel、DatagramChannel、SocketChannel和ServerSocketChannel,分别对应文件IO、UDP和TCP(client和server)IO

Buffer

NIO 中的关键 Buffer 实现有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应基本数据类型: byte, char, double, float, int, long, short。

Selector

Selector 运行单线程处理多个 Channel,如果你的应用打开了多个通道,但每个连接 的流量都很低,使用 Selector 就会很方便。例如在一个聊天服务器中。要使用 Selector, 得向 Selector 注册 Channel,然后调用它的 select()方法。这个方法会一直 阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件, 事件的例子有如新的连接进来、数据接收等。

三个组件之间的关系

先上图

image-20211101092708772

关系图说明:

  1. 每个Channel都会对应一个Buffer
  2. Selector对应一个线程,一个线程对应多个Channel(连接)
  3. 该图反应了有三个channel注册到该selector程序
  4. 程序切换到哪个channel是由事件决定的,Event就是一个重要概念,具体事件是啥意思?可以类比前端中的点击事件onClick,这里的事件可以是网络连接,数据读取
  5. selector会根据不同的事件,在各个通道上切换
  6. buffer就是一个内存块,底层就是一个数组
  7. 数据的读取和写入都是通过Buffer来的,这个和BIO中的不一样,BIO中要么是输入流,要么是输出流,不能双向,但是NIO的Buffer是可以读也可以写,但是需要flip方法切换模式
  8. channel是双向的,可以返回底层操作系统的情况,比如Linux,底层操作系统的通道就是双向的

Channel

channel是基于流实现的,比如说创建一个输出流,才能创建channel,到时候数据也都是在这个输出流的channel里面

通道和传统的IO流还是有区别的:

image-20211101092727138

重要的Channel实现

通道覆盖了文件IO和网络IO,牛比!

FileChannel介绍和示例

API概述:

image-20211101092743590

SocketChannel介绍和示例

共有三种SocketChannel

  1. ServerSocketChannel:注意这个是没有读写操作的,主要作用就是用于监听一个端口,来了连接了就创建一个SocketChannel对象去处理连接请求
  2. SocketChannel:基于TCP建立套接字连接
  3. DatagramChannel:基于UDP进行读写网络数据

ServerSocketChannel

下面是一段监听端口是否有连接的程序,有连接就打印远程连接的地址,无连接就打印null

注意非阻塞的使用

SocketChannel

下面是SocketChannel建立一段连接的程序

DatagramChannel

Buffer

buffer实际是啥

buffer底层就是维护着一个数组,如byteBuffer,就是维护一个byte[]

如:

final byte[] hb;

真正的数据其实就是存在这个数组里面了

buffer都有java基本类型的实现,想读啥样的数据,就选对应buffer即可

image-20211101092808090

其实ByteBuffer用的最多,因为在网络传输时,基本单位也都是用的字节

顶层抽象类Buffer类

定义了几个关键参数

// Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

position:实时记录指针当前位置

limit:当前数组的数据大小,比如数组大小是10,只存了5个数据,limit就是5

capacity:数组容量,就是数组的实际大小,一旦确定不能修改

mark:标记?还没发现有啥用呢

buffer常用API

image-20211101092826038

ByteBuffer常用API

image-20211101092840867

MappedByteBuffer

可以直接在内存中修改文件,没有尝试过,没见过,见过再说

分散和聚集(Scatter和Gather)

之前都是在一个Buffer中操作的,我们这里可以用多个buffer来操作

Selector

概述

Selector能够检测多个注册的通道是否有事件发生(多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每一个事件进行相应的处理,这样就可以用一个线程去管理多个连接了!

image-20211101092900425

特点说明

image-20211101092916863

运行过程

image-20211101092934037

具体使用

  1. 生成各种channel对象
  2. 然后使用channel.regiister(selector, op_accept),第二个参数是各种事件,注册到selector中
  3. 然后selector.select(long timeout),检查有没有事件发生,如果返回0,无事发生,其他就有事件了
  4. 然后获取selectionKeys,遍历这个集合,挨个查看每个key发生的是啥事件,是accept还是read还是啥的
  5. 然后根据key获取channel,也就是调用key.channel方法,然后根据实际情况进行强转为具体的channel,然后进行accept或者read操作或者其他操作

运行效果:

image-20211101093005234

SelectionKey相关API

image-20211101093018850

BUG

nio空轮询bug


分享到:

上一篇
Spring使用@Value值注入不成功问题
下一篇
适配器模式(Adapter)