应该捕获并分类处理IOException:SocketTimeoutException适合重试,ConnectException需检查配置,UnknownHostException不应盲目重试,EOFException需判断是否预期结束;Socket需显式关闭且顺序为shutdownOutput→读响应→close;HttpURLConnection的connect()和getInputStream()均可能抛异常,须分别处理;CompletableFuture中应封装带异常分类与退避的网络操作。

Java网络连接抛出的IOException到底该不该捕获?
绝大多数网络操作(如Socket连接、HttpURLConnection请求、URL.openStream())在底层失败时都会抛出IOException或其子类(如ConnectException、SocketTimeoutException)。它不是“可以忽略的检查异常”,而是必须处理的信号——网络不可靠是常态,不是例外。
常见错误是只用一个catch (IOException e)笼统吞掉异常,却不区分原因。比如把超时当成连接拒绝处理,会导致重试逻辑失效。
-
SocketTimeoutException:说明已连上服务器但响应慢,适合重试(加退避) -
ConnectException:目标地址不可达或端口未监听,重试前应检查配置或等服务启动 -
UnknownHostException:DNS解析失败,大概率是域名写错或本地DNS异常,不应盲目重试 -
EOFException(读取流时):对方提前关闭连接,需判断是否为预期结束
用try-with-resources关流,但别忘了Socket本身也要显式close()
很多人以为只要用try-with-resources包装了InputStream或OutputStream,Socket就会自动关闭。其实不会:Socket不实现AutoCloseable(JDK 7+ 的Socket虽实现了,但部分旧版 Android 或定制 JRE 可能不兼容),且即使实现了,也仅保证其关联流被关,Socket底层连接状态未必释放干净。
正确做法是明确关闭Socket,且顺序重要:
立即学习“Java免费学习笔记(深入)”;
- 先关闭输出流(调用
shutdownOutput()或close()),通知对方“我发完了” - 再读取剩余响应(如有)
- 最后调用
socket.close()
示例片段:
Socket socket = null;
try {
socket = new Socket("example.com", 80);
socket.setSoTimeout(5000);
OutputStream out = socket.getOutputStream();
out.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".getBytes());
socket.shutdownOutput(); // 关闭写方向
InputStream in = socket.getInputStream();
// ... 读响应
} catch (IOException e) {
// 分类型处理
} finally {
if (socket != null && !socket.isClosed()) {
try { socket.close(); } catch (IOException ignored) {}
}
}
HttpURLConnection的connect()和getInputStream()都可能抛异常
很多人以为调用connect()成功就代表请求完成,其实不然。connect()只建立 TCP 连接(或复用已有连接),真正的 HTTP 请求发送和响应读取发生在getInputStream()或getOutputStream()调用时。
这意味着:
-
connect()抛IOException→ 连接层失败(如防火墙拦截、DNS失败) -
getInputStream()抛IOException→ 已连上但 HTTP 层出错(如服务器崩溃、返回空响应、SSL握手失败) -
getResponseCode()返回HTTP 5xx或4xx→ 业务层面失败,不算异常,但需按协议逻辑处理
别跳过setConnectTimeout()和setReadTimeout()——默认无限等待,线程会卡死。
异步场景下,CompletableFuture不能掩盖网络异常的传播路径
用CompletableFuture.supplyAsync(() -> new URL(url).openStream())看似简洁,但问题明显:
- 异常被封装进
CompletionException,原始类型信息丢失,getCause()才看到IOException - 超时无法用
orTimeout()精确控制网络阶段(连接 vs 读取) - 没有重试上下文,失败后无法根据异常类型决策
更可控的方式是把网络操作封装成独立方法,内部做类型判断和退避,再交由CompletableFuture调度:
static CompletableFuturefetchWithRetry(String url, int retries) { return CompletableFuture.supplyAsync(() -> { for (int i = 0; i <= retries; i++) { try { return doFetch(url); // 内部处理各类 IOException } catch (SocketTimeoutException e) { if (i == retries) throw e; try { Thread.sleep((long) Math.pow(2, i) * 100); } catch (InterruptedException ie) {} } } return null; }); }
网络异常从来不是“一次处理完就结束”的问题,关键在于分清连接、传输、协议、业务四层失败点,并让每层有对应的恢复策略。漏掉任意一层的判断,都会让程序在真实环境中静默降级或无限阻塞。











