基本方法
最直接的方法是调用 Socket 对象的 close() 方法。

import java.io.IOException;
import java.net.Socket;
public class BasicCloseExample {
public static void main(String[] args) {
Socket socket = null;
try {
// 1. 创建一个Socket连接
socket = new Socket("example.com", 80);
System.out.println("Socket 已连接: " + socket.isConnected());
// ... 进行网络IO操作 ...
} catch (IOException e) {
System.err.println("发生IO异常: " + e.getMessage());
} finally {
// 2. 在 finally 块中关闭连接
if (socket != null && !socket.isClosed()) {
try {
socket.close();
System.out.println("Socket 已关闭");
} catch (IOException e) {
System.err.println("关闭Socket时发生IO异常: " + e.getMessage());
}
}
}
}
}
关键点:
socket.close(): 这个方法会关闭 Socket 连接,并释放与该 Socket 相关的所有系统资源(如文件描述符)。finally块: 这是一个非常重要的编程习惯,无论try块中的代码是否抛出异常,finally块中的代码都一定会被执行,这确保了即使在发生异常的情况下,Socket 也能被尝试关闭,避免资源泄漏。
最佳实践与推荐方法
仅仅调用 socket.close() 是不够的,一个完整的 Socket 通信通常包括一个输入流和一个输出流,为了确保所有资源都被正确释放,推荐使用 try-with-resources 语句(Java 7+ 引入),这是目前最安全、最简洁的方式。
为什么需要单独关闭流?
Socket 的 close() 方法在关闭连接的同时,也会关闭其关联的 InputStream 和 OutputStream,一个良好的实践是显式地关闭这些流,原因如下:
- 及时释放资源: 如果你先关闭了流,可以立即释放与该流相关的缓冲区等资源,即使 Socket 本身还暂时存在。
- 明确的语义: 显式关闭流表明你已经完成了数据的读写,这是一个清晰的信号。
推荐方式一:try-with-resources (首选)
try-with-resources 语句可以确保任何实现了 AutoCloseable 接口(Closeable 接口是其子接口)的资源在语句块执行完毕后,都会被自动调用 close() 方法,这完全避免了手动关闭时可能忘记或发生异常导致资源泄漏的问题。

客户端示例:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class TryWithResourcesClient {
public static void main(String[] args) {
// try-with-resources 会自动关闭 socket, input stream, output stream
try (Socket socket = new Socket("example.com", 80);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
System.out.println("Socket 已连接");
// 发送数据
out.println("GET / HTTP/1.1");
out.println("Host: example.com");
out.println(); // 空行表示头部结束
// 读取响应
String responseLine;
while ((responseLine = in.readLine()) != null) {
System.out.println(responseLine);
}
} catch (IOException e) {
System.err.println("客户端发生IO异常: " + e.getMessage());
}
// 无需在 finally 块中手动关闭,所有资源已自动关闭
System.out.println("连接已关闭,资源已释放");
}
}
服务器端示例(处理客户端连接):
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TryWithResourcesServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(1234)) {
System.out.println("服务器启动,等待客户端连接...");
while (true) { // 保持服务器运行,持续接受连接
try (Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {
System.out.println("客户端已连接: " + clientSocket.getInetAddress());
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("收到客户端消息: " + inputLine);
if ("bye".equalsIgnoreCase(inputLine)) {
out.println("服务器:再见!");
break;
}
out.println("服务器:收到你的消息 -> " + inputLine);
}
System.out.println("客户端断开连接");
} catch (IOException e) {
System.err.println("处理客户端连接时发生IO异常: " + e.getMessage());
}
}
} catch (IOException e) {
System.err.println("服务器启动失败: " + e.getMessage());
}
}
}
推荐方式二:手动关闭(传统方式)
如果你使用的是 Java 7 之前的版本,或者有特殊需求,必须手动关闭资源。关闭的顺序很重要:应该先关闭输出流,再关闭输入流,最后关闭 Socket。
- 为什么先关闭输出流? 在 TCP 协议中,关闭输出流会向对方发送一个
FIN(Finish) 包,表示“我已经没有数据要发送了”,如果先关闭输入流,对方可能还在等待你的数据,导致不必要的等待。
Socket socket = null;
PrintWriter out = null;
BufferedReader in = null;
try {
socket = new Socket("example.com", 80);
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// ... IO操作 ...
} catch (IOException e) {
System.err.println("发生IO异常: " + e.getMessage());
} finally {
// 关闭顺序:out -> in -> socket
if (out != null) {
out.close(); // PrintWriter 的 close() 也会关闭底层的 OutputStream
}
if (in != null) {
try {
in.close(); // BufferedReader 的 close() 也会关闭底层的 InputStream
} catch (IOException e) {
System.err.println("关闭输入流时发生异常: " + e.getMessage());
}
}
if (socket != null) {
try {
// 检查 socket 是否已经关闭,因为关闭流时可能已经关闭了它
if (!socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
System.err.println("关闭Socket时发生异常: " + e.getMessage());
}
}
}
常见问题与注意事项
问题1:SocketException: Socket is closed
原因: 当你尝试对一个已经关闭的 Socket 或其流进行读写操作时,会抛出此异常。 解决方法:

- 在进行任何 IO 操作前,检查
socket.isClosed()和socket.isConnected()的状态。 - 确保你的代码逻辑不会在关闭连接后,还试图使用这个连接。
问题2:资源泄漏
原因:
- 忘记在
finally块中关闭资源。 close()方法本身抛出异常,导致后续的关闭操作没有执行。 解决方法:
- 强烈推荐使用
try-with-resources,它能从根本上解决资源泄漏问题。 - 如果手动关闭,确保在
finally块中对每个资源的关闭操作都使用try-catch包裹,避免一个资源关闭失败影响其他资源的释放。
问题3:SocketTimeoutException
原因: 如果你设置了 socket.setSoTimeout(timeout),并且在指定的时间内没有收到数据,read() 方法会抛出此异常。
注意: SocketTimeoutException 不是 IO 异常,它是一个 InterruptedIOException,它表示的是一次读取操作超时,但连接本身仍然是有效的,你通常不应该因为一次读取超时就关闭整个连接。
socket.setSoTimeout(5000); // 设置5秒超时
try {
// 这行代码可能会在5秒后抛出 SocketTimeoutException
int data = in.read();
} catch (SocketTimeoutException e) {
System.out.println("读取超时,但连接仍然存在,可以继续尝试读取或发送数据。");
// 不要在这里关闭 socket
}
问题4:优雅地关闭 vs 强制关闭
socket.close()(优雅关闭): 这是最常用的方法,它会先关闭输入流和输出流,发送FIN包给对方,然后释放资源,这给了对方一个机会去处理剩余的数据并正常关闭连接。socket.shutdownInput()/socket.shutdownOutput()(半关闭):shutdownInput(): 关闭输入流,你不能再从该 Socket 读取数据,但可以继续向它写入数据,对方会收到一个EOF(End-Of-File) 信号。shutdownOutput(): 关闭输出流,你不能再向该 Socket 写入数据,但可以继续读取数据,并向对方发送FIN包。- 这在需要告知对方“我这边说完了,但还在听你说”的场景下非常有用。
socket.close()中的SO_LINGER选项:- 你可以通过
socket.setSoLinger(true, delay)来设置 linger 选项。 delay> 0,调用close()方法会阻塞最多delay秒,直到所有未发送的数据被发送出去,或者超时。delay= 0,会立即丢弃发送缓冲区中所有未发送的数据,并立即发送RST(Reset) 包强制关闭连接,这是一种强制关闭,可能会对对方造成困扰。
- 你可以通过
| 方法 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
try-with-resources |
代码简洁、安全,自动管理资源,避免泄漏 | 需要 Java 7+ | 所有现代 Java 应用(首选) |
手动关闭 (finally) |
兼容旧版 Java | 代码冗长,容易出错,忘记关闭或处理异常 | Java 7 之前的项目,或需要精细控制关闭顺序的场景 |
socket.close() |
简单直接 | 可能不完整,未显式关闭流 | 仅在非常简单的场景或与 try-with-resources 结合使用时 |
核心建议:
- 总是关闭资源:无论是 Socket、InputStream 还是 OutputStream。
- 优先使用
try-with-resources:这是现代 Java 编程的最佳实践,能让你从繁琐的资源管理中解放出来。 - 理解关闭顺序:如果手动关闭,遵循
输出流 -> 输入流 -> Socket的顺序。 - 区分超时和错误:
SocketTimeoutException不等于连接断开,不要轻易关闭 Socket。
