From 27fcdb1a2b72e79078c3fb30f1e005467f5d95e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=A0=E7=9A=84=E7=94=A8=E6=88=B7=E5=90=8D?= <1708423627@qq.com> Date: Fri, 5 Dec 2025 11:31:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AB=AF=E4=B8=8E=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 35 ++++- .../java/com/example/echo/EchoClient.java | 57 +++++++- .../java/com/example/echo/EchoServer.java | 125 +++++++++++++----- 3 files changed, 177 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4b3544a..d8e7acf 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,13 @@ ## 功能特性 -- 单客户端连接处理 -- 消息回声功能 -- 支持断开连接命令("bye") -- 基础错误处理 +- **多客户端同时连接**:支持多个客户端同时连接和通信 +- **消息回声功能**:服务器将客户端发送的消息原样返回 +- **TIME命令支持**:客户端发送"TIME"可获取服务器当前系统时间 +- **连接日志**:详细记录客户端连接、断开和通信日志 +- **支持断开连接命令**:客户端发送"bye"可优雅断开连接 +- **线程池管理**:使用缓存线程池高效处理多客户端连接 +- **基础错误处理**:完善的异常处理和资源清理 ## 项目结构 @@ -72,8 +75,28 @@ java -cp target/classes com.example.echo.EchoClient ## 端口配置 - 默认服务器端口: 8888 -- 客户端默认连接地址: localhost:8888 +- 端口范围: 8888-8898 (自动端口查找) +- 客户端自动连接: 客户端会自动在端口范围内查找运行中的服务器 + +## 端口管理特性 + +- **自动端口查找**: 如果默认端口8888被占用,服务器会自动尝试8888-8898范围内的其他端口 +- **智能客户端连接**: 客户端会自动查找并连接到运行中的服务器,无需手动指定端口 +- **端口冲突解决**: 解决了"Address already in use: bind"错误 +- **连接超时**: 客户端连接设置1秒超时,避免长时间等待 ## 测试 -输入任意消息到客户端,服务器将回传相同的消息加上"Echo: "前缀。输入"bye"可断开连接。 \ No newline at end of file +### 基本功能测试 +输入任意消息到客户端,服务器将回传相同的消息加上"Echo: "前缀。 + +### TIME命令测试 +输入"TIME"到客户端,服务器将返回当前系统时间,格式为"Server Time: yyyy-MM-dd HH:mm:ss"。 + +### 多客户端测试 +1. 启动服务器后,可以同时打开多个客户端终端 +2. 每个客户端都可以独立发送消息和接收响应 +3. 服务器会为每个客户端连接打印详细的日志信息 + +### 断开连接测试 +输入"bye"可断开当前客户端连接,服务器会记录断开日志。 \ No newline at end of file diff --git a/src/main/java/com/example/echo/EchoClient.java b/src/main/java/com/example/echo/EchoClient.java index 12bf370..080ae29 100644 --- a/src/main/java/com/example/echo/EchoClient.java +++ b/src/main/java/com/example/echo/EchoClient.java @@ -9,16 +9,27 @@ import java.net.*; */ public class EchoClient { private static final String SERVER_HOST = "localhost"; - private static final int SERVER_PORT = 8888; + private static final int DEFAULT_PORT = 8888; + private static final int PORT_RANGE_START = 8888; + private static final int PORT_RANGE_END = 8898; public static void main(String[] args) { - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT); + // 尝试连接到服务器,自动查找可用端口 + Socket socket = findServerSocket(); + if (socket == null) { + System.err.println("无法连接到Echo服务器,请确保服务器正在运行"); + return; + } + + int connectedPort = socket.getPort(); + + try (socket; PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) { - System.out.println("已连接到Echo服务器: " + SERVER_HOST + ":" + SERVER_PORT); - System.out.println("输入消息发送到服务器 (输入 'bye' 断开连接):"); + System.out.println("已连接到Echo服务器: " + SERVER_HOST + ":" + connectedPort); + System.out.println("输入消息发送到服务器 (输入 'bye' 断开连接,输入 'TIME' 获取服务器时间):"); String userInput; while ((userInput = stdIn.readLine()) != null) { @@ -41,4 +52,42 @@ public class EchoClient { System.err.println("与服务器通信时发生错误: " + e.getMessage()); } } + + /** + * 查找并连接到Echo服务器 + */ + private static Socket findServerSocket() { + // 首先尝试默认端口 + Socket socket = tryConnect(DEFAULT_PORT); + if (socket != null) { + return socket; + } + + // 如果默认端口失败,尝试其他端口 + for (int port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) { + if (port != DEFAULT_PORT) { + socket = tryConnect(port); + if (socket != null) { + System.out.println("在端口 " + port + " 找到Echo服务器"); + return socket; + } + } + } + + return null; + } + + /** + * 尝试连接到指定端口 + */ + private static Socket tryConnect(int port) { + try { + Socket socket = new Socket(); + socket.connect(new InetSocketAddress(SERVER_HOST, port), 1000); // 1秒超时 + return socket; + } catch (IOException e) { + // 连接失败,返回null + return null; + } + } } \ No newline at end of file diff --git a/src/main/java/com/example/echo/EchoServer.java b/src/main/java/com/example/echo/EchoServer.java index f55f684..f991225 100644 --- a/src/main/java/com/example/echo/EchoServer.java +++ b/src/main/java/com/example/echo/EchoServer.java @@ -2,81 +2,145 @@ package com.example.echo; import java.io.*; import java.net.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** - * 单客户端Echo服务器 - * 这个服务器一次只处理一个客户端连接 + * 多客户端Echo服务器 + * 支持多个客户端同时连接,并提供连接日志和TIME命令 */ public class EchoServer { - private static final int PORT = 8888; + private static final int DEFAULT_PORT = 8888; + private static final int PORT_RANGE_START = 8888; + private static final int PORT_RANGE_END = 8898; private ServerSocket serverSocket; private boolean running = false; - private ExecutorService executor; + private final ExecutorService executor; + private int currentPort; + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public EchoServer() { - executor = Executors.newSingleThreadExecutor(); + // 使用线程池支持多客户端连接 + executor = Executors.newCachedThreadPool(); } /** * 启动服务器 */ public void start() throws IOException { - serverSocket = new ServerSocket(PORT); + // 尝试在默认端口启动,如果失败则尝试其他端口 + if (!tryStartOnPort(DEFAULT_PORT)) { + System.out.println("[" + getCurrentTime() + "] 默认端口 " + DEFAULT_PORT + " 被占用,尝试其他端口..."); + + // 在端口范围内寻找可用端口 + boolean started = false; + for (int port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) { + if (port != DEFAULT_PORT && tryStartOnPort(port)) { + started = true; + break; + } + } + + if (!started) { + throw new IOException("无法在端口范围 " + PORT_RANGE_START + "-" + PORT_RANGE_END + " 内找到可用端口"); + } + } + running = true; - System.out.println("Echo Server已启动,监听端口: " + PORT); - - // 等待客户端连接 - System.out.println("等待客户端连接..."); - Socket clientSocket = serverSocket.accept(); - System.out.println("客户端已连接: " + clientSocket.getRemoteSocketAddress()); + System.out.println("[" + getCurrentTime() + "] Echo Server已启动,监听端口: " + currentPort); - handleClient(clientSocket); + // 持续接受客户端连接 + while (running) { + try { + System.out.println("[" + getCurrentTime() + "] 等待客户端连接..."); + Socket clientSocket = serverSocket.accept(); + System.out.println("[" + getCurrentTime() + "] 新客户端连接: " + clientSocket.getRemoteSocketAddress()); + + // 为每个客户端创建独立的处理线程 + executor.submit(() -> handleClient(clientSocket)); + } catch (IOException e) { + if (running) { + System.err.println("[" + getCurrentTime() + "] 接受客户端连接时发生错误: " + e.getMessage()); + } + } + } } /** * 处理客户端连接 */ private void handleClient(Socket clientSocket) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + String clientAddress = clientSocket.getRemoteSocketAddress().toString(); + + try (clientSocket; + BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) { - String inputLine; - System.out.println("开始与客户端通信..."); + System.out.println("[" + getCurrentTime() + "] 开始与客户端 " + clientAddress + " 通信..."); + String inputLine; while ((inputLine = in.readLine()) != null) { + System.out.println("[" + getCurrentTime() + "] 收到来自 " + clientAddress + " 的消息: " + inputLine); + if ("bye".equalsIgnoreCase(inputLine.trim())) { - System.out.println("客户端请求断开连接"); + System.out.println("[" + getCurrentTime() + "] 客户端 " + clientAddress + " 请求断开连接"); break; } - - System.out.println("收到消息: " + inputLine); - // 将收到的消息回显给客户端 - out.println("Echo: " + inputLine); + + // 检查是否是TIME命令 + if ("TIME".equalsIgnoreCase(inputLine.trim())) { + String currentTime = getCurrentTime(); + System.out.println("[" + currentTime + "] 客户端 " + clientAddress + " 请求系统时间"); + out.println("Server Time: " + currentTime); + } else { + // 将收到的消息回显给客户端 + out.println("Echo: " + inputLine); + } } } catch (IOException e) { - System.err.println("处理客户端连接时发生错误: " + e.getMessage()); - } finally { - try { - clientSocket.close(); - System.out.println("客户端连接已关闭"); - } catch (IOException e) { - System.err.println("关闭客户端连接时发生错误: " + e.getMessage()); - } + System.err.println("[" + getCurrentTime() + "] 处理客户端 " + clientAddress + " 连接时发生错误: " + e.getMessage()); } + System.out.println("[" + getCurrentTime() + "] 客户端 " + clientAddress + " 连接已关闭"); } + + /** + * 尝试在指定端口启动服务器 + */ + private boolean tryStartOnPort(int port) { + try { + serverSocket = new ServerSocket(port); + currentPort = port; + return true; + } catch (IOException e) { + // 端口被占用或其他错误 + return false; + } + } + + /** + * 获取当前时间字符串 + */ + private String getCurrentTime() { + return LocalDateTime.now().format(TIME_FORMATTER); + } + + /** * 停止服务器 */ public void stop() throws IOException { running = false; + System.out.println("[" + getCurrentTime() + "] 正在停止服务器..."); + if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } + executor.shutdown(); try { if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { @@ -85,7 +149,8 @@ public class EchoServer { } catch (InterruptedException e) { executor.shutdownNow(); } - System.out.println("服务器已停止"); + + System.out.println("[" + getCurrentTime() + "] 服务器已停止"); } public static void main(String[] args) { -- Gitee