You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何判断SocketTimeoutException是否代表服务器不可连接?

区分Retrofit客户端中服务器不可达异常与普通超时

我们有一个运行在GUI应用里的Retrofit Java客户端,想要在服务器不可达时通知用户。ConnectException、UnknownHostException这类异常的含义很明确,但普通超时情况我们不想处理——因为后续API调用可能成功。核心疑问是:SocketTimeoutException什么时候代表服务器不可达? 这个异常的消息有时是"Connect timed out",有时只显示"timeout",怎么区分普通超时和服务器不可达的情况?目前我写了这段代码:

public static boolean isConnectionUnavailableException(@NotNull Throwable exception) {

    //these are considered just timeout
    if ((SocketTimeoutException.class.equals(exception.getClass()) && !isConnectTimeout(exception)) ||
            HttpTimeoutException.class.equals(exception.getClass()) ||
            isIOExceptionTimeout(exception)) {
        return false;
    }

    return isConnectTimeout(exception) ||
            isConnectionIssueErrorCode(exception) ||
            exception instanceof SocketException ||
            exception instanceof UnknownHostException ||
            exception instanceof HttpConnectTimeoutException ||
            exception instanceof InterruptedIOException;

}


private static boolean isConnectionIssueErrorCode(@NotNull Throwable exception){
    var analyticsProviderException = findCause(AnalyticsProviderException.class,exception);

    if (analyticsProviderException != null){
        var connectionIssueCodes = List.of(503,504,502);
        return connectionIssueCodes.contains(analyticsProviderException.getResponseCode());
    }

    return false;
}

private static boolean isConnectTimeout(@NotNull Throwable exception){
    return SocketTimeoutException.class.equals(exception.getClass()) && "Connect timed out".equalsIgnoreCase(exception.getMessage());
}


private static boolean isIOExceptionTimeout(@NotNull Throwable exception) {
    return IOException.class.equals(exception.getClass()) &&
            exception.getMessage() != null &&
            exception.getMessage().toLowerCase().contains("timeout");

}

问题分析与优化方案

你的思路方向是对的,但依赖异常消息字符串判断存在明显风险——不同JDK版本、网络库实现可能会修改消息文本,导致判断逻辑失效。下面是更可靠的优化方案:

1. 明确SocketTimeoutException的两种场景

SocketTimeoutException本质对应两类超时:

  • 连接超时:发起连接后服务器完全无响应,属于服务器不可达/无响应的情况
  • 读取超时:连接已成功建立,但服务器迟迟不返回数据,属于普通超时,后续重试大概率成功

2. 用类型判断替代字符串匹配

Retrofit默认依赖的OkHttp库已经帮我们区分了这两种场景:

  • 连接超时会抛出HttpConnectTimeoutException(它是SocketTimeoutException的子类)
  • 读取超时会抛出普通的SocketTimeoutException

所以可以直接通过类型判断,不用依赖不稳定的消息文本:

private static boolean isConnectTimeout(Throwable exception) {
    // 直接判断类型,比字符串匹配可靠得多
    return exception instanceof HttpConnectTimeoutException 
            || (exception instanceof SocketTimeoutException 
                && exception.getCause() instanceof ConnectException);
}

3. 简化整体判断逻辑

调整主方法,减少冗余判断,同时提升可靠性:

public static boolean isConnectionUnavailableException(@NotNull Throwable exception) {
    // 先排除普通超时场景:读取超时、HTTP层面超时
    if (exception instanceof SocketTimeoutException && !(exception instanceof HttpConnectTimeoutException)
            || exception instanceof HttpTimeoutException) {
        return false;
    }

    // 服务器不可达/连接异常的场景
    return exception instanceof ConnectException
            || exception instanceof UnknownHostException
            || exception instanceof HttpConnectTimeoutException
            || exception instanceof SocketException
            || isConnectionIssueErrorCode(exception);
}

4. 优化IOException超时判断

你之前的isIOExceptionTimeout方法太宽泛,普通IO异常也可能包含"timeout"字符串,但未必是需要忽略的超时。建议直接去掉这个判断,或者缩小范围到明确的超时子类,避免误判。

5. 完善异常链遍历

Retrofit可能会把底层异常包装在多层异常里,确保你的findCause方法能遍历整个异常链找到目标异常:

private static <T extends Throwable> T findCause(Class<T> clazz, Throwable exception) {
    Throwable current = exception;
    while (current != null) {
        if (clazz.isInstance(current)) {
            return clazz.cast(current);
        }
        current = current.getCause();
    }
    return null;
}

内容的提问来源于stack exchange,提问作者Shin938

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 19:38:11