Socket 连接的断开通常分为两种情况:

- 正常关闭:客户端或服务器主动调用
socket.close()方法来关闭连接。 - 异常断开:网络出现问题,如网线被拔掉、网络设备故障、对方程序崩溃等,导致连接被强制中断。
处理这两种情况的方式有所不同,但核心都是通过 Java I/O 提供的机制来检测连接状态。
核心原理:如何检测连接是否已断开?
无论是客户端还是服务器,当连接断开后,再通过 Socket 的输入流进行读取操作时,Java 会通过不同的方式通知你连接已断开。
-
对于 TCP Socket (基于字节流):
- 正常关闭:当对方调用
close()后,你再从输入流中读取,会返回-1,这是最可靠的检测方式。 - 异常断开:当网络连接被中断时,你再从输入流中读取,会抛出
IOException。SocketException: Connection reset或SocketTimeoutException(如果设置了超时)。
- 正常关闭:当对方调用
-
对于 UDP Socket (基于数据报):
(图片来源网络,侵删)UDP 是无连接的,所以没有“断开”的概念,你发送数据包,它可能到达,也可能丢失,你需要自己处理发送失败或接收超时的情况。
下面我们主要讨论最常见的 TCP Socket。
客户端处理服务器断开
这是最常见的情况,客户端连接到服务器后,会进入一个循环来持续读取服务器的消息,我们需要在这个循环中检测服务器是否已经断开。
示例代码:客户端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
public class ClientExample {
public static void main(String[] args) {
String hostname = "localhost";
int port = 12345;
try (Socket socket = new Socket(hostname, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
System.out.println("Connected to server.");
// 启动一个线程来发送消息,避免阻塞主线程(这里简化了,直接在主线程演示)
// 在实际应用中,发送和接收通常在不同的线程中
new Thread(() -> {
try (BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
String userInput;
while ((userInput = consoleReader.readLine()) != null) {
out.println(userInput);
if ("exit".equalsIgnoreCase(userInput)) {
break;
}
}
} catch (IOException e) {
System.err.println("Error reading from console: " + e.getMessage());
}
}).start();
// 主线程用于接收服务器消息并检测断开
String serverResponse;
// 循环读取,直到 in.readLine() 返回 null
while ((serverResponse = in.readLine()) != null) {
System.out.println("Server: " + serverResponse);
}
// 当循环结束时,说明 in.readLine() 返回了 null,表示服务器正常关闭了连接
System.out.println("Server has closed the connection. Client is shutting down.");
} catch (UnknownHostException e) {
System.err.println("Don't know about host " + hostname);
e.printStackTrace();
} catch (SocketException e) {
// 捕获 SocketException,这通常表示网络连接被重置或突然中断
System.err.println("Connection to server lost: " + e.getMessage());
} catch (IOException e) {
System.err.println("I/O Error: " + e.getMessage());
e.printStackTrace();
}
}
}
代码解析:

try-with-resources:确保Socket,PrintWriter,BufferedReader在使用后被正确关闭。in.readLine():这个方法会阻塞,直到服务器发送一行数据或者连接关闭。while ((serverResponse = in.readLine()) != null):这是关键!- 如果服务器正常关闭,
in.readLine()会返回null,循环结束。 - 如果服务器异常断开,
in.readLine()会抛出IOException,循环也会被中断,程序会跳到catch块。
- 如果服务器正常关闭,
catch (SocketException e):专门捕获网络层面的异常,比如连接被重置(Connection reset by peer)。
服务器处理客户端断开
服务器通常需要为每个客户端连接创建一个独立的线程(或使用线程池),在每个处理线程中,逻辑与客户端类似:循环读取客户端的数据,并检测客户端是否断开。
示例代码:服务器
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.net.SocketException;
public class ServerExample {
public static void main(String[] args) {
int port = 12345;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Server is listening on port " + port);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
// 为每个客户端创建一个新线程来处理
new Thread(() -> handleClient(clientSocket)).start();
}
} catch (IOException e) {
System.err.println("Server exception: " + e.getMessage());
e.printStackTrace();
}
}
private static void handleClient(Socket clientSocket) {
// 使用 try-with-resources 确保流和socket被关闭
try (
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))
) {
String inputLine;
// 循环读取客户端消息,直到客户端断开连接
while ((inputLine = in.readLine()) != null) {
System.out.println("Received from " + clientSocket.getInetAddress() + ": " + inputLine);
// 简单的回显服务
out.println("Echo: " + inputLine);
if ("exit".equalsIgnoreCase(inputLine)) {
System.out.println("Client requested to close connection.");
break; // 跳出循环,准备关闭资源
}
}
// 当循环结束时,说明客户端已经断开连接
System.out.println("Client " + clientSocket.getInetAddress() + " has disconnected.");
} catch (SocketException e) {
// 捕获客户端异常断开的情况
System.err.println("Client " + clientSocket.getInetAddress() + " connection lost: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error handling client " + clientSocket.getInetAddress() + ": " + e.getMessage());
} finally {
// 确保在finally块中关闭socket,防止资源泄露
try {
clientSocket.close();
System.out.println("Socket for client " + clientSocket.getInetAddress() + " closed.");
} catch (IOException e) {
System.err.println("Failed to close client socket: " + e.getMessage());
}
}
}
}
代码解析:
serverSocket.accept():阻塞等待客户端连接。handleClient方法:每个客户端连接的处理逻辑都在这个方法里,它在一个独立的线程中运行。while ((inputLine = in.readLine()) != null):和客户端一样,这是检测客户端是否断开的核心,如果客户端关闭,这里会返回null或抛出异常。finally块:非常重要!无论连接是正常关闭还是异常中断,finally块中的代码都会被执行,确保clientSocket被关闭,防止服务器资源耗尽。
关键点与最佳实践
- 永远不要假设连接是稳定的:任何网络调用都可能失败,必须用
try-catch包裹。 - 使用
try-with-resources:这是管理 I/O 资源(如Socket,InputStream,OutputStream)的最佳方式,可以确保它们在使用后被自动关闭,避免资源泄露。 - 区分正常关闭和异常断开:
in.read()/in.readLine()返回-1或null:正常关闭。in.read()/in.readLine()抛出IOException:异常断开。
- 设置读取超时:为了避免
read()方法无限期阻塞,可以设置读取超时。socket.setSoTimeout(5000); // 设置5秒读取超时
如果超过5秒没有数据到达,
read()方法会抛出java.net.SocketTimeoutException,你可以据此决定是继续等待还是认为连接已断开。 - 优雅关闭:在关闭连接前,最好先通知对方,客户端发送一个 "logout" 或 "exit" 消息,服务器收到后主动关闭
Socket,这样对方就能通过read()返回null来得知连接是正常结束的,而不是异常中断。
| 情况 | 检测方式 | 表现 |
|---|---|---|
| 正常关闭 | 对方调用 close() |
InputStream.read() 返回 -1 BufferedReader.readLine() 返回 null |
| 异常断开 | 网络问题(拔网线、崩溃等) | InputStream.read() / BufferedReader.readLine() 抛出 IOException (如 SocketException) |
| 读取超时 | 调用 socket.setSoTimeout() |
InputStream.read() 抛出 SocketTimeoutException |
在 Java 中处理 Socket 断开连接,核心就是在读取数据的循环中,正确处理返回 null 的情况和捕获 IOException,并始终确保 Socket 和相关流被正确关闭。
