本文目录导读:

我来为你展示一个完整的Java NIO Selector(选择器)案例,这是一个使用非阻塞I/O实现的多路复用服务器。
完整的Selector案例:简易聊天服务器
服务器端代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 基于Selector的多路复用聊天服务器
* 可以同时处理多个客户端的连接和消息
*/
public class ChatServer {
// 选择器
private Selector selector;
// 服务器通道
private ServerSocketChannel serverChannel;
// 存储所有连接的客户端
private static ConcurrentHashMap<SocketChannel, String> clients = new ConcurrentHashMap<>();
// 服务器端口
private static final int PORT = 8888;
public ChatServer() {
try {
// 1. 打开选择器
selector = Selector.open();
// 2. 打开服务器通道
serverChannel = ServerSocketChannel.open();
// 3. 设置为非阻塞模式
serverChannel.configureBlocking(false);
// 4. 绑定端口
serverChannel.socket().bind(new InetSocketAddress(PORT));
// 5. 注册到选择器,关注OP_ACCEPT事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动,监听端口:" + PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 启动服务器 - 核心事件循环
*/
public void start() {
try {
while (true) {
// 核心方法:阻塞等待就绪的事件
// select() 会阻塞,直到至少有一个通道的事件就绪
int readyCount = selector.select();
if (readyCount == 0) {
continue;
}
// 获取所有就绪的SelectionKey
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 处理完当前key后必须移除,防止重复处理
iterator.remove();
// 处理事件
try {
if (key.isAcceptable()) {
// 新连接事件
handleAccept(key);
} else if (key.isReadable()) {
// 可读事件
handleRead(key);
} else if (key.isWritable()) {
// 可写事件
handleWrite(key);
}
} catch (IOException e) {
// 客户端断开连接
closeConnection(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 处理新的客户端连接
*/
private void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 接受客户端连接
SocketChannel clientChannel = server.accept();
// 设置为非阻塞模式
clientChannel.configureBlocking(false);
// 为客户端生成名称
String clientName = "Client-" + clientChannel.hashCode();
// 注册该客户端到选择器,关注读事件
clientChannel.register(selector, SelectionKey.OP_READ);
// 保存客户端信息
clients.put(clientChannel, clientName);
System.out.println(clientName + " 已连接");
// 通知其他客户端
broadcast("【系统】" + clientName + " 加入了聊天室", null);
}
/**
* 处理客户端发来的消息
*/
private void handleRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取数据
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
// 客户端关闭了连接
closeConnection(key);
return;
}
// 切换为读模式
buffer.flip();
// 读取内容
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data, StandardCharsets.UTF_8).trim();
String clientName = clients.get(clientChannel);
System.out.println(clientName + " 发送:" + message);
// 广播给所有客户端
broadcast(clientName + ":" + message, clientChannel);
}
/**
* 处理写事件(本例主要用于演示)
*/
private void handleWrite(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
// 这里简化为:取消写关注
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
// 实际项目中,这里会处理待发送的数据队列
}
/**
* 广播消息给所有客户端
*/
private void broadcast(String message, SocketChannel exceptChannel) {
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));
for (SocketChannel client : clients.keySet()) {
if (client != exceptChannel) {
try {
// 将缓冲区重置到读位置
buffer.rewind();
client.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 关闭连接
*/
private void closeConnection(SelectionKey key) {
try {
SocketChannel channel = (SocketChannel) key.channel();
String clientName = clients.remove(channel);
if (clientName != null) {
System.out.println(clientName + " 断开连接");
broadcast("【系统】" + clientName + " 离开了聊天室", null);
}
// 取消注册
key.cancel();
// 关闭通道
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ChatServer server = new ChatServer();
server.start();
}
}
客户端代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 聊天客户端
*/
public class ChatClient {
private SocketChannel channel;
public ChatClient(String host, int port) {
try {
// 打开客户端通道
channel = SocketChannel.open(new InetSocketAddress(host, port));
// 设置阻塞模式
channel.configureBlocking(false);
System.out.println("已连接到服务器");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 启动客户端
*/
public void start() {
// 启动线程接收服务器消息
new Thread(this::receiveMessage).start();
// 主线程用于发送消息
sendMessage();
}
/**
* 接收消息线程
*/
private void receiveMessage() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
while (true) {
// 清除缓冲区
buffer.clear();
// 读取数据
int bytesRead = channel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data, StandardCharsets.UTF_8);
System.out.println(message);
}
Thread.sleep(100);
}
} catch (IOException | InterruptedException e) {
System.out.println("连接已断开");
e.printStackTrace();
}
}
/**
* 发送消息
*/
private void sendMessage() {
Scanner scanner = new Scanner(System.in);
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
while (true) {
System.out.print("请输入消息(输入exit退出):");
String message = scanner.nextLine();
if ("exit".equalsIgnoreCase(message)) {
channel.close();
break;
}
// 发送消息
buffer.clear();
buffer.put(message.getBytes(StandardCharsets.UTF_8));
buffer.flip();
while (buffer.hasRemaining()) {
channel.write(buffer);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
scanner.close();
}
}
public static void main(String[] args) {
ChatClient client = new ChatClient("localhost", 8888);
client.start();
}
}
测试示例代码
import java.io.IOException;
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.net.InetSocketAddress;
import java.util.Iterator;
/**
* 简单Selector测试示例
* 演示Selector的基本使用
*/
public class SelectorDemo {
public static void main(String[] args) throws IOException {
// 1. 创建Selector
Selector selector = Selector.open();
// 2. 创建ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8080));
// 3. 注册到Selector,关注ACCEPT事件
SelectionKey serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动,等待连接...");
// 4. 事件循环
while (true) {
// 阻塞等待,直到有事件发生
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
// 获取就绪的事件集合
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 处理完必须移除
keyIterator.remove();
if (key.isAcceptable()) {
// 有新的连接
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
// 注册读事件
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("新的客户端连接: " + clientChannel.getRemoteAddress());
// 发送欢迎消息
ByteBuffer buffer = ByteBuffer.wrap("欢迎连接服务器!\n".getBytes());
clientChannel.write(buffer);
} else if (key.isReadable()) {
// 客户端发送了数据
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = clientChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = new String(data);
System.out.println("收到消息: " + message.trim());
// 回显消息
ByteBuffer response = ByteBuffer.wrap(("服务器收到: " + message).getBytes());
clientChannel.write(response);
} else if (bytesRead == -1) {
// 客户端断开
clientChannel.close();
System.out.println("客户端断开连接");
}
}
}
}
}
}
使用说明
运行步骤:
-
先运行服务器:
javac ChatServer.java java ChatServer
-
运行多个客户端:
javac ChatClient.java java ChatClient
-
开始聊天:多个客户端可以互相发送消息
关键概念解析:
| 概念 | 说明 |
|---|---|
| Selector | 多路复用器,监测多个通道的事件 |
| SelectionKey | 表示通道和Selector的注册关系 |
| OP_ACCEPT | 接受连接事件 |
| OP_READ | 读取数据事件 |
| OP_WRITE | 写入数据事件 |
| select() | 阻塞直到至少一个通道就绪 |
优点:
- ✅ 单线程可处理多个连接
- ✅ 高并发性能
- ✅ 节省系统资源
- ✅ 非阻塞I/O模型
这个案例展示了Selector在实际应用中的完整用法,你可以根据需求自由修改和扩展。