本文目录导读:

这是一个使用Java NIO构建的简易聊天室实现方案,我将为你提供完整的代码结构和核心实现逻辑。
整体架构
ChatRoom
├── ChatServer.java // 服务器端
├── ChatClient.java // 客户端
└── UserInputHandler.java // 用户输入处理
服务器端实现
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class ChatServer {
private static final int PORT = 8888;
private static final Charset CHARSET = Charset.forName("UTF-8");
public void start() throws IOException {
// 1. 创建Selector
Selector selector = Selector.open();
// 2. 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动,监听端口:" + PORT);
// 3. 事件循环
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();
keyIterator.remove();
if (!key.isValid()) continue;
// 处理新连接
if (key.isAcceptable()) {
acceptConnection(selector, key);
}
// 处理读事件
else if (key.isReadable()) {
readMessage(selector, key);
}
}
}
}
private void acceptConnection(Selector selector, SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
// 广播新用户加入消息
System.out.println("用户 [" + clientChannel.getRemoteAddress() + "] 加入聊天室");
broadcastMessage(selector, clientChannel,
"系统消息:新用户 [" + clientChannel.getRemoteAddress() + "] 加入聊天室");
}
private void readMessage(Selector selector, SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder message = new StringBuilder();
try {
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
// 客户端断开连接
System.out.println("用户 [" + clientChannel.getRemoteAddress() + "] 离开聊天室");
broadcastMessage(selector, clientChannel,
"系统消息:用户 [" + clientChannel.getRemoteAddress() + "] 离开聊天室");
key.cancel();
clientChannel.close();
return;
}
buffer.flip();
message.append(CHARSET.decode(buffer).toString().trim());
String msg = message.toString();
if (msg.length() > 0) {
System.out.println("收到消息:" + msg);
// 广播消息给所有其他客户端
broadcastMessage(selector, clientChannel,
"[" + clientChannel.getRemoteAddress() + "]: " + msg);
}
} catch (IOException e) {
// 异常处理:关闭连接
key.cancel();
clientChannel.close();
}
}
private void broadcastMessage(Selector selector, SocketChannel sender, String message)
throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(CHARSET));
for (SelectionKey key : selector.keys()) {
Channel channel = key.channel();
if (channel instanceof SocketChannel && channel != sender) {
SocketChannel target = (SocketChannel) channel;
target.write(buffer);
buffer.rewind();
}
}
}
public static void main(String[] args) throws IOException {
new ChatServer().start();
}
}
客户端实现
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class ChatClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 8888;
private static final Charset CHARSET = Charset.forName("UTF-8");
public void start() throws IOException {
// 创建Selector
Selector selector = Selector.open();
// 创建SocketChannel并连接服务器
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 启动用户输入线程
new Thread(new UserInputHandler(socketChannel)).start();
// 事件循环
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
if (!key.isValid()) continue;
// 处理连接事件
if (key.isConnectable()) {
connectToServer(key);
}
// 处理读事件
else if (key.isReadable()) {
readFromServer(key);
}
}
}
}
private void connectToServer(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.isConnectionPending()) {
channel.finishConnect();
}
channel.configureBlocking(false);
channel.register(key.selector(), SelectionKey.OP_READ);
System.out.println("已连接到聊天服务器");
}
private void readFromServer(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder message = new StringBuilder();
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
System.out.println("服务器连接断开");
key.cancel();
channel.close();
return;
}
buffer.flip();
message.append(CHARSET.decode(buffer).toString().trim());
if (message.length() > 0) {
System.out.println(message.toString());
}
}
public static void main(String[] args) throws IOException {
new ChatClient().start();
}
}
用户输入处理
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class UserInputHandler implements Runnable {
private SocketChannel socketChannel;
private static final Charset CHARSET = Charset.forName("UTF-8");
public UserInputHandler(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
String input;
while ((input = reader.readLine()) != null) {
if ("/quit".equalsIgnoreCase(input)) {
System.out.println("退出聊天室");
System.exit(0);
}
ByteBuffer buffer = ByteBuffer.wrap(input.getBytes(CHARSET));
socketChannel.write(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用方法
-
启动服务器:
javac ChatServer.java java ChatServer
-
启动多个客户端:
javac ChatClient.java UserInputHandler.java java ChatClient
关键特性说明
非阻塞I/O
- 所有操作都是非阻塞的,一个线程可以处理多个连接
- 使用Selector实现事件驱动
事件处理
OP_ACCEPT: 处理新客户端连接OP_READ: 读取客户端消息OP_CONNECT: 客户端连接服务器
消息广播
- 服务器收到消息后广播给所有其他客户端
- 使用ByteBuffer进行数据传输
用户交互
- 输入
/quit退出聊天室 - 消息格式:
[客户端地址]: 消息内容
扩展建议
- 添加用户名:连接时发送用户名
- 私聊功能:支持
@用户 消息格式 - 消息队列:使用消息队列处理并发消息
- 心跳检测:定期检测客户端连接状态
- 日志记录:记录聊天历史
这个简易聊天室实现了基本的NIO聊天功能,你可以根据需求进行扩展和优化。