Java Socket聊天客户端输入'exit'无法正常关闭问题求助
Java Socket聊天应用exit无法立即退出问题解决
我用Java的Socket和ServerSocket开发简易聊天应用,客户端分两个线程分别处理消息接收和发送。但输入'exit'时,应用不会立即关闭,必须等收到其他客户端消息后才退出,求解决。
客户端代码(Client.java)
import java.io.*; import java.net.Socket; import java.net.ConnectException; import java.util.Scanner; public class Client { private Socket clientSocket; private BufferedReader serverReader; private boolean stopReceiving; private boolean stopSending; public static void main(String[] args) { Client client = new Client(); } public Client() { try { this.clientSocket = new Socket("localhost", 8080); System.out.println("Spuštění klienta proběhlo úspěšně."); this.serverReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); this.stopReceiving = false; this.stopSending = false; Thread receiveThread = new Thread(new Runnable() { public void run() { try { while (!stopReceiving) { String message = serverReader.readLine(); if (message == null) { // Vypnutí připojení k serveru stopReceiving = true; closeClient(); System.exit(0); // Vypnutí aplikace } if (message.equals("Připojení odmítnuto - dosaženo maximálního počtu klientů. Zkuste se připojit později.")) { closeClient(); System.exit(0); // Vypnutí aplikace } System.out.println("Zpráva od serveru: " + message); } } catch (IOException e) { e.printStackTrace(); } } }); receiveThread.start(); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(this.clientSocket.getOutputStream())); // vytvoření BufferedWriter Scanner in = new Scanner(System.in); while (!stopSending) { String message = in.nextLine(); if (message.equals("exit")) { System.out.println("REACHED EXIT"); serverReader.close(); clientSocket.close(); in.close(); out.close(); System.out.println("EXITED"); System.exit(0); break; }else{ out.write(message + "\r\n"); out.flush(); System.out.println("Zpráva \"" + message + "\" byla odeslána."); } } } catch (ConnectException e) { System.out.println("Nepodařilo se připojit k serveru. Klient se ukončuje."); } catch (IOException e) { e.printStackTrace(); } finally { closeClient(); } } private void closeClient() { try { if (serverReader != null) serverReader.close(); } catch (IOException e) { e.printStackTrace(); } try { if (clientSocket != null) clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
服务端代码(Server.java)
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.Iterator; public class Server { private ServerSocket serverSocket; private ArrayList<ClientHandler> clients; private static final int MAX_CLIENTS = 2; // Maximální počet klientů public static void main(String[] args) { Server server = new Server(); } public Server() { try { this.serverSocket = new ServerSocket(8080); System.out.println("Spuštění serveru proběhlo úspěšně.\nČekám na připojení klienta...\n"); this.clients = new ArrayList<>(); clients(); } catch (IOException e) { e.printStackTrace(); } } private void clients() { Thread acceptThread = new Thread(new Runnable() { public void run() { while (true) { try { Socket clientSocket = serverSocket.accept(); PrintWriter writer = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true); if (clients.size() < MAX_CLIENTS) { // Check if the maximum number of clients is reached ClientHandler clientHandler = new ClientHandler(clientSocket, writer); synchronized (clients) { clients.add(clientHandler); } System.out.println("Klient " + clientHandler.getAddress() + " se připojil."); } else { writer.println("\"Připojení odmítnuto - dosaženo maximálního počtu klientů. Zkuste se připojit později.\""); writer.flush(); clientSocket.close(); System.out.println("Připojení odmítnuto - dosaženo maximálního počtu klientů."); } } catch (IOException e) { e.printStackTrace(); } } } }); acceptThread.start(); while (true) { synchronized (clients) { Iterator<ClientHandler> iterator = clients.iterator(); while (iterator.hasNext()) { ClientHandler clientHandler = iterator.next(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(clientHandler.getSocket().getInputStream())); if (reader.ready()) { String message = reader.readLine(); System.out.println("Přijata zpráva od klienta " + clientHandler.getAddress() + " : " + message); broadcastMessage(message); } } catch (IOException e) { e.printStackTrace(); iterator.remove(); // Bezpečně odstraní klienta z iterátoru } } } } } private void broadcastMessage(String message) { synchronized (clients) { for (ClientHandler clientHandler : clients) { clientHandler.getWriter().println(message); clientHandler.getWriter().flush(); } } } private static class ClientHandler { private final Socket socket; private final PrintWriter writer; public ClientHandler(Socket socket, PrintWriter writer) { this.socket = socket; this.writer = writer; } public Socket getSocket() { return this.socket; } public PrintWriter getWriter() { return this.writer; } public String getAddress() { return this.socket.getInetAddress().toString(); } } }
问题原因分析
- 客户端接收线程阻塞:
serverReader.readLine()是阻塞方法,客户端输入exit关闭socket后,接收线程仍卡在读取操作上,只有当服务器向该socket写入数据(如其他客户端消息广播)时,才会触发IO异常或返回null,进而退出线程。 - 资源关闭顺序错误:客户端先关闭
serverReader再关闭socket,可能导致socket关闭不彻底;且未设置标志通知接收线程主动退出。 - 服务端读取机制低效:服务端用单线程循环检查所有客户端的
reader.ready(),无法及时检测到客户端断开,导致客户端退出后服务器未及时清理连接。
解决方案
修改后的客户端代码(Client.java)
import java.io.*; import java.net.Socket; import java.net.ConnectException; import java.util.Scanner; public class Client { private Socket clientSocket; private BufferedReader serverReader; private BufferedWriter serverWriter; private volatile boolean stopReceiving; private volatile boolean stopSending; private Thread receiveThread; public static void main(String[] args) { new Client(); } public Client() { try { this.clientSocket = new Socket("localhost", 8080); System.out.println("客户端启动成功。"); this.serverReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); this.serverWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); this.stopReceiving = false; this.stopSending = false; receiveThread = new Thread(() -> { try { while (!stopReceiving) { String message = serverReader.readLine(); if (message == null) { break; } if (message.equals("Připojení odmítnuto - dosaženo maximálního počtu klientů. Zkuste se připojit později.")) { closeClient(); System.exit(0); } System.out.println("服务器消息: " + message); } } catch (IOException e) { // 客户端主动关闭socket时触发的异常,无需打印 } finally { closeClient(); System.exit(0); } }); receiveThread.start(); Scanner in = new Scanner(System.in); while (!stopSending) { String message = in.nextLine(); if (message.equals("exit")) { System.out.println("准备退出..."); stopReceiving = true; stopSending = true; // 中断接收线程的阻塞读取 receiveThread.interrupt(); // 先关闭输出流告知服务器,再关闭其他资源 serverWriter.close(); serverReader.close(); clientSocket.close(); in.close(); System.out.println("已退出"); break; } else { serverWriter.write(message + "\r\n"); serverWriter.flush(); System.out.println("消息\"" + message + "\"已发送。"); } } } catch (ConnectException e) { System.out.println("无法连接到服务器,客户端即将退出。"); } catch (IOException e) { e.printStackTrace(); } finally { closeClient(); } } private void closeClient() { try { if (serverWriter != null) serverWriter.close(); } catch (IOException e) { e.printStackTrace(); } try { if (serverReader != null) serverReader.close(); } catch (IOException e) { e.printStackTrace(); } try { if (clientSocket != null && !clientSocket.isClosed()) clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
修改后的服务端代码(Server.java)
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.CopyOnWriteArrayList; public class Server { private ServerSocket serverSocket; private CopyOnWriteArrayList<ClientHandler> clients; private static final int MAX_CLIENTS = 2; // 最大客户端数量 public static void main(String[] args) { new Server(); } public Server() { try { this.serverSocket = new ServerSocket(8080); System.out.println("服务器启动成功。\n等待客户端连接...\n"); this.clients = new CopyOnWriteArrayList<>(); acceptClients(); } catch (IOException e) { e.printStackTrace(); } } private void acceptClients() { new Thread(() -> { while (true) { try { Socket clientSocket = serverSocket.accept(); PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true); if (clients.size() < MAX_CLIENTS) { ClientHandler clientHandler = new ClientHandler(clientSocket, writer); clients.add(clientHandler); // 为每个客户端启动独立的读取线程 new Thread(clientHandler).start(); System.out.println("客户端 " + clientHandler.getAddress() + " 已连接。"); } else { writer.println("Připojení odmítnuto - dosaženo maximálního počtu klientů. Zkuste se připojit později."); clientSocket.close(); System.out.println("连接被拒绝 - 已达最大客户端数量。"); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } private void broadcastMessage(String message) { for (ClientHandler clientHandler : clients) { clientHandler.getWriter().println(message); } } private class ClientHandler implements Runnable { private final Socket socket; private final PrintWriter writer; private BufferedReader reader; public ClientHandler(Socket socket, PrintWriter writer) { this.socket = socket; this.writer = writer; try { this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { String message; try { while ((message = reader.readLine()) != null) { System.out.println("收到客户端 " + getAddress() + " 的消息: " + message); broadcastMessage(message); } } catch (IOException e) { // 客户端断开连接时触发的异常 } finally { // 清理客户端连接资源 try { reader.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } clients.remove(this); System.out.println("客户端 " + getAddress() + " 已断开连接。"); } } public Socket getSocket() { return this.socket; } public PrintWriter getWriter() { return this.writer; } public String getAddress() { return this.socket.getInetAddress().toString(); } } }
内容的提问来源于stack exchange,提问作者mitas1c
相关产品推荐
相关产品推荐

