NIO学习笔记
NIO是什么?
NIO(Non-blocking I/O)是Java中的一种高性能I/O模型,用于处理大量并发连接。与传统的阻塞式I/O模型不同,NIO允许在单个线程上管理多个通道(网络连接或文件IO),并使用选择器(Selector)实现非阻塞式的事件驱动IO操作。这种方式可以大大减少线程的数量,提高系统的并发能力和性能。
哪些场景需要 NIO
Java NIO 适用于需要高性能、高并发的网络应用程序场景。以下是一些适合使用Java NIO的场景:
高并发的网络服务器:NIO允许一个线程管理多个连接,这使得它非常适合处理高并发的网络通信,比如Web服务器、聊天服务器等。
实时通信应用:对于需要实时处理消息、低延迟的应用程序,NIO提供了非阻塞IO和事件驱动的特性,能够更好地满足这类需求。
大规模文件传输:如果需要传输大量的数据,特别是大文件,NIO的非阻塞IO特性可以使得系统更高效地利用资源。
多协议支持:NIO可以轻松地支持多种协议,如TCP、UDP等,因为它提供了更灵活的IO操作方式。
游戏服务器:游戏服务器通常需要处理大量的并发连接和实时通信,NIO能够提供良好的性能和可扩展性,满足游戏服务器的需求。
总的来说,如果应用程序需要处理大量的并发连接、需要实时通信或者需要高性能的网络IO操作,那么就适合使用Java NIO。
代码示例
下面是一个简单的Java NIO实现的聊天弹幕服务器和客户端的代码示例:
// 服务器端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class ChatServer {
private static final int PORT = 8080;
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port " + PORT);
while (true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
acceptConnection(selector, serverSocketChannel);
} else if (key.isReadable()) {
readMessage(key);
}
}
}
}
private static void acceptConnection(Selector selector, ServerSocketChannel serverSocketChannel) throws IOException {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("New client connected: " + socketChannel.getRemoteAddress());
}
private static void readMessage(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
channel.close();
key.cancel();
return;
}
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String message = new String(bytes).trim();
System.out.println("Received message: " + message);
broadcastMessage(channel, message);
}
private static void broadcastMessage(SocketChannel sender, String message) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
for (SelectionKey key : sender.selector().keys()) {
if (key.isValid() && key.channel() instanceof SocketChannel && key.channel() != sender) {
SocketChannel channel = (SocketChannel) key.channel();
channel.write(buffer);
buffer.rewind();
}
}
}
}
// 客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class ChatClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 8080;
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
Thread receiverThread = new Thread(() -> {
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
while (true) {
buffer.clear();
int bytesRead = socketChannel.read(buffer);
if (bytesRead == -1) {
break;
}
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String message = new String(bytes).trim();
System.out.println("Received message: " + message);
}
} catch (IOException e) {
e.printStackTrace();
}
});
receiverThread.start();
Scanner scanner = new Scanner(System.in);
while (true) {
String message = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
socketChannel.write(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个简单的示例实现了一个基于Java NIO的简易聊天弹幕系统,其中服务器接受来自客户端的连接,并将客户端发送的消息广播给所有连接的客户端。客户端可以发送消息到服务器,并接收其他客户端发送的消息。
NIO如何实现允许在单个线程上管理多个通道?
Java NIO 实现允许在单个线程上管理多个通道的核心是通过 Selector(选择器)机制。Selector 是一个多路复用器,用于监听多个通道的事件,并且当事件发生时,能够通知应用程序进行处理。Selector 提供了一种高效的方式来处理多个通道的 I/O 事件,避免了传统的阻塞 I/O 模型中需要为每个连接创建一个线程的开销。
Selector 的工作原理如下:
- 将多个 Channel 注册到 Selector 上,并指定感兴趣的事件,如读、写、连接、接收等。
- Selector 负责监听这些通道上发生的事件,一旦某个通道上的事件发生,Selector 就会返回对应的 SelectionKey。
- 应用程序可以遍历 SelectionKey,获取发生事件的通道,然后进行相应的处理。
这种机制使得一个线程可以同时管理多个通道的 I/O 操作,大大减少了线程的数量,提高了系统的并发性能。
Java NIO 实现允许在单个线程上管理多个通道的核心是通过 Selector(选择器)机制。Selector 是一个多路复用器,用于监听多个通道的事件,并且当事件发生时,能够通知应用程序进行处理。Selector 提供了一种高效的方式来处理多个通道的 I/O 事件,避免了传统的阻塞 I/O 模型中需要为每个连接创建一个线程的开销。
Selector 的工作原理如下:
将多个 Channel 注册到 Selector 上,并指定感兴趣的事件,如读、写、连接、接收等。
Selector 负责监听这些通道上发生的事件,一旦某个通道上的事件发生,Selector 就会返回对应的 SelectionKey。
应用程序可以遍历 SelectionKey,获取发生事件的通道,然后进行相应的处理。
这种机制使得一个线程可以同时管理多个通道的 I/O 操作,大大减少了线程的数量,提高了系统的并发性能。
以下是一个简单的示例代码,演示了如何使用 Selector 实现单个线程管理多个通道:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class NioServer {
private static final int PORT = 8080;
public static void main(String[] args) throws IOException {
// 创建 Selector
Selector selector = Selector.open();
// 创建 ServerSocketChannel 并绑定端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
// 将 ServerSocketChannel 注册到 Selector,并指定关注的事件为接收连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port " + PORT);
while (true) {
// Selector 调用 select() 方法进行监听,阻塞直到有事件发生
selector.select();
// 获取就绪的事件集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) { // 如果是接收连接事件
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept(); // 接收客户端连接
clientChannel.configureBlocking(false); // 设置为非阻塞模式
clientChannel.register(selector, SelectionKey.OP_READ); // 注册读事件
System.out.println("Accepted new connection from " + clientChannel.getRemoteAddress());
} else if (key.isReadable()) { // 如果是可读事件
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer); // 读取数据到缓冲区
if (bytesRead != -1) {
buffer.flip(); // 切换为读模式
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("Received message: " + new String(data));
} else {
clientChannel.close(); // 关闭连接
}
}
}
}
}
}