Netty学习系列之二
本文是Netty学习路线的第二篇,重点讲解Java NIO的核心概念及编程模型,这是理解Netty设计理念的关键基础。
在上一篇文章中,我们介绍了学习Netty的第一阶段:Java基础与网络编程基础。本篇文章我们将深入探讨Java NIO (New I/O或Non-blocking I/O)的核心概念和编程模型,这是理解Netty框架的关键一步。
Netty的底层实现大量借鉴并扩展了Java NIO的设计思想,掌握NIO不仅能让我们更好地理解Netty的架构,还能帮助我们在使用Netty时做出更优的设计决策。
Buffer是NIO中的核心概念,所有数据的读写都必须经过缓冲区。
ByteBuffer // 最常用
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
// 创建一个容量为1024的ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 写入数据
buffer.put((byte)1);
buffer.put(new byte[] {2, 3, 4});
// 从写模式切换到读模式
buffer.flip();
// 读取数据
byte b = buffer.get();
// 清空缓冲区,准备重新写入
buffer.clear();
// 压缩缓冲区,未读数据移到缓冲区头部
buffer.compact();
// 非直接缓冲区(堆缓冲区)
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
// 直接缓冲区
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
直接缓冲区的优势在于避免了Java堆与本地堆的数据复制,适合需要频繁I/O操作的场景。Netty的ByteBuf扩展了这一概念,提供了更高效的内存管理机制。
Channel是NIO中的另一核心概念,代表与I/O设备(如文件、套接字)的连接。与传统Stream不同,Channel是双向的,可读可写。
FileChannel // 文件操作
SocketChannel // TCP客户端
ServerSocketChannel // TCP服务器端
DatagramChannel // UDP通信
// 文件Channel示例
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer); // 从channel读取数据到buffer
buffer.flip();
channel.write(buffer); // 从buffer写数据到channel
channel.close();
// 网络Channel示例
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("example.com", 80));
// 设置为非阻塞模式
socketChannel.configureBlocking(false);
Selector是NIO的核心,实现了多路复用,允许单线程处理多个Channel。
SelectionKey.OP_READ // 读就绪事件
SelectionKey.OP_WRITE // 写就绪事件
SelectionKey.OP_CONNECT // 连接就绪事件(客户端)
SelectionKey.OP_ACCEPT // 接收就绪事件(服务器)
// 创建Selector
Selector selector = Selector.open();
// 创建ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
// 注册到Selector
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞等待事件
int readyChannels = selector.select();
if (readyChannels == 0) continue;
// 获取就绪事件
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// 处理接收就绪事件
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 处理读就绪事件
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
// 处理读取的数据...
}
// 从就绪集合中移除已处理的事件
keyIterator.remove();
}
}
在阻塞I/O模式下:
// BIO示例
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
// 阻塞等待连接
Socket clientSocket = serverSocket.accept();
// 为每个客户端创建新线程
new Thread(() -> {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String line;
// 阻塞读取数据
while ((line = in.readLine()) != null) {
out.println("Echo: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
在非阻塞I/O模式下:
// 上面的Selector示例代码展示了NIO的非阻塞特性
特性 | 阻塞I/O (BIO) | 非阻塞I/O (NIO) |
---|---|---|
编程复杂度 | 简单直观 | 较复杂 |
线程模型 | 一连接一线程 | 一线程多连接 |
内存消耗 | 较高 | 较低 |
并发能力 | 有限 | 高 |
适用场景 | 连接数少、业务复杂 | 连接数多、业务简单 |
public class NIOEchoServer {
public static void main(String[] args) throws IOException {
// 创建Selector
Selector selector = Selector.open();
// 创建并配置ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(8080));
// 注册到Selector
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Echo Server started on port 8080");
while (true) {
// 等待事件
selector.select();
// 处理就绪事件
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid()) continue;
try {
// 根据事件类型分别处理
if (key.isAcceptable()) {
handleAccept(key, selector);
} else if (key.isReadable()) {
handleRead(key);
} else if (key.isWritable()) {
handleWrite(key);
}
} catch (IOException e) {
key.cancel();
key.channel().close();
}
}
}
}
private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
// 准备缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将缓冲区附加到通道上
clientChannel.register(selector, SelectionKey.OP_READ, buffer);
System.out.println("Connection accepted: " + clientChannel.getRemoteAddress());
}
private static void handleRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
// 连接已关闭
clientChannel.close();
key.cancel();
System.out.println("Connection closed");
return;
}
// 切换为写模式
buffer.flip();
// 切换为可写事件
key.interestOps(SelectionKey.OP_WRITE);
}
private static void handleWrite(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
clientChannel.write(buffer);
// 如果缓冲区中的数据已全部写出
if (!buffer.hasRemaining()) {
// 切换回读模式
key.interestOps(SelectionKey.OP_READ);
}
}
}
Reactor模式是NIO编程的核心架构模式,也是Netty框架的设计基础。
┌─────────┐
│ │
Client Request │ │ read/write
─────────────── │ Reactor │ ───────────
│ │
└─────────┘
│
│ dispatch
▼
┌─────────┐
│ Handler │
└─────────┘
┌─────────┐
│ │
Client Request │ │
────────────────── │ Reactor │
│ │
└─────────┘
│
│ dispatch
▼
┌─────────┬──────┴──────┬─────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐┌─────────┐ ┌─────────┐┌─────────┐
│ Handler ││ Handler │ │ Handler ││ Handler │
└─────────┘└─────────┘ └─────────┘└─────────┘
(Thread 1) (Thread 2) (Thread 3) (Thread 4)
┌───────────┐
│ │
Client Request │ Main │
───────────────────►│ Reactor │
│ │
└───────────┘
│
│ dispatch accept
▼
┌───────────┬───┴───────┬───────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌───────────┐┌───────────┐┌───────────┐┌───────────┐
│ Sub ││ Sub ││ Sub ││ Sub │
│ Reactor ││ Reactor ││ Reactor ││ Reactor │
└───────────┘└───────────┘└───────────┘└───────────┘
│ │ │ │
│dispatch │dispatch │dispatch │dispatch
┌─────┴─────┐┌─────┴─────┐┌─────┴─────┐┌─────┴─────┐
│ ThreadPool││ ThreadPool││ ThreadPool││ ThreadPool│
│ ││ ││ ││ │
└───────────┘└───────────┘└───────────┘└───────────┘
Netty框架采用了多Reactor多线程模型:
Java NIO是Netty的基础,理解NIO不仅能帮助我们更好地理解Netty,还能在不使用Netty的场景下高效地开发网络应用。本文介绍的Buffer、Channel、Selector概念在Netty中有对应的增强实现,而Reactor模式更是Netty架构的核心。
在下一篇文章中,我们将深入探讨Netty的核心概念,包括Bootstrap、Channel、ChannelPipeline等,敬请期待!
作者:by.G
如需转载,请注明出处