
Java NIO 网络编程详细使用教程
NIO 概述
Java NIO(New I/O)自 Java 1.4 引入,是对传统 BIO(Blocking I/O)的革新。NIO 采用非阻塞 IO和IO 多路复用技术,让单个线程可以高效管理成千上万的连接,特别适合高并发网络应用场景。
核心区别:BIO vs NIO
| 特性 | BIO(传统 IO) | NIO(新 IO) |
|---|---|---|
| 阻塞模式 | 阻塞式 | 非阻塞/多路复用 |
| 连接处理 | 每个连接一个线程 | 单线程管理多个连接 |
| 适用场景 | 低并发连接 | 高并发连接 |
| 性能 | 连接数多时性能下降 | 高并发下性能优异 |
| 编程模型 | 同步阻塞 | 异步/选择器模式 |
NIO 核心组件
1. Buffer(缓冲区)
Buffer 是 NIO 读写数据时的中间容器,所有数据都必须经过 Buffer。
“`java
// 创建容量为 1024 字节的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 写入数据
buffer.putInt(12345);
buffer.putFloat(3.14f);
// 切换为读取模式
buffer.flip();
// 读取数据
int value = buffer.getInt();
float floatValue = buffer.getFloat();
// 清空缓冲区
buffer.clear();
Buffer 关键模式:
- write 模式:向 Buffer 写入数据
- flip 模式:切换为读取,position 重置为 0,limit 设为 capacity
- clear 模式:清空 Buffer,准备下一次写入
2. Channel(通道)
Channel 类似于传统 IO 的 Stream,但支持双向读写。
java
// ServerSocketChannel:监听连接的通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8888));
serverChannel.configureBlocking(false); // 非阻塞模式
// SocketChannel:客户端连接通道
SocketChannel clientChannel = SocketChannel.open();
clientChannel.connect(new InetSocketAddress(“localhost”, 8888));
// FileChannel:文件通道
FileChannel fileChannel = Files.newChannel(Paths.get(“file.txt”));
常见 Channel 类型:
- `ServerSocketChannel`:服务端监听通道
- `SocketChannel`:客户端连接通道
- `DatagramChannel`:UDP 数据报通道
- `FileChannel`:文件读写通道
3. Selector(选择器)
Selector 是 NIO 的灵魂,允许单个线程监听多个 Channel 的多种事件(连接、读取、写入等)。
java
// 创建选择器
Selector selector = Selector.open();
// 注册 Channel 到选择器
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
// 阻塞等待事件发生
selector.select();
// 获取有事件发生的 SelectionKey 集合
Set
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
// 接受新连接
SocketChannel clientChannel = serverChannel.accept();
clientChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// 处理读事件
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
}
if (key.isWritable()) {
// 处理写事件
}
// 从 selectedKeys 中移除,避免重复处理
key.cancel();
keys.remove(key);
}
NIO 阻塞与非阻塞模式
阻塞模式(Blocking Mode)
默认模式下,Channel 操作会阻塞等待,直到操作完成。
java
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8888));
// 阻塞等待连接
SocketChannel client = server.accept(); // 线程在此阻塞
// 阻塞读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer); // 线程在此阻塞
非阻塞模式(Non-blocking Mode)
Channel 不阻塞等待,操作立即返回。
java
server.configureBlocking(false); // 设置为非阻塞
// accept() 立即返回,没有连接返回 null
SocketChannel client = server.accept();
// read() 立即返回,没有数据返回 -1
int bytesRead = client.read(buffer); // 立即返回,可能是 0 或 -1
非阻塞优势:
- 单线程可管理多个连接
- 不会因等待而浪费线程资源
- 适合高并发场景
完整示例:NIO 服务端
java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NioServer {
private static final int PORT = 8888;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) {
Selector selector = null;
try {
// 1. 创建选择器
selector = Selector.open();
// 2. 创建服务端通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(PORT));
// 3. 设置为非阻塞模式
serverChannel.configureBlocking(false);
// 4. 注册到选择器,监听连接事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println(“NIO 服务端启动,监听端口:” + PORT);
// 5. 循环处理事件
while (true) {
// 阻塞等待至少一个事件发生
selector.select();
// 获取有事件发生的 Key 集合
Set
Iterator
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove(); // 重要:处理后移除
if (!key.isValid()) continue;
// 处理连接请求
if (key.isAcceptable()) {
handleAccept((ServerSocketChannel) key.channel(), selector);
}
// 处理读事件
if (key.isReadable()) {
handleRead((SocketChannel) key.channel());
}
// 处理写事件
if (key.isWritable()) {
handleWrite((SocketChannel) key.channel());
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 资源清理
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 处理新连接
private static void handleAccept(ServerSocketChannel serverChannel, Selector selector) throws IOException {
SocketChannel clientChannel = serverChannel.accept();
if (clientChannel != null) {
clientChannel.configureBlocking(false);
// 注册读事件
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println(“新客户端连接:” + clientChannel.getRemoteAddress());
}
}
// 处理读事件
private static void handleRead(SocketChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int bytesRead = channel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data, “UTF-8”);
System.out.println(“收到消息:” + message);
// 回显响应
channel.write(ByteBuffer.wrap((“服务器回复:” + message).getBytes(“UTF-8”)));
} else if (bytesRead == -1) {
// 客户端断开连接
channel.close();
System.out.println(“客户端断开连接”);
}
}
// 处理写事件
private static void handleWrite(SocketChannel channel) throws IOException {
// 处理写完成事件
System.out.println(“写操作完成”);
}
}
完整示例:NIO 客户端
java
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.SocketChannel;
import java.util.Set;
public class NioClient {
private static final String HOST = “localhost”;
private static final int PORT = 8888;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) {
try {
// 1. 创建通道
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
// 2. 连接到服务端(非阻塞,需要手动完成连接)
channel.connect(new InetSocketAddress(HOST, PORT));
// 3. 创建选择器
Selector selector = Selector.open();
// 4. 注册选择事件
SelectionKey key = channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
System.out.println(“NIO 客户端启动,正在连接服务端…”);
// 5. 等待连接完成
while (!key.isConnectable()) {
selector.select();
}
if (channel.finishConnect()) {
System.out.println(“连接成功!”);
// 发送消息
String message = “你好,NIO 服务器!”;
channel.write(ByteBuffer.wrap(message.getBytes(“UTF-8”)));
System.out.println(“发送:” + message);
// 注册读事件
key.interestOps(SelectionKey.OP_READ);
// 处理响应
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
while (true) {
selector.select();
Set
for (SelectionKey key1 : keys) {
if (key1.isReadable()) {
int bytesRead = channel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println(“收到:” + new String(data, “UTF-8”));
}
}
keys.remove(key1);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
“`
实际应用场景
场景一:高并发聊天室
使用 NIO 选择器实现单线程管理数千个在线用户,支持群发消息、私聊等功能。相比 BIO,可节省大量线程资源。
场景二:网络游戏服务器
MMORPG 等在线游戏需要同时处理成千上万个玩家的实时操作,NIO 非阻塞 IO 是理想选择,可大幅降低服务器负载。
场景三:消息中间件
如 RabbitMQ、Kafka 等消息队列系统采用 NIO 处理大量连接,实现高效的消息路由和传输。
场景四:Web 服务器框架
Netty、Mina 等基于 NIO 的网络框架被广泛应用于 Web 服务器,如 Netty 支撑了支付宝、淘宝等核心系统。
场景五:微服务通信
Spring Cloud、Dubbo 等微服务框架使用 NIO 实现服务间的远程调用,提升系统吞吐能力。
最佳实践建议
- 合理设置 Buffer 大小:根据实际业务需求设置,过大浪费内存,过小影响性能
- 注意资源清理:使用 try-finally 确保 Selector 和 Channel 关闭
- 避免频繁创建对象:复用 Buffer 等对象减少 GC 压力
- 处理异常连接:及时检测并清理断开或异常的连接
- 选择器事件优化:合理配置 interestOps,避免不必要的轮询
- 考虑使用 Netty:对于复杂场景,建议使用成熟的 Netty 框架,它基于 NIO 二次封装,提供了更强大的功能
常见问题
1. 连接状态处理
非阻塞 connect 需要等待连接完成,通过 `finishConnect()` 检测。
2. 数据粘包/拆包
TCP 是流式协议,需要自定义协议处理消息边界,如添加长度字段或分隔符。
3. Selector 单线程问题
虽然 NIO 支持多线程,但单个 Selector 应由一个线程独占,多个线程同时调用 select() 会导致线程安全问题。
—
总结:Java NIO 通过 Selector 机制实现了高并发网络编程,是构建高性能网络应用的基础。掌握 NIO 的核心原理和编程技巧,将为你的网络应用开发打开新的大门。对于复杂场景,建议结合 Netty 等成熟框架使用,可进一步提升开发效率和系统稳定性。






















暂无评论内容