HttpClient超时浅析

在HttpClient的使用上一直对超时的设置有点模糊,搞不懂各种超时的含义,在这里整理下各种超时的设置方式以及对应含义。以便理清他们之间的关系。

题图:from Zoommy

一、序

在HttpClient的使用上一直对超时的设置有点模糊,搞不懂各种超时的含义,在这里整理下各种超时的设置方式以及对应含义。以便理清他们之间的关系。

二、HttpClient 4.3.X版本设置超时

  • 基于HttpClient
1
2
3
4
5
6
7
8
RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES)
.setSocketTimeout(150).setConnectTimeout(150).setConnectionRequestTimeout(150).build();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
httpClient.execute(new HttpGet("http://www.baidu.com"));
  • 基于HttpPost或HttpGet
1
2
3
4
5
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet=new HttpGet("http://www.baidu.com");//HTTP Get请求(POST雷同)
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();//设置请求和传输超时时间
httpGet.setConfig(requestConfig);
httpClient.execute(httpGet);//执行请求

三、各种超时含义

简述:HttpClient调用一次Http请求的流程如下

  1. 从连接管理器ConnManager中获取连接。通过设置ConnectionRequestTimeout可以定义获取连接操作的超时。
  2. 请求建立连接,Http底层使用的依然是TCP,因此需要三次握手。通过设置ConnectTimeout可以定义建立连接的超时。
  3. 从socket读取响应。通过设置SocketTimeout可以定义获取响应数据的超时。

注意:如果不设置,默认值为0,0会被解析为无穷大。

四、Socket建立连接以及读超时

按照时间顺序

  1. 创建一个Socket后,操作系统会为其分配发送缓冲队列Send-Q和接收缓冲队列Recv-Q;所有需要网络发送的数据都需要进入Send-Q后等待网络模块发送,而所有网络接收到的数据都会进入Recv-Q等待应用层处理。(参考链接4)
  2. socket进行TCP三次握手,进入ConnectTimeout的定义范围。
  3. 通过socket调用recv命令阻塞式读取Recv-Q,直到接收到消息或者超时,这里就是SocketTimeout的定义范围。

五、ConnectTimeout

注意connectTimeout不要设置为超过75秒,对于基于 Berkeley BSD的系统,默认时间是75秒,因此,超过75秒没有意义,而且75秒对于应用来说也未免太过长了。

具体代码可以参考Socket类中的connect方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Connects this socket to the server with a specified timeout value.
* A timeout of zero is interpreted as an infinite timeout. The connection
* will then block until established or an error occurs.
*
* @param endpoint the {@code SocketAddress}
* @param timeout the timeout value to be used in milliseconds.
* @throws IOException if an error occurs during the connection
* @throws SocketTimeoutException if timeout expires before connecting
* @throws java.nio.channels.IllegalBlockingModeException
* if this socket has an associated channel,
* and the channel is in non-blocking mode
* @throws IllegalArgumentException if endpoint is null or is a
* SocketAddress subclass not supported by this socket
* @since 1.4
* @spec JSR-51
*/
public void connect(SocketAddress endpoint, int timeout) throws IOException {
}

六、SocketTimeout

具体实现是native方法,SocketInputStream.socketRead0(),源码需要参考具体jvm的实现,不过可以确定的是该方法为阻塞式,SocketTimeout实际为阻塞读取Recv-Q数据的超时时间

七、HttpClient发送请求源码分析

HttpClient创建http请求也是遵照socket建立连接,接收消息的规则,通过查看具体源码,可以更好地理解ConnectTimeoutSocketTimeout的含义。

可以通过在关键代码处插入断点调试的方式,实际测试各种timeout值。注意:不要将timeout设置过小,如10ms,可能造成代码未执行完就已经超时

关键代码如下:(org.apache.http.impl.execchain.MainClientExec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
// ...省略
try {
// ...省略
for (int execCount = 1;; execCount++) {
// ...省略
if (!managedConn.isOpen()) {
this.log.debug("Opening connection " + route);
try {
// ***************************************创建连接关键代码,connectTimeout****************************************************
establishRoute(proxyAuthState, managedConn, route, request, context);
// ***************************************创建连接关键代码,connectTimeout****************************************************
} catch (final TunnelRefusedException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage());
}
response = ex.getResponse();
break;
}
}
// ...省略
// ***************************************阻塞接收数据关键代码,socketTimeout****************************************************
response = requestExecutor.execute(request, managedConn, context);
// ***************************************阻塞接收数据关键代码,socketTimeout****************************************************
// ...省略
}
// ...省略
} catch (final ConnectionShutdownException ex) {
// ...省略
}
}

八、总结

ConnectionRequestTimeoutConnectTimeoutSocketTimeout是三个相对独立的操作过程的超时,三者互不包含,并无覆盖关系,通过以上分析,相信之后不会再混淆各种超时的含义了。

九、参考链接

  1. JAVA Socket超时浅析
  2. Linux 下socket超时(connect超时/recv超时)
  3. HttpClient 4.* connectionRequestTimeout, connectionTimeOut, socketTimeOut
  4. 【Linux 内核网络协议栈源码剖析】socket 函数剖析