NIO三大核心理念
首先介绍NIO中三个核心组件
- Buffer缓冲区
- Channel通道
- Selector选择器
一、Buffer缓冲区
1.Buffer 三个重要属性:
- capacity容量:作为一个内存块,Buffer具有一定的固定大小,也成为“容量”
- position位置:写入模式时代表写数据的位置。读取模式时,代表读取数据的位置
-
limit限制:写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量
//注意区分不同模式下的含义。
2. ByteBuffer内存类型
-
直接内存(direct堆外内存):在JVM之外向物理内存直接申请内存
进行IO或文件IO时比heapBuffer少一次拷贝
ByteBuffrt directByteBuffer = ByteBuffer.allocateDirect(noBNytes);
优点:
1、进行IO或文件IO时比heapBuffer少一次拷贝。(file/socket -> OS memory -> jvm heap)
2、在GC范围之外,降低GC压力,但实现了自动管理。DirextByteBuffer类提供Cleaner对象(PhantomReference),Cleaner被GC前会执行clean方法,触发DirectByteBuffer中定义的Deallocator。
- 非直接内存(heap堆内存):在JVM的申请内存
建议:性能确实可观的时候才去使用;分配给大型、长寿命;(网络传输、文件读写场景)
通过虚拟机参数MaxDirectMemorySize限制大小,防止耗尽整个机器内存
二、Channel: 通道
和标准的IO Stream区别:在一个通道内进行读取和写入时stream通常是单向的(input和output),可以非阻塞读取和写入通道,通道始终读取或写入缓冲区,channel的API涵盖了UDP/TCP网络和文件IO。(FileCjannel,DatagramCjannel,SocketChannel,ServerSocketChannel)
SocketChannel:用于创建TCP连接,类似java.net.Socket.有两种创建的方式:
1、客户端主动发起和服务的连接。
2、服务端获取新的连接
- SockerChannel:客户端
//客户端主动发起连接的方式
SocketChannel socketChannel = SocketChannel.open();
//设置为非阻塞模式
socketChannel.configureBlocking(false);
//创建一个端口和ip
socketChannel.connect(new InetSocketAddress(“htttp:163.com”,80));
//发送请求数据 - 向通道写入数据
socketChannel.write(byteBuffer);
//读取服务端返回 - 读取缓冲区的数据
int butesRead = socketChannel.read(byteBuffer);
//关闭连接
socketChannel.close();
write写:非阻塞,write()在尚未写入任何内容可能就返回了。需要在循环中调用
read读:非阻塞,read()方法可能直接返回而根本不读取任何数据,根据返回的int值判断读取了多少字节
- ServerSocketChannel:服务端
//创建网络服务端
ServerSocketChannel serverSockerChannel = ServerSocketChannel.open();
//设为非阻塞模式
serverSocletChannel.configureBlocking(false);
//绑定端口
serverSocletChannel.socket().bind(new InetSocketAddress(8080));
while(true){
SocketChannel soccketChannel = serverSocletChannel.accept();
if(socketChannel != null){
//TCP请求 读取/响应
}
}
注:serverSocletChannel.accept()如果该通道处于非阻塞,如果没有挂起的连接则该方法立即返回null。
所以必须检查返回的对象是否为空
三、Selector:选择器
- Selector是一个javaNIO 组件,可以检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。实现单个线程可以管理多个通道,从而管理多个网络连接。核心概念是事件驱动机制。
-
一个线程使用Selector监听多个channel的不同事件:
四种事件分别对应SelectionKey四个常量:
Connect:连接(Selector.OP_CONNECT) 客户端与服务端建立连接,客户端触发事件 Accept: 准备就绪(OP_ACCEPT) 服务端检测到客户端连接请求,服务端触发事件 Read: 读取(OP_READ) 客户端与服务端建立连接之后读取操作,双端触发事件 Write: 写入(OP_WRITE) 客户端与服务端建立连接之后写入操作,双端触发事件
NIO对比BIO
四、NIO与多线程结合的改进方案
- mainReactor接收->分发给subReactor读写->具体业务逻辑分发给单独的线程池处理
总结
NIO应用于网络应用开发还是比较繁琐,要想将性能提升,还需要和多线程技术结合起来。
因为网络编程本身的复杂性,以及JDK API开发的使用难度较高, 所以在开源社区中,涌出
来很多对JDK NIO进行封装、增强后的网络编程框架,例如:Netty、Mina等。