多线程聊天客户端运行时触发java.net.SocketException: Socket Closed错误排查求助
问题分析与解决方案
首先,你的问题根源出在客户端的Socket被提前关闭了!
看一下Peer类的构造函数:
try (Socket socket = new Socket(hostname, port)){ // 创建线程并启动 Thread t1 = new Thread(new SendThread(writer)); Thread t2 = new Thread(new ReceiveThread(reader)); t1.start(); t2.start(); } // 这里try-with-resources会自动关闭Socket!
Java的try-with-resources语法会在代码块执行完毕后,自动调用资源的close()方法。当你启动两个线程后,构造函数里的try块就结束了,Socket被立刻关闭,这时候SendThread和ReceiveThread还在尝试通过这个已关闭的Socket读写数据,自然就会抛出Socket closed异常。
修复方案
1. 调整客户端Socket的生命周期
把Socket的声明移出try-with-resources,改为手动管理Socket的生命周期,确保两个线程运行期间Socket保持打开状态。修改后的Peer类构造函数如下:
public Peer(String hostname, int port){ this.hostname = hostname; this.port = port; Socket socket = null; try { socket = new Socket(hostname, port); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); Thread t1 = new Thread(new SendThread(writer, socket)); // 传递Socket引用,方便后续主动关闭 Thread t2 = new Thread(new ReceiveThread(reader, socket)); t1.start(); t2.start(); } catch (UnknownHostException e) { e.printStackTrace(); try { if(socket != null) socket.close(); } catch (IOException ex) { ex.printStackTrace(); } System.exit(-1); } catch (IOException e) { e.printStackTrace(); try { if(socket != null) socket.close(); } catch (IOException ex) { ex.printStackTrace(); } System.exit(-1); } }
2. 优化线程的关闭逻辑
给SendThread和ReceiveThread添加Socket引用,支持用户主动退出时关闭资源,同时处理服务器断开的情况:
修改SendThread:
public class SendThread implements Runnable{ private BufferedWriter writer; private Socket socket; public SendThread(BufferedWriter writer, Socket socket){ this.writer = writer; this.socket = socket; } @Override public void run() { Scanner sc = new Scanner(System.in); while (true) { System.out.print("Enter a String (type 'exit' to quit): "); String str = sc.nextLine(); if ("exit".equals(str)) { // 用户主动退出时关闭所有资源 try { writer.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } System.exit(0); } if (str != null){ try { this.writer.write(str); this.writer.newLine(); this.writer.flush(); } catch (IOException e) { e.printStackTrace(); break; } } } } }
修改ReceiveThread:
public class ReceiveThread implements Runnable{ private BufferedReader reader; private Socket socket; public ReceiveThread(BufferedReader reader, Socket socket){ this.reader = reader; this.socket = socket; } @Override public void run() { while (true){ String res = null; try { res = this.reader.readLine(); if (res != null){ System.out.println("Server response: "+ res); } else { // 服务器断开连接时,关闭本地Socket socket.close(); break; } } catch (IOException e) { // 忽略Socket主动关闭的异常,直接结束线程 if (!(e instanceof SocketException && "Socket closed".equals(e.getMessage()))) { e.printStackTrace(); System.exit(-1); } break; } } } }
3. 修复服务器端的广播问题
服务器的broadcast方法每次都用try-with-resources创建BufferedWriter,会导致客户端的输出流被关闭,后续无法接收消息,需要调整:
public synchronized void broadcast(String message) throws IOException { Iterator<Socket> iterator = clients.iterator(); while (iterator.hasNext()) { Socket client = iterator.next(); if (client.isClosed()) { iterator.remove(); // 移除已断开的客户端 continue; } BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); writer.write(message); writer.newLine(); writer.flush(); // 不要关闭writer,否则客户端的输出流会被永久关闭 } }
这样修改后,客户端的Socket不会被提前关闭,也能正常接收服务器的广播消息了。
内容的提问来源于stack exchange,提问作者Yige Song
相关产品推荐
相关产品推荐

