作者 邓耀骏

feat:完成实验1和2

### 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
\ No newline at end of file
... ...
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="Software_Architecture_Experiment_onetwo" />
</profile>
</annotationProcessing>
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="accountSettings">
<option name="activeRegion" value="us-east-1" />
<option name="recentlyUsedRegions">
<list>
<option value="us-east-1" />
</list>
</option>
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Software_Architecture_Experiment_onetwo.iml" filepath="$PROJECT_DIR$/Software_Architecture_Experiment_onetwo.iml" />
</modules>
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
</component>
</module>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dyj</groupId>
<artifactId>Software_Architecture_Experiment_onetwo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.92.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
\ No newline at end of file
... ...
package one;
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8888);
System.out.println("连接服务器成功!");
Scanner scanner = new Scanner(System.in);
String input;
while (true){
if ((input = scanner.next()) != null){
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.println(input);
writer.flush();
System.out.println("已发送命令:"+input);
if(input.equals("exit")){
break;
}
}
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String time = reader.readLine();
// 处理读取到的数据
System.out.println("收到服务器时间:" + time);
// String time = reader.readLine();
// System.out.println("收到服务器时间:" + time);
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
... ...
package one;
import java.net.*;
import java.io.*;
import java.util.Date;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已启动,等待客户端连接...");
Socket socket = null;
while (true) {
if (socket == null){
socket = serverSocket.accept();
System.out.println("客户端连接成功!");
}
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String command = reader.readLine();
if(command != null && command.equals("exit")){
break;
}
System.out.println("收到客户端命令:" + command);
if (command != null){
if (command.equals("get_time")) {
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.println(new Date(System.currentTimeMillis()));
writer.flush();
System.out.println("服务器时间已发送!");
}else {
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.println("该命令错误,请输入正确命令!");
writer.flush();
System.out.println("命令错误!!!");
}
}
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
... ...
package two;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class Client {
private static final int BUFFER_SIZE = 1024;
private static final String HOST = "localhost";
private static final int PORT = 8888;
public static void main(String[] args) {
try {
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(HOST, PORT));
while (!clientChannel.finishConnect()) {
// wait until connection is established
}
System.out.println("Connected to server " + HOST + ":" + PORT);
System.out.println("Enter your name:");
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(name.getBytes());
clientChannel.write(buffer);
new Thread(() -> {
while (true) {
try {
ByteBuffer receiveBuffer = ByteBuffer.allocate(BUFFER_SIZE);
int bytesRead = clientChannel.read(receiveBuffer);
if (bytesRead > 0) {
String message = new String(receiveBuffer.array()).trim();
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
while (true) {
String message = scanner.nextLine();
buffer = ByteBuffer.wrap(message.getBytes());
clientChannel.write(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
... ...
package two;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class NIOClient {
public void start(String hostname, int port) throws IOException {
// 创建SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(hostname, port));
// 等待连接完成
while (!socketChannel.finishConnect()) {
// do nothing
}
System.out.println("Connected to server " + socketChannel.getRemoteAddress());
// 循环读取用户输入,并发送到服务器
Scanner scanner = new Scanner(System.in);
new Thread(() -> {
while (true) {
try {
// 读取服务器发送的消息
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int numRead = socketChannel.read(readBuffer);
if (numRead > 0) {
String receivedMessage = new String(readBuffer.array(), 0, numRead).trim();
System.out.println("Received message from server: " + receivedMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
while (true) {
if (scanner.hasNext()){
String message = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
socketChannel.write(buffer);
}
}
}
public static void main(String[] args) throws IOException {
NIOClient client = new NIOClient();
client.start("localhost", 8888);
}
}
\ No newline at end of file
... ...
package two;
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class NIOServer {
private Selector selector;
private Map<SocketChannel, String> clients = new HashMap<>();
public void start(int port) throws IOException {
// 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
// 创建Selector
selector = Selector.open();
// 注册ServerSocketChannel到Selector上,监听ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started...");
// 循环处理事件
while (true) {
// 调用select方法阻塞,等待事件发生
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
if (key.isAcceptable()) { // 处理ACCEPT事件
handleAccept(key);
} else if (key.isReadable()) { // 处理READ事件
handleRead(key);
}
}
}
}
private void handleAccept(SelectionKey key) throws IOException {
// 获取ServerSocketChannel
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 接受客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 注册SocketChannel到Selector上,监听READ事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("Client " + socketChannel.getRemoteAddress() + " connected.");
// 将新连接的客户端添加到clients列表中
clients.put(socketChannel, socketChannel.getRemoteAddress().toString());
}
private void handleRead(SelectionKey key) throws IOException {
// 获取SocketChannel
SocketChannel socketChannel = (SocketChannel) key.channel();
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int numRead = socketChannel.read(buffer);
if (numRead == -1) { // 客户端关闭连接
key.cancel();
clients.remove(socketChannel);
System.out.println("Client " + socketChannel.getRemoteAddress() + " disconnected.");
socketChannel.close();
return;
}
// 将数据转发给其他客户端
String message = new String(buffer.array(), 0, numRead).trim();
System.out.println("Received message from " + socketChannel.getRemoteAddress() + ": " + message);
for (SocketChannel client : clients.keySet()) {
if (client != socketChannel) {
ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
client.write(writeBuffer);
}
}
}
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.start(8888);
}
}
... ...
package two;
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Server {
private static final int BUFFER_SIZE = 1024;
private static final int PORT = 8888;
private Selector selector;
private Map<SocketChannel, String> clientMap = new HashMap<>();
public static void main(String[] args) {
Server server = new Server();
server.startServer();
}
public void startServer() {
try {
selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started on port " + PORT);
while (true) {
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
SocketChannel clientChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("New client connected: " + clientChannel.getRemoteAddress());
clientMap.put(clientChannel, "");
}
if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int bytesRead = clientChannel.read(buffer);
if (bytesRead > 0) {
String message = new String(buffer.array()).trim();
if (clientMap.get(clientChannel).equals("")) {
clientMap.put(clientChannel, message);
System.out.println("New user registered: " + message);
broadcast(message + "上线啦 ", clientChannel);
} else {
System.out.println("Message received from " + clientMap.get(clientChannel) + ": " + message);
broadcast(clientMap.get(clientChannel) + ": " + message, clientChannel);
}
}
}
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void broadcast(String message, SocketChannel senderChannel) throws IOException {
for (SocketChannel clientChannel : clientMap.keySet()) {
if (clientChannel != senderChannel) {
System.out.println(clientChannel);
//多个对象发送,每次都得载一个新的对象,不然就只能发送一次
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
clientChannel.write(buffer);
}
}
}
}
... ...
# 代码流程
代码实现了一个基于Java NIO的C/S架构的简单聊天室,其中服务器端使用epoll模型,支持多个客户端连接,一个客户端发送消息,服务器会将消息转发到其他客户端并在其他客户端显示这条消息。
服务器端代码的实现流程如下:
1. 创建Selector对象和ServerSocketChannel对象,并将ServerSocketChannel注册到Selector上,监听连接事件。
2. 进入主循环,调用Selector的select()方法等待事件发生。
3. 如果有事件发生,使用迭代器遍历SelectionKey集合,处理每个事件。
4. 如果是连接事件,使用ServerSocketChannel的accept()方法接受客户端连接,并将客户端的SocketChannel注册到Selector上,监听读事件。
5. 如果是读事件,使用SocketChannel的read()方法读取客户端发送的消息,并将其转发给其他客户端。
6. 处理完事件后,使用迭代器的remove()方法将SelectionKey从集合中删除。
7. 回到主循环,等待下一个事件。
客户端代码的实现流程如下:
1. 创建SocketChannel对象,连接服务器。
2. 如果连接未完成,等待连接完成。
3. 读取用户输入的用户名,将其发送给服务器。
4. 创建一个新线程,使用SocketChannel的read()方法读取服务器发送的消息,并在控制台上显示。
5. 在主线程中,读取用户输入的消息,使用SocketChannel的write()方法将其发送给服务器。
服务器端代码的重点内容:
1. 使用Selector模型实现了高效的I/O多路复用,支持同时处理多个客户端连接。
2. 使用ByteBuffer缓冲区实现了高效的读写操作。
3. 使用Map集合保存每个客户端的SocketChannel和用户名,实现了转发消息给其他客户端的功能。
4. 使用迭代器遍历SelectionKey集合,处理每个事件,实现了高效的事件处理。
客户端代码的重点内容:
1. 使用SocketChannel实现了非阻塞式的I/O操作。
2. 使用ByteBuffer缓冲区实现了高效的读写操作。
3. 使用Scanner读取用户输入的消息,实现了用户与服务器的交互。
\ No newline at end of file
... ...