Initial commit

This commit is contained in:
2025-11-06 10:32:28 +08:00
commit 1f42e79061
112 changed files with 5101 additions and 0 deletions

29
NIOChatServ/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

10
NIOChatServ/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 依赖于环境的 Maven 主目录路径
/mavenHomeManager.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
NIOChatServ/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
NIOChatServ/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/NIOChatServ.iml" filepath="$PROJECT_DIR$/NIOChatServ.iml" />
</modules>
</component>
</project>

4
NIOChatServ/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings" defaultProject="true" />
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,255 @@
package client;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NioChatClient extends JFrame {
// UI 组件
private JTextField hostField;
private JTextField portField;
private JButton connectButton;
private JTextArea messageArea; // 显示聊天内容
private JTextField inputField; // 输入框
private JButton sendButton; // 发送按钮
// NIO相关
private volatile SocketChannel socketChannel;
private volatile Selector selector;
private volatile boolean running = false; // 用于控制读线程
private Thread readThread; // 后台读取服务器消息的线程
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
NioChatClient client = new NioChatClient();
client.setVisible(true);
});
}
/**
* 构造UI
*/
public NioChatClient() {
super("NIO Chat Client");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600, 400);
// 主机、端口输入区域
JPanel connectPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
connectPanel.add(new JLabel("服务器主机:"));
hostField = new JTextField("127.0.0.1", 10);
connectPanel.add(hostField);
connectPanel.add(new JLabel("端口:"));
portField = new JTextField("8888", 5);
connectPanel.add(portField);
connectButton = new JButton("连接");
connectPanel.add(connectButton);
// 聊天显示区域
messageArea = new JTextArea();
messageArea.setEditable(false);
JScrollPane scrollPane = new JScrollPane(messageArea);
// 发送消息区域
JPanel inputPanel = new JPanel(new BorderLayout());
inputField = new JTextField();
sendButton = new JButton("发送");
inputPanel.add(inputField, BorderLayout.CENTER);
inputPanel.add(sendButton, BorderLayout.EAST);
// 布局
getContentPane().setLayout(new BorderLayout());
getContentPane().add(connectPanel, BorderLayout.NORTH);
getContentPane().add(scrollPane, BorderLayout.CENTER);
getContentPane().add(inputPanel, BorderLayout.SOUTH);
// 按钮事件绑定
connectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!running) {
connectToServer();
} else {
disconnectFromServer();
}
}
});
sendButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
sendMessage(inputField.getText().trim());
inputField.setText("");
}
});
}
/**
* 连接到服务器
*/
private void connectToServer() {
final String host = hostField.getText().trim();
final int port = Integer.parseInt(portField.getText().trim());
try {
// 打开Selector和SocketChannel
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(host, port));
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 启动后台线程,处理服务器消息
running = true;
readThread = new Thread(this::readLoop, "NioClient-ReadThread");
readThread.start();
// 更新UI状态
connectButton.setText("断开");
appendMessage("正在连接到服务器 " + host + ":" + port + "...\n");
} catch (IOException e) {
appendMessage("连接失败: " + e.getMessage() + "\n");
e.printStackTrace();
}
}
/**
* 断开与服务器的连接
*/
private void disconnectFromServer() {
running = false;
try {
if (socketChannel != null) {
socketChannel.close();
}
if (selector != null) {
selector.wakeup();
selector.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
socketChannel = null;
selector = null;
connectButton.setText("连接");
appendMessage("已断开连接。\n");
}
}
/**
* 发送消息给服务器
*/
private void sendMessage(String msg) {
if (!running || socketChannel == null || !socketChannel.isOpen()) {
appendMessage("未连接服务器,无法发送消息。\n");
return;
}
if (msg.isEmpty()) {
return;
}
try {
socketChannel.write(ByteBuffer.wrap((msg + "\n").getBytes()));
} catch (IOException e) {
appendMessage("发送失败: " + e.getMessage() + "\n");
e.printStackTrace();
}
}
/**
* 读取服务器消息的循环线程
*/
private void readLoop() {
try {
while (running && selector != null && selector.isOpen()) {
// 阻塞等待事件
selector.select();
// 处理所有就绪事件
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (!key.isValid()) {
continue;
}
// 处理连接完成
if (key.isConnectable()) {
handleConnect(key);
}
// 处理可读
if (key.isReadable()) {
handleRead(key);
}
}
}
} catch (IOException e) {
appendMessage("读取服务器消息时出现异常: " + e.getMessage() + "\n");
e.printStackTrace();
} finally {
// 读线程结束时进行清理
disconnectFromServer();
}
}
/**
* 处理连接完成
*/
private void handleConnect(SelectionKey key) throws IOException {
SocketChannel sc = (SocketChannel) key.channel();
if (sc.isConnectionPending()) {
sc.finishConnect();
}
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
appendMessage("已连接到服务器。\n");
}
/**
* 处理可读事件:从服务器接收数据
*/
private void handleRead(SelectionKey key) throws IOException {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);
if (readBytes == -1) {
// 服务器端关闭
appendMessage("服务器已断开连接。\n");
running = false;
return;
}
if (readBytes > 0) {
buffer.flip();
// 按照简单文本协议,读到换行符即可作为一条消息
String msg = new String(buffer.array(), 0, buffer.limit()).trim();
appendMessage(msg + "\n");
}
}
/**
* 在UI文本框中追加消息 (线程安全)
*/
private void appendMessage(String msg) {
SwingUtilities.invokeLater(() -> {
messageArea.append(msg);
// 自动滚动到末尾
messageArea.setCaretPosition(messageArea.getDocument().getLength());
});
}
}

View File

@@ -0,0 +1,153 @@
package server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class NioChatServer {
// 维护所有已连接的 SocketChannel
private static final ConcurrentHashMap<SocketChannel, String> clientMap = new ConcurrentHashMap<>();
public static void main(String[] args) {
// 端口可根据需要进行修改
int port = 8888;
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
// 配置为非阻塞
serverSocketChannel.configureBlocking(false);
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(port));
// 注册到 selector监听 "接收新连接" 事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器已启动,监听端口: " + port);
// 事件循环
while (true) {
// 阻塞等待就绪的通道
selector.select();
// 获取就绪的事件集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove(); // 每次处理完一个事件后必须移除,避免重复处理
try {
// 处理新连接
if (key.isAcceptable()) {
handleAccept(key, selector);
}
// 处理可读事件
if (key.isReadable()) {
handleRead(key, selector);
}
} catch (IOException e) {
// 发生异常时关闭连接并清理
closeChannel(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 处理新客户端连接
*/
private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
if (sc == null) {
return;
}
// 配置为非阻塞
sc.configureBlocking(false);
// 注册到选择器中,关注读事件
sc.register(selector, SelectionKey.OP_READ);
// 将此客户端加入管理集合(如果需要,可以存储客户端的名称/ID等信息)
clientMap.put(sc, sc.getRemoteAddress().toString());
System.out.println("客户端连接: " + sc.getRemoteAddress());
}
/**
* 处理客户端发送的数据
*/
private static void handleRead(SelectionKey key, Selector selector) throws IOException {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);
// 正常关闭或异常
if (readBytes == -1) {
closeChannel(key);
return;
}
// 如果有数据
if (readBytes > 0) {
buffer.flip();
String msg = new String(buffer.array(), 0, buffer.limit()).trim();
System.out.println("收到消息: " + msg + " 来自: " + clientMap.get(sc));
// 识别特殊命令
if ("EXIT".equalsIgnoreCase(msg)) {
// 关闭连接
System.out.println("客户端请求断开连接: " + clientMap.get(sc));
closeChannel(key);
} else if ("PING".equalsIgnoreCase(msg)) {
// 回复 PONG
String pong = "PONG\n";
sc.write(ByteBuffer.wrap(pong.getBytes()));
} else {
// 普通消息进行广播
broadcastMessage(sc, msg);
}
}
}
/**
* 向其他所有客户端广播消息
*/
private static void broadcastMessage(SocketChannel sender, String msg) {
// 构造广播内容
String senderInfo = clientMap.get(sender);
String broadcastMsg = "[" + senderInfo + "] " + msg + "\n";
// 遍历所有连接的客户端, 写入消息
clientMap.forEach((channel, info) -> {
if (channel != sender && channel.isOpen()) {
try {
channel.write(ByteBuffer.wrap(broadcastMsg.getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
/**
* 关闭通道,清理资源
*/
private static void closeChannel(SelectionKey key) {
try {
SocketChannel sc = (SocketChannel) key.channel();
System.out.println("连接关闭: " + clientMap.get(sc));
clientMap.remove(sc);
key.cancel();
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}