You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java TCP客户端-服务器双向通信陷入无限循环排查求助

Java TCP双向通信无限循环/服务器卡ss.accept()问题排查与解决

嘿,我来帮你搞定这个头疼的问题!从你描述的情况来看,原本单向通信正常,加了双向回复后就陷入死循环,服务器还卡在ss.accept(),大概率是两个核心原因导致的:

问题根源扒一扒

  1. 单线程忙不过来:你的服务器主线程可能既在做ServerSocket.accept()监听新连接,又要处理已连接客户端的读写操作。一旦开始处理某个客户端的双向通信,主线程就被读写循环卡住了,根本没法回到ss.accept()继续等新连接,同时客户端那边如果也在等服务器回复,两边就互相耗着了。
  2. 读写顺序搞反了:可能客户端和服务器的代码都先跑去执行read()操作(等着对方发消息),谁都不先主动发第一条数据,结果就是两边都卡在“等消息”的状态,直接死锁。

解决方案:分线程干活 + 明确读写逻辑

1. 服务器端:给每个客户端单独开线程

服务器的正确姿势是:主线程只专心监听新连接,每收到一个客户端连接,就启动一个独立线程去处理这个客户端的双向通信。这样主线程不会被阻塞,能继续接新连接,每个客户端的通信也互不干扰。

修改后的服务器代码示例:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8888)) {
            System.out.println("服务器启动,蹲守客户端连接中...");
            
            // 主线程循环蹲守新连接
            while (true) {
                Socket clientSocket = serverSocket.accept(); // 现在不会被长期卡着了,因为处理逻辑在子线程
                System.out.println("新客户端上线:" + clientSocket.getInetAddress());
                
                // 开新线程伺候这个客户端
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 专门处理单个客户端的类,实现Runnable接口
    static class ClientHandler implements Runnable {
        private final Socket clientSocket;

        public ClientHandler(Socket socket) {
            this.clientSocket = socket;
        }

        @Override
        public void run() {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                 OutputStreamWriter out = new OutputStreamWriter(clientSocket.getOutputStream())) {
                
                String clientMsg;
                // 循环读客户端消息,直到连接断开
                while ((clientMsg = in.readLine()) != null) {
                    System.out.println("收到客户端消息:" + clientMsg);
                    
                    // 给客户端回消息
                    String response = "服务器已查收:" + clientMsg;
                    out.write(response + "\n"); // 必须加换行!因为客户端用readLine()读
                    out.flush(); // 一定要刷缓冲区,不然消息会憋在里面发不出去
                    
                    // 收到exit就断开
                    if ("exit".equalsIgnoreCase(clientMsg)) {
                        break;
                    }
                }
                System.out.println("客户端下线:" + clientSocket.getInetAddress());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2. 客户端:分开处理发送和接收

客户端这边要避免主线程被读取服务器回复的操作卡住,最好单独开一个线程来监听服务器的回复,主线程专心负责输入消息发送给服务器,这样就不会出现两边都等的情况。

修改后的客户端代码示例:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 8888);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
             Scanner scanner = new Scanner(System.in)) {
            
            System.out.println("成功连接服务器!");
            
            // 开个线程专门读服务器的回复,别让主线程被卡
            new Thread(() -> {
                try {
                    String serverResponse;
                    while ((serverResponse = in.readLine()) != null) {
                        System.out.println("服务器回复:" + serverResponse);
                    }
                } catch (IOException e) {
                    System.out.println("和服务器断开连接啦");
                }
            }).start();
            
            // 主线程负责发消息给服务器
            String input;
            while (true) {
                System.out.print("请输入要发送的消息(输入exit退出):");
                input = scanner.nextLine();
                out.write(input + "\n");
                out.flush();
                
                if ("exit".equalsIgnoreCase(input)) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

几个必须注意的细节

  • 刷缓冲区!刷缓冲区!刷缓冲区!:用write()发消息后一定要调用flush(),不然数据会留在输出缓冲区里,对方根本收不到,还以为没发。
  • 换行符要配对:如果用readLine()读取数据,发送消息的时候必须在末尾加\n,不然readLine()会一直等换行符,直接阻塞住。
  • 资源要关好:用try-with-resources语法自动关闭Socket、流这些资源,避免内存泄漏。
  • 线程要分开:服务器的监听和通信要分离,客户端的发送和接收要分离,单线程很容易出阻塞问题。

内容的提问来源于stack exchange,提问作者Le Svr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:05:31