1. 概述

本文将深入讲解 OkHttp 客户端中几种关键的超时(Timeout)配置方式。掌握这些超时机制,能有效避免请求堆积、资源耗尽等线上“踩坑”问题。

如果你还不熟悉 OkHttp 的基本使用,建议先阅读我们之前的 OkHttp 入门指南

2. 连接超时(Connect Timeout)

连接超时指的是:客户端尝试与目标服务器建立 TCP 连接的最大等待时间

✅ 默认值:OkHttpClient 的连接超时默认为 10 秒
❌ 设置为 0 表示不设限,即永不超时,生产环境慎用。

可通过 OkHttpClient.Builder#connectTimeout() 方法自定义。

示例:触发连接超时

@Test
public void whenConnectTimeoutExceeded_thenSocketTimeoutException() {
    OkHttpClient client = new OkHttpClient.Builder()
      .connectTimeout(10, TimeUnit.MILLISECONDS)
      .build();

    Request request = new Request.Builder()
      .url("http://203.0.113.1") // 无效地址,无法建立连接
      .build();

    Throwable thrown = catchThrowable(() -> client.newCall(request).execute());

    assertThat(thrown).isInstanceOf(SocketTimeoutException.class);
}

⚠️ 注意:虽然异常是 SocketTimeoutException,但它实际是由连接阶段超时引发的。这是 OkHttp 的设计行为,别被名字误导。

3. 读取超时(Read Timeout)

读取超时指的是:连接建立后,客户端等待服务器返回数据时,两次数据包之间的最大空闲时间

简单说:服务器“卡住”不发数据,超过这个时间就断。

✅ 默认值:同样是 10 秒
✅ 零值表示无限制。

使用 OkHttpClient.Builder#readTimeout() 配置。

示例:触发读取超时

@Test
public void whenReadTimeoutExceeded_thenSocketTimeoutException() {
    OkHttpClient client = new OkHttpClient.Builder()
      .readTimeout(10, TimeUnit.MILLISECONDS)
      .build();

    Request request = new Request.Builder()
      .url("https://httpbin.org/delay/2") // 延迟 2 秒返回
      .build();

    Throwable thrown = catchThrowable(() -> client.newCall(request).execute());

    assertThat(thrown).isInstanceOf(SocketTimeoutException.class);
}

服务器响应时间(2s) > 读取超时(10ms),触发超时。

4. 写入超时(Write Timeout)

写入超时指的是:客户端向服务器发送请求体(如 POST 数据)时,两次数据包之间的最大空闲时间

适用于上传大文件或网络较差的场景。

✅ 默认值:10 秒
✅ 零值表示无限制。

通过 OkHttpClient.Builder#writeTimeout() 设置。

示例:触发写入超时

@Test
public void whenWriteTimeoutExceeded_thenSocketTimeoutException() {
    OkHttpClient client = new OkHttpClient.Builder()
      .writeTimeout(10, TimeUnit.MILLISECONDS)
      .build();

    Request request = new Request.Builder()
      .url("https://httpbin.org/delay/2")
      .post(RequestBody.create(MediaType.parse("text/plain"), create1MBString()))
      .build();

    Throwable thrown = catchThrowable(() -> client.newCall(request).execute());

    assertThat(thrown).isInstanceOf(SocketTimeoutException.class);
}

假设网络慢,10ms 内无法发完 1MB 数据,触发写入超时。

5. 调用超时(Call Timeout)

调用超时是对整个 HTTP 请求生命周期的总时限控制,涵盖:

  • DNS 解析
  • 建立连接(Connect)
  • 发送请求体(Write)
  • 服务器处理
  • 接收响应体(Read)

✅ 默认值:0(无超时),需手动开启。
✅ 使用 OkHttpClient.Builder#callTimeout() 设置。

⚠️ 一旦触发,抛出的是 InterruptedIOException,不是 SocketTimeoutException,注意区分。

示例:触发调用超时

@Test
public void whenCallTimeoutExceeded_thenInterruptedIOException() {
    OkHttpClient client = new OkHttpClient.Builder()
      .callTimeout(1, TimeUnit.SECONDS)
      .build();

    Request request = new Request.Builder()
      .url("https://httpbin.org/delay/2")
      .build();

    Throwable thrown = catchThrowable(() -> client.newCall(request).execute());

    assertThat(thrown).isInstanceOf(InterruptedIOException.class);
}

总耗时超过 1 秒即中断,即使某个阶段(如 read)单独看未超时。

6. 按请求设置超时(Per-Request Timeout)

✅ 最佳实践:全局共用一个 OkHttpClient 实例,避免资源浪费。

但某些接口响应慢(如报表导出),不能因此拉高全局超时。此时应:

  1. 基于默认 client 创建新 builder
  2. 调整特定超时
  3. 构建临时 client 发起请求

示例:为特定请求延长超时

@Test
public void whenPerRequestTimeoutExtended_thenResponseSuccess() throws IOException {
    OkHttpClient defaultClient = new OkHttpClient.Builder()
      .readTimeout(1, TimeUnit.SECONDS)
      .build();

    Request request = new Request.Builder()
      .url("https://httpbin.org/delay/2")
      .build();

    // 默认 client 超时失败
    Throwable thrown = catchThrowable(() -> defaultClient.newCall(request).execute());
    assertThat(thrown).isInstanceOf(InterruptedIOException.class);

    // 为当前请求创建专用 client
    OkHttpClient extendedTimeoutClient = defaultClient.newBuilder()
      .readTimeout(5, TimeUnit.SECONDS)
      .build();

    Response response = extendedTimeoutClient.newCall(request).execute();
    assertThat(response.code()).isEqualTo(200);
}

newBuilder() 复用原配置,只修改所需项,简单粗暴有效。

7. 总结

本文梳理了 OkHttp 四类超时机制:

超时类型 作用阶段 默认值 触发异常
Connect 建立 TCP 连接 10 秒 SocketTimeoutException
Read 接收响应数据时的空闲间隔 10 秒 SocketTimeoutException
Write 发送请求数据时的空闲间隔 10 秒 SocketTimeoutException
Call 整个请求生命周期 InterruptedIOException

✅ 核心要点:

  • 生产环境务必显式设置超时,避免线程阻塞。
  • callTimeout 是总闸,建议对关键接口设置。
  • 利用 newBuilder() 实现 per-request 级别的灵活控制。

所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/libraries-http


原始标题:A Quick Guide to Timeouts in OkHttp