NIO(同步非阻塞IO)
同步&异步
同步
同一时间,一个服务,只能被一个线程使用
异步
同一时间,一个服务,可以被多个线程使用
阻塞&非阻塞
阻塞
进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作
非阻塞
进程给CPU传达任务后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。
BIO
BlockingIO
同步阻塞式IO
举例
File、UDP、TCP
BIO缺点
一对一连接
大量的客户端,需要产生大量的线程,会导致服务器卡顿,甚至崩溃
客户端连接后,如果不发生任何操作依然会占用服务器端的线程
NIO
NewIO
/NonBlockingIO
同步式非阻塞IO
重要位置
capacity
容量位
用于标记当前缓冲区的容量/大小
limit
限制位
用于限定position所能达到的最大下标。默认和容量位是重合的
position
操作位
类似于数组中的下标,用于指向要操作的位置。默认是第0位
mark
标记位
用于进行标记,通常是用于避免数据大批量产生错误。注意,标记位默认是不启用的
NIO的三大组件
Buffer、Channel、Selector
Buffer - 缓冲区
用于进行数据的存储
底层是基于数组进行存储的,只能存储基本数据类型
针对八种基本类型提供了7个子类,其中没有针对boolean类型的子类
主要掌握ByteBuffer
ByteBufferDemo.java
ByteBuffer buffer = ByteBuffer.allocate(10);
创建大小为10的缓冲区
buffer.put("abc".getBytes());
添加数据
buffer.flip();
翻转缓冲区
需要先将limit挪到position上
然后将position归零
等价于
buffer.limit(buffer.position()); buffer.position(0);
while (buffer.hasRemaining()) { System.out.println(buffer.get()); }
遍历缓冲区的数据
Channel - 通道
在NIO中,用于进行数据的传输
Channel可以实现双向传输
Channel默认是阻塞的,可以手动设置非阻塞
针对不同的场景提供了不同的子类:
File:FileChannel
UDP:DatagramChannel
TCP:SocketChannel、ServerSocketChannel
Client.java
package com.wiscom.channel; public class Client { public static void main(String[] args) throws IOException { // 开启一个客户端通道 SocketChannel sc = SocketChannel.open(); // 可以将这个通道手动设置为非阻塞 sc.configureBlocking(false); // 发起连接 sc.connect(new InetSocketAddress("localhost", 8090)); // 因为设置为了非阻塞,所以需要保证连接建立之后才能写出数据 // 先去判断连接是否建立 while (!sc.isConnected()) // 在这个方法中自动进行计数,如果连接多次未连接上 // 那么认为这个连接无法建立 // 此时会抛出异常 sc.finishConnect(); // 写出数据 sc.write(ByteBuffer.wrap("hello server".getBytes())); // 关流 sc.close(); } }
Server.java
package com.wiscom.channel; public class Server { public static void main(String[] args) throws IOException { // 开启服务器端的通道 ServerSocketChannel ssc = ServerSocketChannel.open(); // 绑定要监听端口 ssc.bind(new InetSocketAddress(8090)); // 设置为非阻塞 ssc.configureBlocking(false); // 接收连接 SocketChannel sc = ssc.accept(); // 判断是否接收到了连接 while (sc == null) sc = ssc.accept(); // 读取数据 ByteBuffer buffer = ByteBuffer.allocate(1024); sc.read(buffer); buffer.flip(); // 获取字节数组 byte[] data = buffer.array(); System.out.println(new String(data, 0, buffer.limit())); // while(buffer.hasRemaining()) // System.out.println(buffer.get()); // 关流 ssc.close(); } }
Selector - 多路复用选择器
Selector针对事件来进行选择的
利用Selector可以实现一对多的连接效果
Selector是面向通道进行选择,并且要求被选择的通道必须是非阻塞的
选择器可以分为三类
可接受
可写
可读
Client.java
package com.wiscom.selector; public class Client { public static void main(String[] args) throws IOException { SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("localhost", 8090)); sc.write(ByteBuffer.wrap("hello server".getBytes())); ByteBuffer buffer = ByteBuffer.allocate(1024); sc.read(buffer); System.out.println(new String(buffer.array(), 0, buffer.position())); sc.close(); } }
Server.java
package com.wiscom.selector; public class Server { public static void main(String[] args) throws IOException { // 开启服务器端的通道 ServerSocketChannel ssc = ServerSocketChannel.open(); // 绑定要监听的端口 ssc.bind(new InetSocketAddress(8090)); // 设置为非阻塞 ssc.configureBlocking(false); // 开启选择器 Selector selc = Selector.open(); // 将通道注册到选择器上 ssc.register(selc, SelectionKey.OP_ACCEPT); while (true) { // 进行选择 selc.select(); // 获取这次选择出来的事件类型 Set<SelectionKey> keys = selc.selectedKeys(); // 获取迭代器遍历 // 根据不同的事件类型来进行不同的处理 Iterator<SelectionKey> it = keys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); // 可接受 if (key.isAcceptable()) { // 从这个事件中将通道取出来 ServerSocketChannel sscx = (ServerSocketChannel) key.channel(); // 接受连接 SocketChannel sc = sscx.accept(); System.out.println("连接成功~~~"); // 注册可读以及可写事件 sc.configureBlocking(false); sc.register(selc, SelectionKey.OP_READ | SelectionKey.OP_WRITE); } // 可读 if (key.isReadable()) { // 从事件中获取到通道 SocketChannel sc = (SocketChannel) key.channel(); // 读取数据 ByteBuffer buffer = ByteBuffer.allocate(1024); sc.read(buffer); // 解析数据 byte[] data = buffer.array(); System.out.println(new String(data, 0, buffer.position())); // 注销掉read事件,目的是为了防止重复读取 // 获取这个通道上的所有的事件 // sc.register(selc, key.interestOps() - // SelectionKey.OP_READ); sc.register(selc, key.interestOps() ^ SelectionKey.OP_READ); } // 可写 if (key.isWritable()) { // 从事件中获取通道 SocketChannel sc = (SocketChannel) key.channel(); // 写出数据 sc.write(ByteBuffer.wrap("hello client".getBytes())); // 注销掉可写事件 sc.register(selc, key.interestOps() - SelectionKey.OP_WRITE); } it.remove(); } } } }
拓展
NIO框架
Netty / Mina
做通讯业务时使用,否则一般用不到