作者 邓耀骏

feat:完成实验1和2

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