NIO编程
NIO编程(同步非阻塞)
1、NIO介绍
NIO三大核心
-
缓冲区Buffer
- Buffer子类
-
Buffer常用API
缓冲区对象创建 static ByteBuffer allocate(长度) 创建byte类型的指定长度的缓冲区 static ByteBuffer wrap(byte[] array) 创建一个有内容的byte类型缓冲区 缓冲区对象添加数据 int position()/position(intnewPosition)获得当前要操作的索引/修改当前要操作的索引位置 int limit()/limit(int newLimit)最多能操作到哪个索引/修改最多能操作的索引位置 int capacity() 返回缓冲区的总长度 int remaining() 还有多少能操作索引个数 boolean hasRemaining() 是否还有能操作 put(byte b)/put(byte[] src) 添加一个字节/添加字节数组 缓冲区对象读取数据 flip() 写切换读模式 limit设置position位置, position设置0 get() 读一个字节 get(byte[] dst) 读多个字节 get(int index) 读指定索引的字节 rewind() 将position设置为0,可以重复读 clear() 切换写模式 position设置为0 , limit 设置为 capacity array() 将缓冲区转换成字节数组返回
-
通道Change
-
常 用 的Channel实现类类
FileChannel 用于文件的数据读写 DatagramChannel 用于 UDP 的数据读写 ServerSocketChannel/SocketChannel 用于 TCP 的数据读写
-
-
选择器Selector
-
Selector常用方法介绍
Selector.open() : //得到一个选择器对象 selector.select() : //阻塞 监控所有注册的通道,当有对应的事件操作时, 会将SelectionKey放入集合内部并返回事件数量 selector.select(1000): //阻塞 1000 毫秒,监控所有注册的通道,当有对应的事件操作时, 会将 SelectionKey放入集合内部并返回 selector.selectedKeys() : // 返回存有SelectionKey的集合
-
SelectionKey常用方法
electionKey.isAcceptable(): 是否是连接继续事件 SelectionKey.isConnectable(): 是否是连接就绪事件 SelectionKey.isReadable(): 是否是读就绪事件 SelectionKey.isWritable(): 是否是写就绪事件
-
SelectionKey中定义的4种事件
SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了 SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户端与服务器的连接已经建立成功 SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了) SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)
-
NIO是 面向缓冲区编程的。数据读取到一个缓冲区中,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
NIO 三大核心原理示意图
- 每个 channel 都会对应一个 Buffer
- Selector 对应一个线程, 一个线程对应多个 channel(连接)
- 每个 channel 都注册到 Selector选择器上
- Selector不断轮询查看Channel上的事件, 事件是通道Channel非常重要的概念
- Selector 会根据不同的事件,完成不同的处理操作
- Buffer 就是一个内存块 , 底层是有一个数组
- 数据的读取写入是通过 Buffer, 这个和 BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是
NIO 的 Buffer 是可以读也可以写 , channel 是双向的.
2、NIO编写demo
Socket 客户端
public static void main(String[] args) throws IOException {
// 1、创建客户端链接
SocketChannel channel = SocketChannel.open();
//2、绑定ip 端口号
channel.connect(new InetSocketAddress("127.0.0.1", 1900));
//3、想服务端写数据
channel.write(ByteBuffer.wrap("惨死的呢".getBytes(StandardCharsets.UTF_8)));
//获取服务端响应
ByteBuffer allocate = ByteBuffer.allocate(1024);
System.out.println("获取服务端响应:" + new String(allocate.array(), 0, channel.read(allocate), StandardCharsets.UTF_8));
channel.close();
}
Socket 服务端
public static void main(String[] args) throws IOException {
//1. 打开一个服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2. 绑定对应的端口号
serverSocketChannel.bind(new InetSocketAddress(1900));
//3. 通道默认是阻塞的,需要设置为非阻塞
serverSocketChannel.configureBlocking(false);
//4. 创建选择器
Selector selector = Selector.open();
//5. 将服务端通道注册到选择器上,并指定注册监听的事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务端启动>>>>");
while (true) {
//6. 检查选择器是否有事件
int select = selector.select(2000);
if (select <= 0) {
System.out.println("无事件,跳过>>>");
continue;
}
//7. 获取事件集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//8. 判断事件是否是客户端连接事件SelectionKey.isAcceptable()
SelectionKey selectionKey = iterator.next();
//9. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
if (selectionKey.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客户端已连接>>>");
//必须设置通道为非阻塞, 因为selector需要轮询监听每个通道的事件
socketChannel.configureBlocking(false);
//指定监听事件为OP_READ
socketChannel.register(selector, SelectionKey.OP_READ);
}
//10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
if (selectionKey.isReadable()) {
//11. 得到客户端通道,读取数据到缓冲区
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer allocate = ByteBuffer.allocate(1024);
int read = socketChannel.read(allocate);
if (read > 0) {
System.out.println("从客户端读取数据:" + new String(allocate.array(), 0, read, StandardCharsets.UTF_8));
}
//12. 给客户端回写数据
socketChannel.write(ByteBuffer.wrap("把你的心不能喝".getBytes(StandardCharsets.UTF_8)));
socketChannel.close();
}
//13. 从集合中删除对应的事件, 因为防止二次处理.
iterator.remove();
}
}
}