Java Socket聊天应用出现连接中断异常,寻求技术解决方案
问题根源及解决方案
核心问题
你的服务器端代码在客户端连接后,发送完ID就直接关闭了DataOutputStream,而Java中关闭Socket的输入/输出流会直接关闭整个Socket连接。这就导致客户端拿到ID后,底层的Socket已经被服务器断开,后续调用SendMessage时,操作的是一个已经失效的连接,所以首次发送无响应,再次发送就会抛出连接中止的异常。另外dout.flush()放在dout.close()之后完全无效,因为close操作会自动完成flush并释放资源。
另外还有两个小问题:
- 创建
Client实例后没有将其加入Clients列表,无法管理已连接的客户端 - 服务器端的UI操作在主线程中执行,同时又阻塞在
ss.accept()循环,可能导致UI卡顿(不过这不是当前报错的直接原因)
修正后的服务器端关键代码
public ServerSocket ss; public ArrayList<Client> Clients = new ArrayList<Client>(); String Alphabets = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789"; public Server() throws IOException { ss = new ServerSocket(1111); // UI初始化代码保持不变 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setLayout(null); this.setResizable(false); this.setSize(400,150); this.getContentPane().setBackground(Color.BLACK); this.setTitle("Server"); JLabel label = new JLabel("Server Running"); label.setForeground(Color.WHITE); label.setBounds(15,25,350,60); label.setFont(new Font(Font.SANS_SERIF,Font.PLAIN,50)); this.add(label); this.setVisible(true); // 把accept循环放到单独线程,避免阻塞UI new Thread(() -> { while(true) { try { Socket socket = ss.accept(); // 不要在这里关闭流,把流交给Client管理 DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); String id = getId(); dout.writeUTF(id); dout.flush(); // 在这里flush确保ID发送到客户端 Client c = new Client(socket, id); Clients.add(c); // 将客户端加入管理列表 // 启动Client的线程来处理后续通信 new Thread(c).start(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } // getId方法保持不变 String getId() { Random rand = new Random(); String id = ""; for(int i = 0;i < 6;i++) { char i3 = Alphabets.charAt(rand.nextInt(0,Alphabets.length())); id += i3; } return id; }
Client类的基本实现参考
Client类需要实现Runnable,负责处理和客户端的持续通信:
public class Client implements Runnable { private Socket socket; private String id; private DataInputStream din; private DataOutputStream dout; public Client(Socket socket, String id) throws IOException { this.socket = socket; this.id = id; this.din = new DataInputStream(socket.getInputStream()); this.dout = new DataOutputStream(socket.getOutputStream()); } @Override public void run() { try { String msg; // 持续读取客户端发送的消息 while ((msg = din.readUTF()) != null) { // 这里可以处理消息,比如广播给其他客户端 System.out.println(id + "发送消息:" + msg); broadcastMessage(id + ": " + msg); } } catch (IOException e) { // 客户端断开连接时移除 Server.Clients.remove(this); try { // 关闭资源 din.close(); dout.close(); socket.close(); } catch (IOException ex) { ex.printStackTrace(); } } } // 广播消息给所有客户端的方法 private void broadcastMessage(String msg) throws IOException { for (Client client : Server.Clients) { client.dout.writeUTF(msg); client.dout.flush(); } } }
客户端代码注意事项
确保客户端的SendMessage方法使用的是连接时创建的mySocket对象,不要提前关闭它:
// 确保mySocket是类的成员变量,保持引用 private Socket mySocket; private String Id; public String Connect() throws UnknownHostException, IOException { mySocket = new Socket("localhost",1111); DataInputStream din = new DataInputStream(mySocket.getInputStream()); Id = din.readUTF(); // 不要关闭din,后续通信还需要 return Id; } public void SendMessage(String msg) throws IOException { // 使用保持连接的mySocket创建输出流,或者复用一个全局的输出流 DataOutputStream dout = new DataOutputStream(mySocket.getOutputStream()); dout.writeUTF(msg); dout.flush(); // 确保消息立即发送 AddToMessages(Id + ": " + msg); }
总结
- 服务器端不能在发送初始ID后关闭Socket流,必须保持连接打开以支持后续通信
- 客户端要保持Socket对象的引用,不要提前关闭输入/输出流
- 服务器端的客户端管理逻辑要完善,将新连接的Client加入列表
- 网络通信逻辑要放到单独线程,避免阻塞UI线程
内容的提问来源于stack exchange,提问作者Iyad
相关产品推荐
相关产品推荐

