****文章内容知识来自“慕课网讲师 张小喜”——《解锁网络编程之NIO的前世今生》****
****本人仅做学习笔记使用****
NIO网络编程初学
一、NIO网络编程模型
编程模型——对编程共性的抽象——把动物装冰箱
例:🐘把大象装冰箱:打开冰箱——放入大象——关上冰箱
🐅把老虎装冰箱:打开冰箱——放入老虎——关上冰箱
1、BIO
编程模型:
由阻塞式I/O引起的3个缺点:
-
阻塞式I/O模型——体现在服务器端线程阻塞等待客户端发起请求
-
弹性伸缩能力差——体现在一个客户端对应一个服务器端线程
-
多线程耗资源——线程创建、销毁、CPU调度
2、NIO
编程模型:
相比于BIO模型改进的地方:
-
非阻塞式I/O模型——服务器端提供一个单线程的Selector统一管理客户端的连接
-
弹性伸缩能力强——服务器端采用单线程处理所有客户端请求
-
单线程节省资源——不需要频繁创建、销毁线程,没有线程上下文切换
二、NIO网络编程详解
核心API:通道(Channel)、缓冲区(Buffer)、选择器或多路复用器(Selector)、选择键(SelectionKey)
1、通道(Channel)
特性🤖:
-
双向性——支持双向传输,即可读又可写
-
非阻塞性——可工作在非阻塞模式下(此特性构成NIO网络编程的基础)
-
操作唯一性——只能通过操作Buffer来对Channel进行读写操作
实现👻:
-
文件类——FileChannel------------用于对文件进行读写操作📄
-
UDP类——DatagramChannel----------用于对UDP数据的读写操作📃
-
TCP类——ServerSocketChannel/ SocketChannel🧾
TCP类Channel使用(部分代码片段):
/**
*代码片段1. 服务器端 通过ServerSocketChannel创建channel通道
*/
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
/**
* 代码片段2. 服务器端 为channel通道绑定监听端口
*/
serverSocketChannel.bind(new InetSocketAddress(8000));
/**
* 代码片段3.服务器端 设置channel为非阻塞模式
*/
serverSocketChannel.configureBlocking(false);
/**
* 代码片段4.服务器端 监听客户端连接,建立socketChannel连接
*/
SocketChannel socketChannel = serverSocketChannel.accept();
/**
* 代码片段5. 客户端 建立与服务器端的连接
*/
SocketChannel socketChannel = SocketChannel.open
(new InetSocketAddress("127.0.0.1", 8000));
2、缓冲区(Buffer)
特性🎮:
-
作用:读写Channel中数据————只能通过Buffer对Channel进行操作
-
本质:一块内存区域————被NIO包装成NIOBuffer对象,并提供一系列方法便于操作此内存
属性👑:
(以byteBuffer举例,相当于一个byte类型的数组)
-
Capacity(容量):标明数组最大可容纳多少字节
-
Position(位置):
写模式下: 表示当前位置,数据写入后,会向后移动到下一个可插入数据的buffer单元;初始为0,最大值为容量-1
读模式下: 切换到读模式时,会被重置为0,读取数据后,将向后移动到下一个可读数据位置
-
Limit(上限):
写模式下: 表示最多可向Buffer中写入多少数据,此模式下等于Capacity
读模式下: 切换到读模式时,表示最多能从Buffer中读取多少数据,此时会被设置成写模式下的Position值
-
Mark(标记):存储一个特定的Position位置,之后可通过调用Buffer的reset()方法来恢复到这个Position位置,从该位置开始处理数据
用法🎨:
/**
* 初始化长度为10的byte类型的buffer
*/
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
/**
* 向byteBuffer中写入三个字节
*/
byteBuffer.put("abc".getBytes(Charset.forName("UTF-8")));
/**
* 将byteBuffer从写模式切换为读模式
*/
byteBuffer.flip();
/**
* 从byteBuffer中读取一个字节
*/
byteBuffer.get();
/**
* 调用mark()方法记录当前position位置
*/
byteBuffer.mark();
/**
* 先调用get方法读取到下一个字节
* 再调用reset方法将position重置到mark位置
*/
byteBuffer.get();
byteBuffer.reset();
/**
* 调用clear方法重置所有属性
*/
byteBuffer.clear();
3、多路复用器(Selector)
-
作用🎯 :I/O就绪选择--------能够检测一到多个NIO通道,并知晓通道是否做好读写准备;通过Selector,一个单线程就可以管理多个Channel,从而管理多个网络连接
-
地位🔫 :NIO网络编程的基础---------NIO网络编程是基于Selector和非阻塞式I/O之上的
Selector使用(部分代码片段):
/**
*代码片段1. 创建Selector
*/
Selector selector = Selector.open();
/**
*代码片段2. 将channel注册到selector上,监听可读就绪事件
*/
SelectionKey selectionKey = channel.register(selector,SelectionKey.OP_READ);
/**
*代码片段3. 阻塞等待channel是否有就绪事件发生,返回就绪channel数量
*/
int selectNum = selector.select();
/**
*代码片段4. 获取发生就绪事件的channel集合
*/
Set<SelectionKey> selectionKeys = selector.selectedKeys();
4、选择键(SelectionKey)
-
四种就绪状态常量:
-
OP_CONNECT(连接就绪)
-
OP_ACCEPT(接收就绪)
-
OP_READ(读就绪)
-
OP_WRITE(写就绪)
-
-
有价值的属性:
调用selector对象的selectedKeys()方法时,会返回一个SelectionKey类型的集合
/** * 通过selectionKeys集合可获取到可用channel集合 */ Set<SelectionKey> selectionKeys = selector.selectedKeys();
NIO编程实现步骤
-
第一步:创建Selector
-
第二步:创建ServerSocketChannel,并绑定监听端口
-
第三步:将Channel设置为非阻塞模式
-
第四步:将Channel注册到Selector上,监听连接事件
-
第五步:循环调用Selector的select()方法,检测就绪情况
-
第六步:调用selectedKeys()方法获取就绪channel集合
-
第七步:判断就绪事件种类,根据状态调用对应业务处理方法
-
第八步:根据业务需要决定是否再次注册监听事件,重复执行第三步操作