如何让 Java 服务器持续监听客户端连接而不退出

java 简单 socket 服务器默认在单次客户端会话结束后即终止;要实现长期运行,需将 accept() 和 i/o 处理逻辑置于无限循环中,并妥善处理异常与资源释放。

这是一个典型的“单连接 vs 持续服务”设计问题。你当前的 Java 服务器代码使用了 try-with-resources 语句,它会在 clientSocket 接收并处理完一次请求(readLine() 返回 null,表示输入流关闭)后自动关闭所有资源——包括 ServerSocket。因此,整个 try 块执行完毕,程序自然退出。

要让服务器持续运行、反复接受新连接,核心修改是:将 accept() 及其后续处理封装进一个外层 while (true) 循环中,并确保每次连接都在独立作用域内完成资源管理,避免因单个客户端异常导致整个服务器崩溃。

以下是改进后的 Java 服务器代码(关键改动已标注):

public class EchoServer {
    public static void main(String[] args) {
        int portNumber = 4444;
        if (args.length > 0) {
            try {
                portNumber = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                System.err.println("Invalid port: " + args[0]);
                System.exit(1);
            }
        }

        // 外层无限循环:持续等待新连接
        while (true) {
            ServerSocket serverSocket = null;
            Socket clientSocket = null;
            try {
                serverSocket = new ServerSocket(portNumber);
                System.out.println("Server listening on port " + portNumber);

                // 阻塞等待客户端连接(每次循环只 accept 一个)
                clientSocket = serverSocket.accept();
                System.out.println("Client connected: " + client

Socket.getRemoteSocketAddress()); // 为每个连接单独创建 I/O 流(不放在 try-with-resources 外层,避免关闭 serverSocket) PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); String inputLine; // 读取客户端发送的单行(注意:Go 客户端未发送换行符!见下方说明) if ((inputLine = in.readLine()) != null) { System.out.println("Received: " + inputLine); out.println("Echo: " + inputLine); // 响应带前缀便于验证 } else { System.out.println("Client closed connection immediately."); } } catch (IOException e) { System.err.println("I/O error handling client: " + e.getMessage()); // 不中断循环,继续监听下一个连接 } finally { // 安全关闭当前连接资源(clientSocket 和 streams),但保留 serverSocket 活跃 try { if (clientSocket != null && !clientSocket.isClosed()) { clientSocket.close(); } if (serverSocket != null && !serverSocket.isClosed()) { // 注意:此处不关闭 serverSocket,否则循环失效 // serverSocket.close(); // ❌ 错误:移除此行 } } catch (IOException e) { System.err.println("Error closing resources: " + e.getMessage()); } } } // 注意:此行永不执行(死循环),如需优雅退出可添加 shutdown hook 或信号处理 } }

⚠️ 重要注意事项

  • Go 客户端需发送换行符:你当前的 Go 代码 conn.Write([]byte("hello world")) 发送的是无 \n 的原始字节,而 Java 的 BufferedReader.readLine() 会一直阻塞直到遇到行结束符(\n, \r\n)或流关闭。因此,服务器实际会卡在 readLine()。修复方式是在 Go 中发送带换行的消息:

    conn.Write([]byte("hello world\n")) // ✅ 添加 \n

    或更推荐使用 fmt.Fprintln:

    fmt.Fprintln(conn, "hello world") // 自动追加 \n 并 flush
  • 并发支持(进阶):上述代码为 迭代式服务器(Iterative Server),一次只服务一个客户端。若需同时处理多个客户端,应为每个 clientSocket 启动一个新线程或使用 ExecutorService:

    ExecutorService pool = Executors.newCachedThreadPool();
    while (true) {
        Socket client = serverSocket.accept();
        pool.submit(() -> handleClient(client)); // 将 I/O 逻辑抽离为方法
    }
  • 资源泄漏防护:务必在 finally 块中关闭 clientSocket,但绝不能关闭 serverSocket —— 它是整个服务的入口,关闭即终止监听。

总结:让 Java Socket 服务器持续运行的本质,是分离“监听套接字”(长期存活)与“服务套接字”(按需创建/销毁),并通过外层循环维持监听生命周期。配合正确的协议约定(如换行符),即可构建稳定的基础 TCP 服务。