在HttpClient的使用上一直对超时的设置有点模糊,搞不懂各种超时的含义,在这里整理下各种超时的设置方式以及对应含义。以便理清他们之间的关系。
题图:from Zoommy
一、序
在HttpClient的使用上一直对超时的设置有点模糊,搞不懂各种超时的含义,在这里整理下各种超时的设置方式以及对应含义。以便理清他们之间的关系。
二、HttpClient 4.3.X版本设置超时
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"));
|
1 2 3 4 5
| CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet=new HttpGet("http://www.baidu.com"); RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build(); httpGet.setConfig(requestConfig); httpClient.execute(httpGet);
|
三、各种超时含义
简述:HttpClient调用一次Http请求的流程如下
- 从连接管理器ConnManager中获取连接。通过设置ConnectionRequestTimeout可以定义获取连接操作的超时。
- 请求建立连接,Http底层使用的依然是TCP,因此需要三次握手。通过设置ConnectTimeout可以定义建立连接的超时。
- 从socket读取响应。通过设置SocketTimeout可以定义获取响应数据的超时。
注意:如果不设置,默认值为0,0会被解析为无穷大。
四、Socket建立连接以及读超时
按照时间顺序
- 创建一个Socket后,操作系统会为其分配发送缓冲队列Send-Q和接收缓冲队列Recv-Q;所有需要网络发送的数据都需要进入Send-Q后等待网络模块发送,而所有网络接收到的数据都会进入Recv-Q等待应用层处理。(参考链接4)
- socket进行TCP三次握手,进入ConnectTimeout的定义范围。
- 通过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建立连接,接收消息的规则,通过查看具体源码,可以更好地理解ConnectTimeout和SocketTimeout的含义。
可以通过在关键代码处插入断点调试的方式,实际测试各种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 { establishRoute(proxyAuthState, managedConn, route, request, context); } catch (final TunnelRefusedException ex) { if (this.log.isDebugEnabled()) { this.log.debug(ex.getMessage()); } response = ex.getResponse(); break; } } response = requestExecutor.execute(request, managedConn, context); } } catch (final ConnectionShutdownException ex) { } }
|
八、总结
ConnectionRequestTimeout、ConnectTimeout和SocketTimeout是三个相对独立的操作过程的超时,三者互不包含,并无覆盖关系,通过以上分析,相信之后不会再混淆各种超时的含义了。
九、参考链接
- JAVA Socket超时浅析
- Linux 下socket超时(connect超时/recv超时)
- HttpClient 4.* connectionRequestTimeout, connectionTimeOut, socketTimeOut
- 【Linux 内核网络协议栈源码剖析】socket 函数剖析