Socket 的超时设置主要分为三个层面:

- 连接超时:指客户端尝试连接到服务器,如果在指定时间内没有成功,则放弃连接并抛出超时异常。
- 读取超时:指当连接建立后,调用
InputStream.read()方法从对端读取数据,如果在指定时间内没有收到任何数据,则抛出超时异常。 - 接受连接超时:指服务器端调用
ServerSocket.accept()方法等待客户端连接,如果在指定时间内没有客户端连接,则抛出超时异常。
下面我们分别对这三个方面进行详细说明,并提供代码示例。
连接超时
连接超时只对 Socket 客户端有效,用于控制 socket.connect() 方法的行为。
设置方法
通过 Socket 对象的 connect(SocketAddress endpoint, int timeout) 方法来设置,单位是毫秒。
endpoint: 要连接的服务器地址和端口。timeout: 超时时间(毫秒),如果为 0,则表示无限期等待(默认行为)。
代码示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class SocketConnectTimeoutExample {
public static void main(String[] args) {
// 目标服务器地址和端口(这里用一个不存在的地址来模拟超时)
String host = "192.0.2.1"; // 这是一个保留的测试地址,不会连接成功
int port = 8080;
Socket socket = new Socket();
try {
// 设置连接超时时间为 3 秒
System.out.println("尝试连接到 " + host + ":" + port + ",超时时间 3 秒...");
socket.connect(new InetSocketAddress(host, port), 3000);
System.out.println("连接成功!");
} catch (SocketTimeoutException e) {
// 如果在3秒内连接未建立,会抛出 SocketTimeoutException
System.err.println("连接超时!在指定时间内未能连接到服务器。");
} catch (IOException e) {
System.err.println("连接发生IO异常: " + e.getMessage());
} finally {
// 确保关闭 socket
if (socket != null && !socket.isClosed()) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
读取超时
读取超时用于控制 InputStream.read() 方法的行为,防止因网络中断或对端无响应而导致程序永久阻塞。

设置方法
通过 Socket 对象的 setSoTimeout(int timeout) 方法来设置,单位是毫秒。
timeout: 超时时间(毫秒),如果为 0,则表示无限期等待(默认行为)。
这个设置必须在连接建立之后、调用 read() 方法之前进行。
代码示例
下面是一个客户端和服务器端的示例,来演示读取超时。
服务器端代码

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketTimeoutServer {
public static void main(String[] args) throws IOException {
int port = 8080;
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务器启动,监听端口 " + port);
// 等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接: " + clientSocket.getInetAddress());
// 获取输入流
InputStream input = clientSocket.getInputStream();
byte[] buffer = new byte[1024];
// 注意:服务器端没有设置读取超时,它会一直等待客户端发送数据
System.out.println("等待客户端发送数据...");
int bytesRead = input.read(buffer);
if (bytesRead != -1) {
String receivedData = new String(buffer, 0, bytesRead);
System.out.println("收到数据: " + receivedData);
}
// 发送响应
OutputStream output = clientSocket.getOutputStream();
output.write("服务器已收到数据".getBytes());
clientSocket.close();
serverSocket.close();
}
}
客户端代码
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class SocketTimeoutClient {
public static void main(String[] args) {
String host = "localhost";
int port = 8080;
try (Socket socket = new Socket()) {
// 1. 连接服务器
socket.connect(new InetSocketAddress(host, port), 3000);
System.out.println("连接服务器成功!");
// 2. 设置读取超时时间为 2 秒
// 如果服务器在2秒内没有发送任何数据,read() 方法会抛出 SocketTimeoutException
socket.setSoTimeout(2000);
System.out.println("设置读取超时时间为 2 秒。");
InputStream input = socket.getInputStream();
byte[] buffer = new byte[1024];
// 3. 尝试读取数据
// 假设服务器在5秒后才发送数据,而我们只设置了2秒的超时
System.out.println("尝试读取数据...");
try {
int bytesRead = input.read(buffer); // 这里会阻塞,直到超时或收到数据
if (bytesRead != -1) {
String receivedData = new String(buffer, 0, bytesRead);
System.out.println("收到数据: " + receivedData);
}
} catch (SocketTimeoutException e) {
// 2秒后没有收到数据,抛出超时异常
System.err.println("读取超时!在指定时间内未收到服务器的数据。");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行分析:
- 先启动服务器端,它会
accept()并等待客户端。 - 启动客户端,客户端成功连接到服务器。
- 客户端设置了
setSoTimeout(2000),即2秒读取超时。 - 客户端调用
input.read()开始等待。 - 关键点:服务器端代码中,
read()方法会一直等待客户端发送数据,而客户端也在等待服务器发送数据,客户端的read()方法在2秒内不会收到任何数据,从而抛出SocketTimeoutException。
接受连接超时
接受连接超时只对 ServerSocket 有效,用于控制 ServerSocket.accept() 方法的行为。
设置方法
通过 ServerSocket 对象的 setSoTimeout(int timeout) 方法来设置,单位是毫秒。
timeout: 超时时间(毫秒),如果为 0,则表示无限期等待(默认行为)。
代码示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class ServerSocketAcceptTimeoutExample {
public static void main(String[] args) {
int port = 8081;
try (ServerSocket serverSocket = new ServerSocket()) {
serverSocket.bind(new InetSocketAddress(port));
System.out.println("服务器启动,监听端口 " + port);
// 设置 accept() 方法的超时时间为 5 秒
serverSocket.setSoTimeout(5000);
System.out.println("设置 accept() 超时时间为 5 秒。");
try {
// 在5秒内等待客户端连接
System.out.println("等待客户端连接...");
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接: " + clientSocket.getInetAddress());
} catch (SocketTimeoutException e) {
// 5秒内没有客户端连接,抛出超时异常
System.err.println("接受连接超时!在指定时间内没有客户端连接。");
}
System.out.println("服务器结束等待。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结与最佳实践
| 超时类型 | 适用对象 | 设置方法 | 作用 | 单位 |
|---|---|---|---|---|
| 连接超时 | Socket (客户端) |
socket.connect(addr, timeout) |
控制建立TCP连接的超时 | 毫秒 |
| 读取超时 | Socket (客户端/服务器) |
socket.setSoTimeout(timeout) |
控制从 InputStream 读取数据的超时 |
毫秒 |
| 接受连接超时 | ServerSocket |
serverSocket.setSoTimeout(timeout) |
控制调用 accept() 等待客户端的超时 |
毫秒 |
最佳实践
- 总是设置超时:在任何涉及网络 I/O 的应用中,都应该为
Socket和ServerSocket设置合理的超时时间,这可以防止你的程序因网络问题或对端故障而“挂起”。 - 使用
try-with-resources:如上面的示例所示,使用try-with-resources语句可以确保Socket和ServerSocket在使用完毕后被自动关闭,避免资源泄漏。 - 捕获
SocketTimeoutException:设置超时后,必须准备好捕获SocketTimeoutException异常,这是程序判断超时并采取相应措施(如重试、记录日志或通知用户)的关键。 - 选择合适的超时值:超时时间不是越长越好,也不是越短越好,需要根据你的网络环境和业务需求来权衡,一个太短的超时可能导致在正常网络波动下频繁失败;一个太长的超时则会降低程序的响应能力。
- 注意
setSoTimeout的影响:setSoTimeout设置后,会影响该Socket对象上所有的InputStream.read()调用,每次调用read()都会重新开始计时,如果你想让读取操作一直等待,直到收到数据或连接关闭,那么就不要设置超时(或设置为0)。
