buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

HttpClient超时设置setConnectionTimeout和setSoTimeout

一次http请求,必定会有三个阶段,一:建立连接;二:数据传送;三,断开连接。

http的三次握手 和 四次挥手

http是基于TCP/IP进行通信的,tcp通过3次握手建立连接,并最终以4次挥手终止通信。

知乎上对三次握手和四次挥手有如下解释:

作者:知乎用户
链接:https://www.zhihu.com/question/67772889/answer/256760079
来源:知乎

http是应用层协议,主要依赖于运输层TCP协议(HTTP协议没有规定具体使用哪个运输层协议)。
tcp连接建立和断开方式涉及到客户端和服务器端的端口,缓存等资源的分配与释放问题。

建立连接时,主动连接方(客户端)向服务器请求建立连接(SYN),服务器端收到后会给客户端响应(ack),表示它这边准备好连接了,但是他要确保客户端收到了它的响应,于是需要第三个数据包 客户端向服务器端确认(ack),这样相互确认之后服务器端就可以给这条连接分配必要的端口、缓存等资源。 假设只有两次数据包交换就分配资源,若服务器端响应客户端连接请求的包丢失了,客户端会认为服务器未响应,放弃本次连接请求,而服务器端之前分配的资源就会被闲置浪费。 更多次的报文交换是可以的,但是完全没有必要。 三次交换是建立连接所需最少的数量。

断开连接时, 任务先完成的一方(这里假设是客户端)会向另一方(即服务端)发送断开请求(FIN),表示自己这一方传输任务已经完成,服务器端收到后会响应。 但是这里 服务器端传送给客户端的数据可能还没有完,他需要维持当前连接来完成剩余的传输任务,即客户端还不能释放当前连接所需的资源。当服务端数据传输任务完成后,他会向客户端发送断开请求(FIN),客户端响应。 这样双方相互确认数据传输完成,可以断开连接,分别释放当前连接占用的系统资源,而不是提前释放导致传输的数据丢失。  

TCP三次握手

TCP三次握手

TCP四次挥手

TCP四次挥手

 

http的连接超时 与 响应超时 

httpConnection有两个重要的属性:http.connection.timeout和http.socket.timeout。connection timeout是建立连接的超时时间,socket timeout表示的是等待服务端响应数据的超时时间,通常也称为“读超时”(ReadTimeOut)。

当建立连接在规定的时间内(ConnectionTimeOut )没有完成,那么此次连接就结束了。后续的SocketTimeOutException就一定不会发生。只有当连接建立起来后, 也就是没有发生ConnectionTimeOutException,才会开始传输数据,如果数据在规定的时间内(SocketTimeOut)传输完毕,则断开连接。否则,触发SocketTimeOutException。

SocketTimeoutException 和 ConnectTimeoutException 均派生自 InterruptedIOException(IO被中断异常、IO被阻断异常)

 

 

commons-httpclient 3.1里HttpConnectionParams.java里有如下2个方法:

    /**
     * Sets the timeout until a connection is etablished. A value of zero 
     * means the timeout is not used. The default value is zero.
     * 
     * @param timeout Timeout in milliseconds.
     */
    public void setConnectionTimeout(int timeout) {
        setIntParameter(CONNECTION_TIMEOUT, timeout);
    }
     
     
    /**
     * Sets the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the 
     * timeout for waiting for data. A timeout value of zero is interpreted as an infinite 
     * timeout. This value is used when no socket timeout is set in the 
     * {@link HttpMethodParams HTTP method parameters}. 
     *
     * @param timeout Timeout in milliseconds
     */
    public void setSoTimeout(int timeout) {
        setIntParameter(SO_TIMEOUT, timeout);
    }

  

如下示例代码,可以模拟ConnectTimeoutException和SocketTimeoutException

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;

import java.io.IOException;

public class HttpclientTimeoutTest {

    public static void main(String[] args) {
        HttpClient client = new HttpClient();

        HttpMethod method = new GetMethod("https://linkedin.com/company/stack-overflow");
        client.getHttpConnectionManager().getParams().setConnectionTimeout(10);
        client.getHttpConnectionManager().getParams().setSoTimeout(1000);
        System.out.println("begin..");
        long start = System.currentTimeMillis();
        try {
            int statusCode = client.executeMethod(method);
            System.out.println(statusCode);

            byte[] responseBody = null;
            responseBody = method.getResponseBody();
            String result = new String(responseBody);

            System.out.println(result);
        } catch (HttpException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            System.out.println("end..Duration MS:" + (System.currentTimeMillis() - start));
        }
    }
}

 

 

这里,将建立连接的超时时间设置为小到10ms。即可复现出来ConnectTimeoutException---connect timed out。

begin..
org.apache.commons.httpclient.ConnectTimeoutException: The host did not accept the connection within timeout of 10 ms
	at org.apache.commons.httpclient.protocol.ReflectionSocketFactory.createSocket(ReflectionSocketFactory.java:155)
	at org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory.createSocket(SSLProtocolSocketFactory.java:130)
	at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:707)
	at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:387)
	at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
	at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
	at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
	at com.emax.paycenter.HttpclientTimeoutTest.main(HttpclientTimeoutTest.java:24)
Caused by: java.net.SocketTimeoutException: connect timed out
	at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:579)
	at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:618)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.apache.commons.httpclient.protocol.ReflectionSocketFactory.createSocket(ReflectionSocketFactory.java:140)
	... 7 more
end..Duration MS: 1064
View Code

 

将读取数据的响应超时时间设置为小到10ms,即可复现出来SocketTimeoutException---Read timed out。

begin..
java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.read(SocketInputStream.java:152)
	at java.net.SocketInputStream.read(SocketInputStream.java:122)
	at sun.security.ssl.InputRecord.readFully(InputRecord.java:442)
	at sun.security.ssl.InputRecord.read(InputRecord.java:480)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:927)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
	at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:702)
	at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:122)
	at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
	at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
	at org.apache.commons.httpclient.HttpConnection.flushRequestOutputStream(HttpConnection.java:828)
	at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2116)
	at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)
	at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398)
	at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
	at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
	at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
	at com.emax.paycenter.HttpclientTimeoutTest.main(HttpclientTimeoutTest.java:25)
end..Duration MS: 1516
View Code

 

 

具体应用中,需要以实际需要来做超时设置

我们的程序中,一定要为http连接设置合理的timeout属性,包括连接超时和响应超时,以防止连接被长时间阻塞导致线程长时间被占用而无法释放。尤其是批处理程序,如果远程http服务不稳定,调用方又没有做超时限制,就会有大量的线程被hang住。这样当jvm线程不足以执行处理任务时,就会出现OOM错误: java.lang.OutOfMemoryError: unable to create new native thread 。 

以下罗列我曾经遇到的问题:

✅ 服务器点对点的通信:调用方未设置超时时间和响应时间,出现了上午9点发起的请求,而下午17点才收到响应。 显然,这是未设置SocketTimeOut导致的。———— 8个小时!我一直以为http超时时间默认半小时,这次经历打破了我此前的认知。

✅ 一个定时任务,批量调用远程http接口,查询数据记录的状态。由于远程http挂掉,而我方的http连接超时时间设置不当(3分钟),导致定时任务频频报Connection timed out异常,单次执行时间严重拉长。祸不单行的是,由于定时任务入口未做防重处理,出现上一次还没执行完下一次又开始执行的可怕情况。

 

references:

文中分析了httpclient3.*的一些漏洞。推荐使用httpclient4.2.3。1. 将connection请求头设置为close,则会关闭连接socket。(如果是keep-alive ,不会关闭);2、在使用httpclient3.1时(4.2.3没问题),尽量不要调用 byte[] getResponseBody() :因为如果Content-Length没设置或者传输的数据大于1M,会有大量冗余日志。

分析了TCP为什么需要3次握手,和为什么需要4次挥手

 

【EOF】知识就是力量,但更重要的是..., 欢迎关注我的微信公众号「靠谱的程序员」,解密靠谱程序员的日常,让我们一起做靠谱的程序员。

posted on 2018-01-16 17:10  buguge  阅读(32113)  评论(0编辑  收藏  举报