Java C/S编程开启PrintWriter自动刷新后首条消息无响应求助
Java Socket通信首条消息收不到响应问题排查
问题现象
使用Java编写客户端/服务端通信程序时,创建PrintWriter实例已传入true开启autoFlush自动刷新机制,但存在异常:客户端向服务端发送第一条消息时,无法收到服务端返回的响应,首条消息发送完成后,后续所有消息的收发交互均正常。
问题复现代码
客户端代码
public class Client1 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { Socket socket = new Socket("localhost",5000); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter output = new PrintWriter(socket.getOutputStream(), true); Scanner scanner = new Scanner(System.in); while (true) { if (socket.getInputStream().available() != 0) { String in = input.readLine(); System.out.println(in); } System.out.println("Enter your message to the server: "); String out = scanner.nextLine(); output.println(out); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } }
服务端代码
public class Main { public static void main(String[] args) throws IOException { new Thread(new Runnable() { @Override public void run() { try { ServerSocket serverSocket1 = new ServerSocket(5000); Socket socket1 = serverSocket1.accept(); System.out.println("Accepted"); BufferedReader input = new BufferedReader(new InputStreamReader(socket1.getInputStream())); PrintWriter output = new PrintWriter(socket1.getOutputStream(), true); while (true) { if (socket1.getInputStream().available() != 0) { String text = input.readLine(); System.out.println("got it!"); output.println("We got your message!"); } } } catch (IOException e) { e.printStackTrace(); } } }).start(); } }
问题根源
核心错误是错误使用InputStream.available()作为消息可读的判断依据,本质是对TCP流特性和方法语义理解偏差:
available()的官方语义是返回当前输入流可以无阻塞读取的字节数,这个值受操作系统内核缓冲区拷贝进度、TCP分段到达节奏、线程调度延迟影响极大,完全不能用来判断“是否有完整的待处理消息”。- 首条消息丢失响应的时序逻辑:
- 客户端连接建立后进入第一轮循环,此时服务端未返回任何数据,
available()返回0,跳过读逻辑,直接阻塞等待用户输入第一条消息。 - 用户输入第一条消息发送给服务端后,客户端立刻进入下一轮循环。此时服务端接收消息、处理、返回响应需要经过内核协议栈处理、线程调度,存在微秒到毫秒级的延迟,客户端执行
available()判断时,服务端的响应数据还没到达客户端的内核接收缓冲区,available()返回0,再次跳过读逻辑,阻塞等待用户输入第二条消息。 - 等用户输入第二条消息时,服务端返回的第一条响应早已存入客户端缓冲区,因此第二条消息发送后,下一轮循环就能读到积压的第一条响应。后续消息收发节奏匹配后,数据到达时机刚好能被
available()检测到,看起来交互恢复正常。
- 客户端连接建立后进入第一轮循环,此时服务端未返回任何数据,
- 额外隐患:服务端也使用了相同的
available()判断逻辑,同样存在漏读消息的风险,当前场景下只是因为客户端先发消息,服务端accept后空转循环刚好撞上第一条消息到达才没有触发异常,写法本身完全不可靠。 - 原有代码还存在读写逻辑耦合的问题:读消息和等待用户输入在同一个循环线程中,
scanner.nextLine()阻塞等待用户输入时,就算服务端有消息到达也无法及时读取。
修复方案
- 彻底删除所有
available()判断逻辑,BufferedReader.readLine()本身是阻塞方法,当流中有完整行数据时才会返回,没有数据时会阻塞等待,不会空转消耗CPU,也不会漏读数据。 - 将读消息逻辑和用户输入发送逻辑拆分为两个独立线程,避免阻塞互相影响。
修复后核心代码示例
客户端修复逻辑
Socket socket = new Socket("localhost",5000); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter output = new PrintWriter(socket.getOutputStream(), true); Scanner scanner = new Scanner(System.in); // 独立线程专门处理服务端响应读取,和用户输入逻辑解耦 new Thread(() -> { try { String inMsg; // 连接未断开时循环阻塞读,无需额外判断 while ((inMsg = input.readLine()) != null) { System.out.println(inMsg); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 主线程专门处理用户输入、消息发送 while (true) { System.out.println("Enter your message to the server: "); String out = scanner.nextLine(); output.println(out); }
服务端修复逻辑
ServerSocket serverSocket1 = new ServerSocket(5000); Socket socket1 = serverSocket1.accept(); System.out.println("Accepted"); BufferedReader input = new BufferedReader(new InputStreamReader(socket1.getInputStream())); PrintWriter output = new PrintWriter(socket1.getOutputStream(), true); while (true) { // 直接阻塞读,删除available判断 String text = input.readLine(); // readLine返回null表示对端断开连接,退出循环 if (text == null) { break; } System.out.println("got it!"); output.println("We got your message!"); }
内容的提问来源于stack exchange,提问作者Knddk
相关产品推荐
相关产品推荐

