Java NIO
全称 Java non-blocking IO
,是指 JDK
提供的新 API
。从 JDK1.4
开始,Java
提供了一系列改进的输入/输出的新特性,被统称为 NIO
(即 NewIO
),是同步非阻塞的。NIO
相关类都被放在 java.nio
包及子包下,并且对原 java.io
包中的很多类进行改写。NIO
有三大核心部分:Channel
(通道)、Buffer
(缓冲区)、Selector
(选择器) 。NIO
是面向缓冲区,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。NIO
是可以做到用一个线程来处理多个操作的。假设有 10000
个请求过来,根据实际情况,可以分配 50
或者 100
个线程来处理。不像之前的阻塞 IO
那样,非得分配 10000
个。HTTP 2.0
使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP 1.1
大了好几个数量级。一张图描述 NIO
的 Selector
、Channel
和 Buffer
的关系。
Channel
都会对应一个 Buffer
。Selector
对应一个线程,一个线程对应多个 Channel
(连接)。Channel
注册到该 Selector
//程序Channel
是由事件决定的,Event
就是一个重要的概念。Selector
会根据不同的事件,在各个通道上切换。Buffer
就是一个内存块,底层是有一个数组。BIO
以流的方式处理数据,而 NIO
以块的方式处理数据,块 I/O
的效率比流 I/O
高很多。
BIO
是阻塞的,NIO
则是非阻塞的。
BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
Buffer和Channel之间的数据流向是双向的
缓冲区(Buffer
):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个**容器对象(含数组)**该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel
提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer
,
NIO
的通道类似于流,但有些区别如下:
BIO
中的 Stream
是单向的,例如 FileInputStream
对象只能进行读取数据的操作,而 NIO
中的通道(Channel
)是双向的,可以读操作,也可以写操作。Channel
在 NIO
中是一个接口 public interface Channel extends Closeable{}
Channel
类有:FileChannel
、DatagramChannel
、ServerSocketChannel
和 SocketChannel
。【ServerSocketChanne
类似 ServerSocket
、SocketChannel
类似 Socket
】FileChannel
用于文件的数据读写,DatagramChannel
用于 UDP
的数据读写,ServerSocketChannel
和 SocketChannel
用于 TCP
的数据读写。NIO
还支持通过多个 Buffer
(即 Buffer
数组)完成读写操作,即 Scattering
和 Gathering
【举例说明】
/**
* @Author:jiangdw7
* @date: 2023/8/9 10:04
*/
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws Exception {
//使用 ServerSocketChannel 和 SocketChannel 网络
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
//绑定端口到 socket,并启动
serverSocketChannel.socket().bind(inetSocketAddress);
//创建 buffer 数组
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
//等客户端连接 (telnet)
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 8; //假定从客户端接收 8 个字节
//循环的读取
while (true) {
int byteRead = 0;
while (byteRead < messageLength) {
long l = socketChannel.read(byteBuffers);
byteRead += l; //累计读取的字节数
System.out.println("byteRead = " + byteRead);
//使用流打印,看看当前的这个 buffer 的 position 和 limit
Arrays.asList(byteBuffers).stream().map(buffer -> "position = " + buffer.position() + ", limit = " + buffer.limit()).forEach(System.out::println);
}
//将所有的 buffer 进行 flip
Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
//将数据读出显示到客户端
long byteWirte = 0;
while (byteWirte < messageLength) {
long l = socketChannel.write(byteBuffers);
byteWirte += l;
}
//将所有的buffer进行clear
Arrays.asList(byteBuffers).forEach(buffer -> {
buffer.clear();
});
System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWirte + ", messagelength = " + messageLength);
}
}
}
Java
的 NIO
,用非阻塞的 IO
方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector
(选择器)。Selector
能够检测多个注册的通道上是否有事件发生(注意:多个 Channel
以事件的方式可以注册到同一个 Selector
),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。NIO
中的 ServerSocketChannel
功能类似 ServerSocket
、SocketChannel
功能类似 Socket
。Selector
相关方法说明
selector.select();
//阻塞selector.select(1000);
//阻塞 1000 毫秒,在 1000 毫秒后返回selector.wakeup();
//唤醒 selectorselector.selectNow();
//不阻塞,立马返还public class NIOClient {
private static Selector selector;
public static void main(String[] args) throws Exception {
selector = Selector.open();
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
sc.connect(new InetSocketAddress("127.0.0.1", 8081));
sc.register(selector, SelectionKey.OP_READ);
ByteBuffer bf = ByteBuffer.allocate(1024);
bf.put("Hi,server,i'm client".getBytes());
if (sc.finishConnect()) {
bf.flip();
while (bf.hasRemaining()) {
sc.write(bf);
}
while (sc.isConnected()) {
selector.select();
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isReadable()) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bf.clear();
SocketChannel othersc = (SocketChannel) key.channel();
while (othersc.read(bf) > 0) {
bf.flip();
while (bf.hasRemaining()) {
bos.write(bf.get());
}
bf.clear();
}
System.out.println("服务端返回的数据:" + bos.toString());
Thread.sleep(5000);
sc.close();
System.out.println("客户端关闭...");
}
}
selector.selectedKeys().clear();
}
}
}
}
public class NIOServer {
private static Selector selector;
private static ServerSocketChannel serverSocketChannel;
private static ByteBuffer bf = ByteBuffer.allocate(1024);
public static void main(String[] args) throws Exception {
init();
while (true) {
int select = selector.select(10000);
if (select == 0) {
System.out.println("等待连接10秒...");
continue;
}
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
System.out.println("连接准备就绪");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
System.out.println("等待客户端连接中........................");
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
System.out.println("读准备就绪,开始读.......................");
SocketChannel channel = (SocketChannel) key.channel();
System.out.println("客户端的数据如下:");
int readLen = 0;
bf.clear();
StringBuffer sb = new StringBuffer();
while ((readLen = channel.read(bf)) > 0) {
bf.flip();
byte[] temp = new byte[readLen];
bf.get(temp, 0, readLen);
sb.append(new String(temp));
bf.clear();
}
if (-1 == readLen) {
System.out.println(channel.hashCode()+"号客户端关闭。");
channel.close();
}else {
channel.write(ByteBuffer.wrap(("客户端,你传过来的数据是:" + sb.toString()).getBytes()));
System.out.println(sb.toString()+"132123");
}
}
it.remove();
}
}
}
private static void init() throws Exception {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8081));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
}
参考连接
https://www.cnblogs.com/xdouby/p/8942083.html
https://www.zhihu.com/question/22524908
https://blog.csdn.net/ArtAndLife/article/details/121001656
JDK7
引入了 AsynchronousI/O
,即 AIO
。在进行 I/O
编程中,常用到两种模式:Reactor
和 Proactor
。Java
的 NIO
就是 Reactor
,当有事件触发时,服务器端得到通知,进行相应的处理
AIO
即 NIO2.0
,叫做异步不阻塞的 IO
。AIO
引入异步通道的概念,采用了 Proactor
模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
目前 AIO
还没有广泛应用,Netty
也是基于 NIO
,而不是 AIO
,因此我们就不详解 AIO
了,有兴趣的同学可以参考《Java新一代网络编程模型AIO原理及Linux系统AIO介绍》