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

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);
}

总结

  1. 服务器端不能在发送初始ID后关闭Socket流,必须保持连接打开以支持后续通信
  2. 客户端要保持Socket对象的引用,不要提前关闭输入/输出流
  3. 服务器端的客户端管理逻辑要完善,将新连接的Client加入列表
  4. 网络通信逻辑要放到单独线程,避免阻塞UI线程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 23:31:06