返回
从零开始认识Java NIO
后端
2023-09-23 09:39:04
最近在学习Netty的相关知识,了解到Java NIO是一个高性能的网络编程框架,可以帮助我们开发高并发、高性能的网络应用。在学习过程中,我发现对NIO的基础知识了解得不够深入,所以对NIO的核心组件进行了梳理。
核心组件
Channel
Channel是Java NIO中最重要的组件之一,它代表了网络通信的通道,是数据传输的媒介。Channel可以分为两种类型:
- 阻塞式Channel: 阻塞式Channel在读取或写入数据时会阻塞,直到数据传输完成。
- 非阻塞式Channel: 非阻塞式Channel在读取或写入数据时不会阻塞,即使数据传输尚未完成。
Buffer
Buffer是Java NIO中用于存储数据的缓冲区。它可以存储各种类型的数据,如字节数组、字符串、整数等。当我们从Channel中读取数据时,数据会被存储到Buffer中。当我们向Channel中写入数据时,数据会从Buffer中读取。
Selector
Selector是Java NIO中用于管理多个Channel的组件。它可以同时监听多个Channel,并当其中一个Channel有数据可读或可写时通知我们。
工作原理
NIO通过事件驱动的方式来工作。当一个Channel有数据可读或可写时,Selector会通知我们。我们就可以从Channel中读取数据,或者向Channel中写入数据。
NIO通过这种方式可以同时监听多个Channel,并在数据可读或可写时及时通知我们,从而实现高并发、高性能的网络通信。
实例
现在,让我们通过一个简单的例子来了解NIO是如何工作的。
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NIOServer {
public static void main(String[] args) throws IOException {
// 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
// 设置非阻塞模式
serverSocketChannel.configureBlocking(false);
// 创建Selector
Selector selector = Selector.open();
// 将ServerSocketChannel注册到Selector上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞等待事件发生
selector.select();
// 获取所有已发生的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍历事件
for (SelectionKey selectionKey : selectionKeys) {
// 如果是accept事件
if (selectionKey.isAcceptable()) {
// 获取客户端连接的SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
// 设置SocketChannel为非阻塞模式
socketChannel.configureBlocking(false);
// 将SocketChannel注册到Selector上
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 获取SocketChannel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 创建ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从SocketChannel中读取数据到ByteBuffer中
int readBytes = socketChannel.read(buffer);
// 如果readBytes小于0,说明客户端连接已关闭
if (readBytes < 0) {
socketChannel.close();
} else {
// 将ByteBuffer中的数据转为字符串
String data = new String(buffer.array(), 0, readBytes);
// 输出数据
System.out.println("收到客户端数据:" + data);
// 将SocketChannel注册到Selector上
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
// 获取SocketChannel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 创建ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 向SocketChannel中写入数据
buffer.put("Hello, client!".getBytes());
buffer.flip();
socketChannel.write(buffer);
// 将SocketChannel注册到Selector上
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
// 清空SelectionKeys集合
selectionKeys.clear();
}
}
}
这个例子演示了一个简单的NIO服务器端。它监听8080端口,当有客户端连接时,它会接受连接并将其注册到Selector上。当客户端有数据可读时,服务器端会从客户端读取数据并输出到控制台。当服务器端有数据可写时,它会向客户端写入数据。