Java Socket服务端读取输入阻塞无法正确输出客户端内容问题
Java Socket服务端输出异常问题排查
问题现象
服务端预期效果为控制台输出客户端发送的输入内容,实际运行时程序阻塞在len = in.read(bytes);语句处,无法执行集合清空逻辑,每次输出的内容存在偏差、不准确。
原实现代码
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class Server { public static void main(String[] args) { try { ServerSocket sc = new ServerSocket(8000); while(true) { Socket socket = sc.accept(); InputStream in = socket.getInputStream(); int len = 0; String str = null; byte[] bytes = new byte[8]; List<Byte> byteL = new ArrayList<>(); do { len =in.read(bytes); for (int i = 0;i < len;i++) { byteL.add(bytes[i]); } byte[] byteLs = new byte[byteL.size()]; for(int j = 0;j < byteL.size();j++) { byteLs[j] = byteL.get(j); } str = new String(byteLs,0,byteLs.length); System.out.println(str); if (len == -1) { byteL.clear(); str = null; } } while (len != -1); } } catch (IOException e) { e.printStackTrace(); } } }
根因分析
InputStream.read()本身是阻塞方法:TCP是面向流的协议,只要客户端保持连接不主动断开、也没有新数据发送,read()方法会一直挂起等待,不会返回-1。原代码把集合清空逻辑放在len == -1的分支里,长连接场景下根本触发不了这个条件,缓冲区会一直累积历史数据。- 消息边界处理逻辑缺失:原代码设置的缓冲区只有8字节,只要读到数据就立刻把当前累计的所有字节转成字符串打印,既没有判断单次发送的消息是否接收完整,也没有在单条消息处理完成后清空缓冲区,只要消息长度超过8字节就会被拆成多段重复输出,内容自然不准确。
- 资源未正常释放:原代码接收到Socket连接、处理完数据后从未关闭Socket和对应的输入流,长时间运行会耗尽系统文件句柄,导致新连接无法接入。
修复方案
TCP流本身没有天然的消息分界,必须在业务层自定义消息边界规则,常见的实现有两种:一是约定固定分隔符(比如换行符\n作为单条消息的结束标记),二是在消息头部固定位置传递消息体总长度,读取到对应长度的字节后再完成解析。
以下是基于换行符分隔的可运行修复版本,适合普通文本消息传输场景,使用try-with-resources语法自动释放资源,不会出现阻塞和内容拼接错误:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; public class Server { public static void main(String[] args) { try (ServerSocket serverSocket = new ServerSocket(8000)) { while (true) { // 接受客户端连接,自动管理连接和输入流的资源释放 try (Socket socket = serverSocket.accept(); BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) { String message; // 逐行读取单条消息,读到null代表客户端主动断开连接 while ((message = reader.readLine()) != null) { System.out.println("收到客户端消息:" + message); } } catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } }
如果业务场景传输的是二进制内容、不适合用分隔符,把边界判断逻辑替换为「先读固定长度的消息头获取内容总长度,再读取对应长度的字节解析」即可,注意单条消息解析完成后立刻清空对应缓冲区,不要等连接断开再做清理。
内容的提问来源于stack exchange,提问作者刘凯远
相关产品推荐
相关产品推荐

