为什么需要设置 Socket 超时?
在网络通信中,可能会遇到各种不可预见的情况,

- 目标主机不在线或未响应。
- 网络中间设备(如路由器、防火墙)阻塞了数据包。
- 网络连接不稳定,数据包丢失。
- 服务器处理缓慢,无法及时返回数据。
如果没有超时机制,当发生上述情况时,调用 Socket 相关方法(如 connect(), read(), write())的线程将会永久阻塞,等待一个可能永远不会到来的响应,导致整个应用程序挂起。
Java Socket 提供了两种主要的超时设置:
- 连接超时:在尝试建立连接时等待的最长时间。
- 读写超时:在进行数据读取或写入时等待的最长时间。
连接超时设置
连接超时通常在 Socket 对象创建之前进行设置,我们通过 Socket 类的构造方法来实现。
关键方法:Socket(InetAddress address, int port, int timeout)
这个构造方法允许你直接在创建 Socket 时指定连接超时(单位:毫秒)。

示例代码:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
public class SocketConnectTimeoutExample {
public static void main(String[] args) {
String host = "www.google.com"; // 目标主机
int port = 80; // 目标端口
int timeout = 5000; // 超时时间:5秒
try {
System.out.println("正在尝试连接到 " + host + ":" + port + "...");
// 在创建Socket对象时指定超时
Socket socket = new Socket(host, port, timeout);
System.out.println("连接成功!");
// ... 在这里进行数据通信 ...
socket.close();
} catch (UnknownHostException e) {
System.err.println("无法找到主机: " + host);
} catch (SocketTimeoutException e) {
// 捕获超时异常
System.err.println("连接超时!在 " + timeout + " 毫秒内未能建立连接。");
} catch (IOException e) {
System.err.println("发生I/O错误: " + e.getMessage());
}
}
}
另一种常见方式(先创建无连接的Socket,再设置超时并连接):
这种方式更灵活,因为它允许你先配置 Socket 的其他属性(如 SO_REUSEADDR),然后再连接。
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class SocketConnectTimeoutExample2 {
public static void main(String[] args) {
String host = "www.google.com";
int port = 80;
int timeout = 3000; // 3秒超时
try {
// 1. 创建一个未连接的Socket对象
Socket socket = new Socket();
// 2. 设置连接超时
// 这个方法必须在 connect() 调用之前执行
socket.setSoTimeout(timeout); // 注意:这个方法主要是用来设置读写超时,但连接超时通常通过下面的 connect(timeout) 方法设置
// 3. 调用带超时的connect方法
// 这是设置连接超时的标准方式
socket.connect(new java.net.InetSocketAddress(host, port), timeout);
System.out.println("连接成功!");
socket.close();
} catch (SocketTimeoutException e) {
System.err.println("连接超时!在 " + timeout + " 毫秒内未能建立连接。");
} catch (IOException e) {
System.err.println("发生I/O错误: " + e.getMessage());
}
}
}
注意: socket.setSoTimeout() 主要用于设置后续的 I/O 操作(读/写)的超时,而 socket.connect(..., timeout) 才是专门用于设置连接超时的,第一种构造方法 new Socket(host, port, timeout) 实际上是第二种方式的简写。

读写超时设置
连接成功后,在进行 InputStream.read() 或 OutputStream.write() 操作时,也可能因为网络问题而阻塞,这时就需要设置读写超时。
关键方法:socket.setSoTimeout(int timeout)
这个方法用于设置 Socket 的SO_TIMEOUT选项,单位是毫秒,它影响的是所有基于此 Socket 的 InputStream 的读操作。
0:表示禁用超时,即无限期等待(默认行为)。- > 0:表示设置指定的超时时间,如果在此时间内没有数据可读,
InputStream.read()方法将抛出java.net.SocketTimeoutException。
重要提示: setSoTimeout() 只影响读操作,不影响写操作,写操作的超时通常需要通过更底层的实现或使用 NIO 来实现。
示例代码:
import java.io.*;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class SocketReadTimeoutExample {
public static void main(String[] args) {
String host = "time.nist.gov"; // 一个提供时间服务的服务器
int port = 13; // Daytime protocol 端口
int readTimeout = 5000; // 读写超时:5秒
try (Socket socket = new Socket(host, port)) {
// 设置读写超时
socket.setSoTimeout(readTimeout);
System.out.println("已连接到 " + host + ":" + port);
System.out.println("设置了 " + readTimeout + " 毫秒的读写超时。");
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
System.out.println("正在等待服务器响应...");
// readLine() 方法可能会在这里阻塞,直到有数据到达或超时
String line = reader.readLine();
if (line != null) {
System.out.println("服务器响应: " + line);
} else {
System.out.println("服务器没有返回数据。");
}
} catch (SocketTimeoutException e) {
System.err.println("读取数据超时!在 " + readTimeout + " 毫秒内未收到数据。");
} catch (IOException e) {
System.err.println("发生I/O错误: " + e.getMessage());
}
}
}
完整示例:连接和读写超时
下面是一个结合了连接超时和读写超时的完整示例。
import java.io.*;
import java.net.*;
public class ComprehensiveSocketTimeoutExample {
public static void main(String[] args) {
String host = "www.baidu.com";
int port = 80;
int connectTimeout = 3000; // 3秒连接超时
int readTimeout = 5000; // 5秒读写超时
try {
// 1. 创建Socket并设置连接超时
System.out.println("正在尝试连接到 " + host + ":" + port + "...");
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), connectTimeout);
System.out.println("连接成功!");
// 2. 设置读写超时
socket.setSoTimeout(readTimeout);
System.out.println("已设置读写超时为 " + readTimeout + " 毫秒。");
// 3. 发送HTTP请求 (示例)
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("GET / HTTP/1.1");
out.println("Host: " + host);
out.println("Connection: close");
out.println(); // 空行表示头部结束
// 4. 读取响应
System.out.println("正在读取服务器响应...");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String inputLine;
int count = 0;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine); // 打印响应内容
count++;
// 防止输出过多,可以限制读取的行数
if (count > 10) {
System.out.println("...(已读取10行,停止打印后续内容)");
break;
}
}
System.out.println("通信完成。");
socket.close();
} catch (SocketTimeoutException e) {
// 这个异常可能是连接超时,也可能是读写超时
// 可以通过判断操作类型来更精确地处理
System.err.println("操作超时!可能是连接或读写超时。");
} catch (IOException e) {
System.err.println("发生I/O错误: " + e.getMessage());
}
}
}
总结与最佳实践
| 超时类型 | 设置方法 | 作用时机 | 异常 |
|---|---|---|---|
| 连接超时 | new Socket(host, port, timeout) |
创建 Socket 时 |
SocketTimeoutException |
socket.connect(addr, timeout) |
调用 connect() 方法前 |
SocketTimeoutException |
|
| 读写超时 | socket.setSoTimeout(timeout) |
在 connect() 成功后,任何 read() 操作前 |
SocketTimeoutException |
最佳实践:
- 总是设置超时:在生产环境中,为所有网络操作设置合理的超时是必须的,以避免程序挂起。
- 区分超时类型:清晰地设置连接超时和读写超时,连接超时通常较短(如3-5秒),而读写超时可以根据业务需求设置得更长。
- 捕获
SocketTimeoutException:务必用try-catch块来捕获SocketTimeoutException,并在异常发生时采取相应措施(如重试、记录日志、通知用户等)。 - 关闭资源:使用
try-with-resources语句或在finally块中确保Socket和相关的InputStream/OutputStream被正确关闭,以释放系统资源。 - 考虑
NIO:对于需要更高性能和更精细超时控制的场景,可以考虑使用 Java NIO (New I/O) 的Selector和Channel,它们提供了非阻塞 I/O 和更灵活的超时管理机制。
