From 32733e3adc4686dbaa10672302e24c05bee54a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E6=9D=BE=E5=B9=B3?= <3554138760@qq.com> Date: Fri, 5 Dec 2025 10:29:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 60 +++++++++++++- .../java/com/example/echo/EchoClient.class | Bin 0 -> 2671 bytes .../java/com/example/echo/EchoServer.class | Bin 0 -> 5548 bytes .../java/com/example/echo/EchoServer.java | 74 +++++++++++------- 4 files changed, 101 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/example/echo/EchoClient.class create mode 100644 src/main/java/com/example/echo/EchoServer.class diff --git a/README.md b/README.md index 4b3544a..3d3bbfa 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ mvn clean compile ### 2. 运行服务器 ```bash -mvn exec:java -Dexec.mainClass="com.example.echo.EchoServer" +mvn exec:java "-Dexec.mainClass=com.example.echo.EchoServer" ``` 或直接运行: @@ -56,7 +56,7 @@ java -cp target/classes com.example.echo.EchoServer 另开一个终端,运行: ```bash -mvn exec:java -Dexec.mainClass="com.example.echo.EchoClient" +mvn exec:java "-Dexec.mainClass=com.example.echo.EchoClient" ``` 或直接运行: @@ -76,4 +76,58 @@ java -cp target/classes com.example.echo.EchoClient ## 测试 -输入任意消息到客户端,服务器将回传相同的消息加上"Echo: "前缀。输入"bye"可断开连接。 \ No newline at end of file +输入任意消息到客户端,服务器将回传相同的消息加上"Echo: "前缀。输入"bye"可断开连接。 + + + +如果删除 clientSocket.close(),会出现以下问题: +主要问题 +资源泄漏 +客户端套接字连接不会被正确关闭 +系统文件描述符资源会被持续占用 +长时间运行可能导致服务器资源耗尽 +连接状态异常 +客户端连接会保持在ESTABLISHED状态 +无法正常释放TCP连接资源 +可能导致连接超时后才被动关闭 +服务器性能影响 +每个未关闭的连接都会占用服务器内存 +连接数达到系统限制时,新客户端将无法连接 +影响服务器处理其他客户端的能力 + + +更高效的替代方案 +1. 线程池模型 +// 使用固定大小的线程池处理客户端连接 + ExecutorService threadPool = Executors.newFixedThreadPool(100); + + 优势: + 限制线程数量,避免资源耗尽 + 复用线程减少创建开销 + 更好的资源控制 +2. I/O多路复用(NIO) + // 使用Selector实现单线程处理多个连接 + Selector selector = Selector.open(); + ServerSocketChannel serverChannel = ServerSocketChannel.open(); + + 优势: + 单线程可处理成千上万连接 + 零拷贝技术提高性能 + 更少的系统资源占用 + +3. 异步I/O(AIO) + // 使用异步套接字通道 + AsynchronousServerSocketChannel serverChannel = + AsynchronousServerSocketChannel.open(); + + 优势: + 事件驱动,无需主动轮询 + 系统级异步支持 + 高的并发处理能力 + + 4. Reactor模式 + 结合NIO和线程池 + 主线程负责I/O操作 + 工作线程处理业务逻辑 + 经典实现如Netty框架 + 对于高并发场景,建议采用NIO或成熟的网络框架如Netty,而不是为每个连接创建独立线程。 \ No newline at end of file diff --git a/src/main/java/com/example/echo/EchoClient.class b/src/main/java/com/example/echo/EchoClient.class new file mode 100644 index 0000000000000000000000000000000000000000..33e830a9badecb35d85d63f86a4c166a78e073d1 GIT binary patch literal 2671 zcmaJ@TW}Lq82(OavLP&_1d0R%D?(|oG~O?XS8B1&ghdqIODTJBKY8pk2orRXSZoX+p&}GzkB}gznuU7{o0(HBMJw_yBn1hj?f!#7`3wUoyn`!$tfdx(Bga)5LzyX=k zj>N6xu(UP!1y(1mk%-)9jAS!1B9lW_q>Y!>Oj??@54ym@jFmJpLss7QL&FjkFd(p^ zsdFY|+|H%V!B~fivs7T2qe)wlZCtju=hC*!DcfZ_mZP3|3`)DFU}p<94W%(6u(B!a zx}#CGCgw*SE>R&@3apw6IUBP>gx&%5Lm**ssI9)ROMBrU|OrOPNj8)%6&TZYDhTYM+h4fsF5X^0l8sg;lx5TA*^AO zjw{i8Q48avd0Vn9HEC0zeqQ&=xJ5?e1|2uzrrDjWbW6*szGhTz^ROVC@F6M? zJA3-A((@mei-*bwPn6zx^W1@h3_cyWp~=-ZY#bPs8ym{+d{+AUfSY4Dsw#Jjj$1+Y z`pA$}XWEnk%PZY*cs@ls#gOZLxRYg|CqG(omxeYSTa|IuSh#S3T5@$n!P<3n;2vV+ zV+A9V?-(?#oNP7n3^ebycKFaq?p9~o($W&-ICksk!8WpG#2A*XmYFo{?P+_6cX`_| z?Yuzae07>TKx*H;I^t@gJ;{ufmm2P`bivtl-%!rlYYb$#(LQX~@c{Y-78|KlypYZ2 zWIiubGmO00f{atO>Q#jA)$owO8fPffd2qiuY+8HGb`E*lzNF0BY0Fd$dql^h4#Q-Q z6Z{|sbR<<>{T!4oMl%Lw5CYvAc2&~kbawPiJMlweAjq?-TVPSVtvAuu`$&6FyiXuF zcl+E)rDh2uQ(z-1Cfj;?X(Bon0P9Y%hpkqM+JK@(cVT2e=K54ks2DNS$gF7EIoEqQ zLH}{vNDg-ySqHD7z^vzia)WWJkW0#~Y2`IAb0W5=l94l8EX&T@IV0O8?IA0b_u+AF zX`0=ow_YoaAC5K%AD$$g%11sbfBAO#rPG{%Gp8oX?~gmGrv=uZIraLq)wu)jotZpV zK2qek{C4=mbBB+fJ#m7_fENe+IEZI7Jgcbo9POOeISo#md#qs@ary1CZ>y2Ct=y=< zyVbm#0=gNu8)hma^Xoe;Yq*e&%`I2e%0-5Kqggo*yt<0+8z?q)q*o`|YGg8Tz7Apn zdWUJsTx-T4XY(3f6xc9lPxGa!;U$5!{}1KM&)pX;$3l`1{9lDMc9W~a&eI}3XD~AT zY!bnlgO^u}h&7ag(z9s{;tSpgWO-F;z)z=-A;)_{Yd)cwMnJ;^wEp0trRz?OVR7dK z>iVxNB3L_y6t{TLc zSc^S;(~sf`?8kb%iVe)Ckr@S;M z#+P=UQ9s3s3k>4Xz|Sd7L!E{Yf1pv5hStAOf47Fd3uKIU7KFz z!0){(C$3gI58;|?>VweD!c}>w+M?k>p7t_;1>CzDguU2Dxt!1Y$>34v`h;_R3eQvH M=jsbMgqIQe5AbW;Pyhe` literal 0 HcmV?d00001 diff --git a/src/main/java/com/example/echo/EchoServer.class b/src/main/java/com/example/echo/EchoServer.class new file mode 100644 index 0000000000000000000000000000000000000000..74eab282b2b2559f0d22a60fb775e4607b6d2a64 GIT binary patch literal 5548 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 V`~Pdb{Xobg$Oj=ti98~_;C~ZkT`>Rv literal 0 HcmV?d00001 diff --git a/src/main/java/com/example/echo/EchoServer.java b/src/main/java/com/example/echo/EchoServer.java index f55f684..b303a27 100644 --- a/src/main/java/com/example/echo/EchoServer.java +++ b/src/main/java/com/example/echo/EchoServer.java @@ -2,13 +2,14 @@ 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服务器 + * 这个服务器可以同时处理多个客户端连接 */ public class EchoServer { private static final int PORT = 8888; @@ -17,7 +18,8 @@ public class EchoServer { private ExecutorService executor; public EchoServer() { - executor = Executors.newSingleThreadExecutor(); + // 使用固定线程池来处理多个客户端连接 + executor = Executors.newFixedThreadPool(10); } /** @@ -28,12 +30,21 @@ public class EchoServer { running = true; System.out.println("Echo Server已启动,监听端口: " + PORT); - // 等待客户端连接 - System.out.println("等待客户端连接..."); - Socket clientSocket = serverSocket.accept(); - System.out.println("客户端已连接: " + clientSocket.getRemoteSocketAddress()); - - handleClient(clientSocket); + // 持续等待客户端连接 + while (running) { + try { + System.out.println("等待客户端连接..."); + Socket clientSocket = serverSocket.accept(); + System.out.println("客户端已连接: " + clientSocket.getRemoteSocketAddress()); + + // 为每个客户端创建一个新的任务 + executor.submit(() -> handleClient(clientSocket)); + } catch (IOException e) { + if (running) { + System.err.println("接受客户端连接时发生错误: " + e.getMessage()); + } + } + } } /** @@ -44,27 +55,35 @@ public class EchoServer { PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) { String inputLine; - System.out.println("开始与客户端通信..."); + System.out.println("开始与客户端 " + clientSocket.getRemoteSocketAddress() + " 通信..."); while ((inputLine = in.readLine()) != null) { if ("bye".equalsIgnoreCase(inputLine.trim())) { - System.out.println("客户端请求断开连接"); + System.out.println("客户端 " + clientSocket.getRemoteSocketAddress() + " 请求断开连接"); break; } - System.out.println("收到消息: " + inputLine); - // 将收到的消息回显给客户端 - out.println("Echo: " + inputLine); + System.out.println("收到来自客户端 " + clientSocket.getRemoteSocketAddress() + " 的消息: " + inputLine); + + // 检查是否是TIME命令 + if ("TIME".equalsIgnoreCase(inputLine.trim())) { + // 返回当前系统时间 + String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + out.println("当前系统时间: " + currentTime); + } else { + // 将收到的消息回显给客户端 + out.println("Echo: " + inputLine); + } } } catch (IOException e) { - System.err.println("处理客户端连接时发生错误: " + e.getMessage()); + System.err.println("处理客户端 " + clientSocket.getRemoteSocketAddress() + " 连接时发生错误: " + e.getMessage()); } finally { try { clientSocket.close(); - System.out.println("客户端连接已关闭"); + System.out.println("客户端 " + clientSocket.getRemoteSocketAddress() + " 连接已关闭"); } catch (IOException e) { - System.err.println("关闭客户端连接时发生错误: " + e.getMessage()); + System.err.println("关闭客户端 " + clientSocket.getRemoteSocketAddress() + " 连接时发生错误: " + e.getMessage()); } } } @@ -78,28 +97,23 @@ public class EchoServer { serverSocket.close(); } executor.shutdown(); - try { - if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - executor.shutdownNow(); - } - } catch (InterruptedException e) { - executor.shutdownNow(); - } System.out.println("服务器已停止"); } public static void main(String[] args) { EchoServer server = new EchoServer(); - try { - server.start(); - } catch (IOException e) { - System.err.println("启动服务器时发生错误: " + e.getMessage()); - } finally { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { server.stop(); } catch (IOException e) { System.err.println("停止服务器时发生错误: " + e.getMessage()); } + })); + + try { + server.start(); + } catch (IOException e) { + System.err.println("启动服务器时发生错误: " + e.getMessage()); } } } \ No newline at end of file -- Gitee