一次SocketException:Connection reset 异常排查

时间:2025-04-22 08:25:12

问题描述

上一期的需求上线之后,线上多了一个异常:Connection reset。如下:

[2017-03-22 00:45:00 ERROR] [creativeAuditTaskScheduler_Worker-9] (:169) - getAuditResult exception, call adx api failed. msg:I/O error on GET request for "https://biz/getAuditInfo?dspId=13":Connection reset; nested exception is : Connection reset
: I/O error on GET request for "https://biz/getAuditInfo?dspId=13":Connection reset; nested exception is : Connection reset
    at (:558)
    at (:511)
    at (:248)
    at :136)
    at .(Unknown Source)
    at (:43)
    at (:606)
    at (:317)
    at (:190)
    at (:157)
    at $(:98)
    at (:262)
    at (:95)
    at (:179)
    at (:207)
    at .$(Unknown Source)
    at (:117)
    at (:88)
    at (:202)
    at $(:573)
Caused by: : Connection reset
    at (:196)
    at (:122)
    at (:442)
    at (:480)
    at (:927)
    at (:884)
    at (:102)
    at (:87)
    at (:139)
    at (:155)
    at (:284)
    at (:140)
    at (:57)
    at (:261)
    at (:165)
    at (:167)
    at (:272)
    at (:124)
    at (:271)
    at (:184)
    at (:88)
    at (:110)
    at (:184)
    at (:82)
    at (:87)
    at (:48)
    at (:52)
    at (:542)
    ... 19 more

这里使用Spring RestTemplate调外部接口查询结果。Spring RestTemplate 配置如下:

    <bean id="" class="">
        <!--整个连接池的并发-->
        <property name="maxTotal" value="1000" />
        <!--每个路由的并发-->
        <property name="defaultMaxPerRoute" value="32" />
    </bean>

    <bean id="" class="" factory-method="create">
        <property name="connectionManager" ref="" />
        <!--开启重试-->
        <property name="retryHandler">
        <!--新加的异常处理,只处理ConnectTimeoutException和UnknownHostException异常-->
        <!--上一版本使用的是-->
        <!--默认处理InterruptedIOException、UnknownHostException、ConnectException、SSLException 4种异常-->
            <bean class="">
                <constructor-arg value="3"/>
                <constructor-arg value="true"/>
                <constructor-arg value="200"/>
            </bean>
        </property>
        <!--开启keep-Alive-->
        <property name="keepAliveStrategy">
            <bean class="" />
        </property>
        <property name="defaultHeaders">
            <list>
                <bean class="">
                    <constructor-arg value="User-Agent"/>
                    <constructor-arg value="Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"/>
                </bean>
                <bean class="">
                    <constructor-arg value="Accept-Encoding"/>
                    <constructor-arg value="gzip,deflate"/>
                </bean>
                <bean class="">
                    <constructor-arg value="Accept-Language"/>
                    <constructor-arg value="zh-CN"/>
                </bean>
                <bean class="">
                    <constructor-arg value="Connection"/>
                    <constructor-arg value="keep-alive"/>
                </bean>
            </list>
        </property>
    </bean>

    <bean id="" factory-bean="" factory-method="build" />

    <bean id="" class="">
        <constructor-arg ref=""/>
        <!--连接超时时间,毫秒-->
        <property name="connectTimeout" value="2000"/>
        <!--读写超时时间,毫秒-->
        <property name="readTimeout" value="5000"/>
    </bean>

    <bean  class="">
        <constructor-arg ref=""/>
        <property name="errorHandler">
            <bean class=""/>
        </property>
        <property name="messageConverters">
            <list>
                <bean class=""/>
                <bean class=""/>
                <bean class="">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/html;charset=UTF-8</value>
                            <value>application/json</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>

本次需求,并没有修改逻辑,为什么会出现这种情况呢?只是网络关系,还是跟代码有关呢。我有几个疑问:

  1. 什么情况下会产生Connection reset?
  2. 长连接中,向server发请求,是先发送数据的,如果连接断开,应该是写数据异常,为什么是读数据异常呢?请求是否发送成功?发送之前有校验连接是否可用吗?
  3. http连接池defaultMaxPerRoute什么意思?每个并发都建立一个长连接吗?
  4. Connection reset之后,如何重新建立连接,继而继续进行业务交互?
  5. RestTemplate中配置了重试,为什么没有重新发起连接?

我们便来解答上面的问题。

问题1,什么情况下会产生Connection reset?

网上搜一下,很多这样的打包附送的答案,如下:

第1个异常是:Address already in use: JVM_Bind。该异常发生在服务器端进行new ServerSocket(port)(port是一个0,65536的整型值)操作时。异常的原因是以为与port一样的一个端口已经被启动,并进行监听。此时用netstat –an命令,可以看到一个Listending状态的端口。只需要找一个没有被占用的端口就能解决这个问题。

第2个异常是: Connection refused: connect。该异常发生在客户端进行new Socket(ip, port)操作时,该异常发生的原因是或者具有ip地址的机器不能找到(也就是说从当前机器不存在到指定ip路由),或者是该ip存在,但找不到指定的端口进行监听。出现该问题,首先检查客户端的ip和port是否写错了,如果正确则从客户端ping一下服务器看是否能ping通,如果能ping通(服务服务器端把ping禁掉则需要另外的办法),则看在服务器端的监听指定端口的程序是否启动,这个肯定能解决这个问题。

第3个异常是: Socket is closed,该异常在客户端和服务器均可能发生。异常的原因是己方主动关闭了连接后(调用了Socket的close方法)再对网络连接进行读写操作。

第4个异常是: (Connection reset或者Connect reset by peer:Socket write error)。该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,第一个就是如果一端的Socket被关闭(或主动关闭或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。简单的说就是在连接断开后的读和写操作引起的。

第5个异常是: Broken pipe。该异常在客户端和服务器均有可能发生。在第4个异常的第一种情况中(也就是抛出SocketExcepton:Connect reset by peer:Socket write error后),如果再继续写数据则抛出该异常。前两个异常的解决方法是首先确保程序退出前关闭所有的网络连接,其次是要检测对方的关闭连接操作,发现对方关闭连接后自己也要关闭该连接。

这里我们关心的是第四个异常,即server已经关闭了连接,client仍然在从连接中读数据。

细节剖析

正常流程(成功日志)剖析

接下来,先逐步debug,分析调用成功的日志。

(URI url, HttpMethod method)

(:?) - Created GET request for "https://domainName/creative/getAuditInfo"
(:?) - Setting request Accept header to [application/json, application/*+json, text/html, application/json]
(:?) - CookieSpec selected: default
(:?) - Auth cache not set in the context

  1. 获取ConnectionRequest (route, userToken);
    调用
(:?) - Connection request: [route: {s}->https://domainName:443][total kept alive: 1; route allocated: 1 of 32; total allocated: 1 of 1000]
  1. 根据ConnectionRequest,获取HttpClientConnection [调用 ]
    获取连接后,校验连接不可用,则关闭连接connection。后面判断connection没有打开,就会重新建立连接
(:?) - http-outgoing-798 << "end of stream" 
 //isStale返回true     
(:?) - http-outgoing-798: Close connection    
(:?) - Connection leased: [id: 799][route: {s}->https://domainName:443][total kept alive: 0; route allocated: 1 of 32; total allocated: 1 of 1000]

3.如果managedConn没有打开, 则建立路由 establishRoute

(:?) - Opening connection {s}->https://domainName:443

调用
调用 遍历地址集,新建socket,并与connection绑定,成功即返回。 以下是建立连接的过程

(:?) - Connecting to domainName/0.0.0.0:443

调用 sock = (connectTimeout, sock, host, remoteAddress, localAddress, context); //建立socket
(sock); //绑定socket

(:?) - Connecting socket to domainName/0.0.0.0:443 with timeout 2000
(:?) - Enabled protocols: [TLSv1]
(:?) - Enabled cipher suites:[]  //()

调用 ();

(:?) - Starting handshake

调用 (sslsock, ()); session可用,打印信息

(:?) - Secure session established
(:?) -  negotiated protocol: TLSv1  
(:?) -  negotiated cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
(:?) -  peer principal: CN=*., OU=IT, O="Beijing Autohome Information Technology Co., Ltd.", L=Beijing, ST=Beijing, C=CN
(:?) -  issuer principal: CN=Symantec Class 3 Secure Server CA - G4, OU=Symantec Trust Network, O=Symantec Corporation, C=US

调用

(:?) - Connection established localhost:4357<->0.0.0.0:443
  1. 设置socket超时时间 (timeout);
(:?) - http-outgoing-799: set socket timeout to 5000
  1. 开始处理请求 根据request头部参数,作相应操作
(:?) - Executing request GET /creative/getAuditInfo HTTP/1.1
(:?) - Target auth state: UNCHALLENGED
(:?) - Proxy auth state: UNCHALLENGED
  1. 处理请求 (request, managedConn, context);
    调用:
    主要功能:
  HttpResponse response = doSendRequest(request, conn, context);
  if (response == null) {
      response = doReceiveResponse(request, conn, context);
  }

6.1 doSendRequest
6.1.1 发送请求头部
调用 (确保socket有效,然后向缓存写请求头部,写完后打印以下日志)

(:?) - http-outgoing-799 >> GET /creative/getAuditInfo HTTP/1.1
(:?) - http-outgoing-799 >> Accept: application/json, application/*+json, text/html, application/json
(:?) - http-outgoing-799 >> User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36
(:?) - http-outgoing-799 >> Accept-Encoding: gzip,deflate
(:?) - http-outgoing-799 >> Accept-Language: zh-CN
(:?) - http-outgoing-799 >> Connection: keep-alive
(:?) - http-outgoing-799 >> Host: domainName

6.1.2 发送请求头部之后,如果请求带有entity,则继续发送entity,即。这里对HTTP 1.0协议做了兼容判断

6.1.3 通过connection将所有缓存数据发送到服务端,并记录日志如下: (headers和wire的日志稍有不同。)

(:?) - http-outgoing-799 >> "GET /creative/getAuditInfo HTTP/1.1[\r][\n]"
(:?) - http-outgoing-799 >> "Accept: application/json, application/*+json, text/html, application/json[\r][\n]"
(:?) - http-outgoing-799 >> "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36[\r][\n]"
(:?) - http-outgoing-799 >> "Accept-Encoding: gzip,deflate[\r][\n]"
(:?) - http-outgoing-799 >> "Accept-Language: zh-CN[\r][\n]"
(:?) - http-outgoing-799 >> "Connection: keep-alive[\r][\n]"
(:?) - http-outgoing-799 >> "Host: domainName[\r][\n]"
(:?) - http-outgoing-799 >> "[\r][\n]"

6.2 doReceiveResponse 如果response为空,则接收服务端的响应

 response = ();
 if (canResponseHaveBody(request, response)) {
     (response);
 }

6.2.1 接收响应头 receiveResponseHeader
--- 读入数据,头部和数据体,第一次读取内容【这里数据包读了两次】 << 表示接收数据,从输入流读入

(:?) - http-outgoing-799 << "HTTP/1.1 200 OK[\r][\n]"
(:?) - http-outgoing-799 << "Server: 10.29[\r][\n]"
(:?) - http-outgoing-799 << "Date: Wed, 22 Mar 2017 01:50:00 GMT[\r][\n]"
(:?) - http-outgoing-799 << "Content-Type: application/json;charset=UTF-8[\r][\n]"
(:?) - http-outgoing-799 << "Transfer-Encoding: chunked[\r][\n]"
(:?) - http-outgoing-799 << "Connection: keep-alive[\r][\n]"
(:?) - http-outgoing-799 << "Pragma: no-cache[\r][\n]"
(:?) - http-outgoing-799 << "Cache-Control: no-cache, no-store, max-age=0[\r][\n]"
(:?) - http-outgoing-799 << "Expires: Thu, 01 Jan 1970 00:00:00 GMT[\r][\n]"
(:?) - http-outgoing-799 << "Content-Language: zh-CN[\r][\n]"
(:?) - http-outgoing-799 << "[\r][\n]"
(:?) - http-outgoing-799 << "6f7[\r][\n]"
(:?) - http-outgoing-799 << "{"data":{......... data1

--- 接收完之后,打印日志

(:?) - http-outgoing-799 << HTTP/1.1 200 OK
(:?) - http-outgoing-799 << Server: 10.29
(:?) - http-outgoing-799 << Date: Wed, 22 Mar 2017 01:50:00 GMT
(:?) - http-outgoing-799 << Content-Type: application/json;charset=UTF-8
(:?) - http-outgoing-799 << Transfer-Encoding: chunked
(:?) - http-outgoing-799 << Connection: keep-alive
(:?) - http-outgoing-799 << Pragma: no-cache
(:?) - http-outgoing-799 << Cache-Control: no-cache, no-store, max-age=0
(:?) - http-outgoing-799 << Expires: Thu, 01 Jan 1970 00:00:00 GMT
(:?) - http-outgoing-799 << Content-Language: zh-CN

6.2.2 如果响应含有消息体,则接收消息体

  1. 检查reuse策略和keepAlive策略,设置connection属性。 这里永久保留
    (:?) - Connection can be kept alive indefinitely
  1. 处理请求response = (); 日志如上
    2.如果响应没有出错,则打印以下日志
(:?) - GET request for "https://domainName/creative/getAuditInfo?" resulted in 200 (OK)
  1. 读取扩展字段 extractData

3.1使用HttpMessageConverter读取并解析数据
调用方法,从输入流中读取数据

(:?) - Reading [] as "application/json;charset=UTF-8" using [@56a00a64]

3.2如果上一次的数据没有读全,这里会接着从输入流读取数据

(:?) - http-outgoing-799 << "......  data2"
(:?) - http-outgoing-799 << "[\r][\n]"
(:?) - http-outgoing-799 << "0[\r][\n]"
(:?) - http-outgoing-799 << "[\r][\n]"

3.3 数据读取完毕,就释放连接
调用

(:?) - Connection [id: 799][route: {s}->https://domainName:443] can be kept alive indefinitely
(:?) - Connection released: [id: 799][route: {s}->https://domainName:443][total kept alive: 1; route allocated: 1 of 32; total allocated: 1 of 1000]

RestTemplate调用完毕

(:?) - getAuditResult .....

总结一下流程:

  • 创建连接请求
  • 根据连接请求的参数,从连接池中获取一个连接
  • 如果连接没有打开,则创建一个底层的socket连接。
  • 设置socket超时时间
  • 发送请求头部(如果请求中带有entity,则发送)
  • 接收响应(先接收头部,如果有主体,则接收)
  • 读取扩展数据(使用HttpMessageConverter读取并解析数据,读取完成后,关闭输入流及释放连接池中的连接)
  • 调用完毕,返回数据
本次异常日志剖析

接下来,查看失败日志:
(URI url, HttpMethod method)

 (:?) - Created GET request for "https://domain/creative/getAuditInfo"
 (:?) - Setting request Accept header to [application/json, application/*+json, text/html, application/json]
 (:?) - CookieSpec selected: default
 (:?) - Auth cache not set in the context

  1. 获取ConnectionRequest (route, userToken);
    调用
 (:?) - Connection request: [route: {s}->https://domain:443][total kept alive: 1; route allocated: 1 of 32; total allocated: 1 of 1000]

2.根据ConnectionRequest,获取HttpClientConnection
调用
其间获取entry时,校验connection().isStale()。

    public boolean isStale() {
        if (!isOpen()) {
            return true;
        }
        try {
            //如果测试结果返回-1说明不可用
            final int bytesRead = fillInputBuffer(1);
            return bytesRead < 0;
        } catch (final SocketTimeoutException ex) {
            //注意这里SocketTimeoutException时,认为是可用的
            return false;
        } catch (final IOException ex) {
            //有I/O异常,不可用
            return true;
        }
    }

这里读超时,返回连接可用。于是后面,就没有关闭连接。也就没有重新建立新连接。具体参见这篇文章

 (:?) - http-outgoing-766 << "[read] I/O error: Read timed out"
 (:?) - Connection leased: [id: 766][route: {s}->https://domain:443][total kept alive: 0; route allocated: 1 of 32; total allocated: 1 of 1000]

设置socket超时时间 (timeout);

 (:?) - http-outgoing-766: set socket timeout to 5000

处理请求,并发送请求数据。处理过程同成功日志中的流程。

 (:?) - Executing request GET /creative/getAuditInfo HTTP/1.1
 (:?) - Target auth state: UNCHALLENGED
 (:?) - Proxy auth state: UNCHALLENGED
 (:?) - http-outgoing-766 >> GET /creative/getAuditInfo HTTP/1.1
 (:?) - http-outgoing-766 >> Accept: application/json, application/*+json, text/html, application/json
 (:?) - http-outgoing-766 >> User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36
 (:?) - http-outgoing-766 >> Accept-Encoding: gzip,deflate
 (:?) - http-outgoing-766 >> Accept-Language: zh-CN
 (:?) - http-outgoing-766 >> Connection: keep-alive
 (:?) - http-outgoing-766 >> Host: domain
 (:?) - http-outgoing-766 >> "GET /creative/getAuditInfo HTTP/1.1[\r][\n]"
 (:?) - http-outgoing-766 >> "Accept: application/json, application/*+json, text/html, application/json[\r][\n]"
 (:?) - http-outgoing-766 >> "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36[\r][\n]"
 (:?) - http-outgoing-766 >> "Accept-Encoding: gzip,deflate[\r][\n]"
 (:?) - http-outgoing-766 >> "Accept-Language: zh-CN[\r][\n]"
 (:?) - http-outgoing-766 >> "Connection: keep-alive[\r][\n]"
 (:?) - http-outgoing-766 >> "Host: domain[\r][\n]"
 (:?) - http-outgoing-766 >> "[\r][\n]"

接收相应数据时,出现I/O异常,关闭连接,并向上抛出异常。

 (:?) - http-outgoing-766 << "[read] I/O error: Connection reset"
 (:?) - http-outgoing-766: Close connection
 (:?) - http-outgoing-766: Shutdown connection
 (:?) - Connection discarded
 (:?) - Connection released: [id: 766][route: {s}->https://domain:443][total kept alive: 0; route allocated: 0 of 32; total allocated: 0 of 1000]
 (:169) - getAuditResult exception, call adx api failed. msg:I/O error on GET request for "https://domain/creative/getAuditInfo":Connection reset; nested exception is : Connection reset

对比失败与成功的日志,发现问题在于获取connection的时候,校验connection是否可用的操作上。此时服务器因为不可知的原因断开了连接(服务端不可以向客户端发数据),这里应该是没有按照正常流程进行四次挥手,所以客户端还保持着连接(可以向服务端发数据,但收不到数据)。测试连接时,客户端读超时(必然的),但此时认为连接可用,实际上不可用(不知道这里是不是认为给的1ms探测时间太短了,允许读超时?),然后就没有重新建立连接。将错误操作延迟到读取请求这一步。

现在可以回答前面的问题

  • 长连接中,向server发请求,是先发送数据的,如果连接断开,应该是写数据异常,为什么是读数据异常呢?请求是否发送成功?发送之前有校验连接是否可用吗?
    本次异常发生在发送完请求,读取response的时候,所以是read异常。既然服务端连接断掉,请求应该是没有发送成功。发送之前有检查连接是否可用,然而检查认为连接可用。

  • http连接池defaultMaxPerRoute什么意思?每个并发都建立一个长连接吗?
    为每一个路由建立一个连接池,连接数最大为defaultMaxPerRoute。每一个连接都是一个socket连接。如果配置为长连接,则是长连接。
  • Connection reset之后,如何重新建立连接,继而继续进行业务交互?
    上面的分析,connection reset之后,把有问题的连接关闭掉了,所以,后面不会再使用这个连接,只要重试,一般是可以成功的。
  • RestTemplate中配置了重试,为什么没有重新发起连接?
    在的execute(调用MainClientExec的execute,但是被RestTemplate的doExecute调用)中,有如下重试处理:

    public CloseableHttpResponse execute(final HttpRoute route,
            final HttpRequestWrapper request, final HttpClientContext context,
            final HttpExecutionAware execAware) throws IOException, HttpException {
        ......

        //不停重试,停下的判断在
        for (int execCount = 1;; execCount++) {
            try {
                //执行操作
                return (route, request, context, execAware);
            } catch (final IOException ex) {
                ......

                //判断是否符合重试的条件   
                if ((ex, execCount, context)) {
                    //重试的话就打印日志
                    if (()) {
                        ("I/O exception ("+ ().getName() +
                                ") caught when processing request to "
                                + route + ": " + ());
                    }
                    //是否可以重试
                    if (!(request)) {
                        ("Cannot retry non-repeatable request");
                        throw new NonRepeatableRequestException("Cannot retry request " +
                                "with a non-repeatable request entity", ex);
                    }
                    (origheaders);
                } else {
                    //不符合重试条件,就抛出异常
                    if (ex instanceof NoHttpResponseException) {
                        final NoHttpResponseException updatedex = new NoHttpResponseException(
                                ().toHostString() + " failed to respond");
                        (());
                        throw updatedex;
                    } else {
                        throw ex;
                    }
                }
            }
        }
    }

上一个版本中,没有connection reset的Error日志,这一个版本就有了。如果说新版本上线之后网络才有问题,可能性小。所以,以前和现在都会偶尔有网络问题产生。
如果之前版本有网络问题,为什么没有Error日志呢?是否有重试呢?是否通过重试补救了呢?
查看一下以前的info日志,发现的确有重试,也的确补救成功(没有类似的Connection reset报出)。

//3.11
(:?) - I/O exception () caught when processing request to {s}->https://domainName:443: Connection reset

//3.12
(:?) - I/O exception () caught when processing request to {s}->https://domainName:443: Connection reset

然后查看新版本上线之后的日志,没有发现重试日志,说明SocketException能被DefaultHttpRequestRetryHandler处理,而不能被CustomRequestRetryHandler处理。在对比两者的异常处理类型,发现DefaultHttpRequestRetryHandler处理的ConnectException extends SocketException,所以能处理。因为,我们只要在CustomRequestRetryHandler中,加入SocketException或者ConnectException,就可以通过重试解决网络问题。

至此,我们搞清楚了这个Connection reset的问题,并且找到了解决方案,开心ing~~

补充与参考

几个重要的类说明:

  • MainClientExec
    执行流程核心类,execute方法。这里有源码分析,还不错。
  • PoolingHttpClientConnectionManager
    用来管理connection,方法如:requestConnection(从连接池中lease一个连接,连接的状态可能是关闭的),connect(真正建立底层socket连接),releaseConnection(释放连接池中的连接)等。这里有一篇分析。

  • 如何检查httpConnection连接是否有效

    方法一,的isStale()方法
    public boolean isStale() {
        //没有打开,即socket为空,则不可用
        if (!isOpen()) {
            return true;
        }
        try {
        //socket链路有了,测试链路是否可用
        //这里的测试方法是查看很短的时间内(这里是1ms),是否可以从输入流中读到数据
        //如果测试结果返回-1说明不可用
            final int bytesRead = fillInputBuffer(1);
            return bytesRead < 0;
        } catch (final SocketTimeoutException ex) {
            //注意这里SocketTimeoutException时,认为是可用的
            return false;
        } catch (final IOException ex) {
            //有I/O异常,不可用
            return true;
        }
    }
方法二、的isResponseAvailable方法

有几种实现方法,但大都被弃用。查看的实现。

    @Override
    public boolean isResponseAvailable(final int timeout) throws IOException {
        ensureOpen();
        try {
            return awaitInput(timeout);
        } catch (final SocketTimeoutException ex) {
           //这里与isStale不同,SocketTimeoutException时认为不可用
            return false;
        }
    }

    protected boolean awaitInput(final int timeout) throws IOException {
        if (()) {
            return true;
        }
        //BHttpConnectionBase 中的fillInputBuffer方法,与isStale的测试方法一致
        fillInputBuffer(timeout);
        return ();
    }

比较两种方法:isResponseAvailable暂时没有找到使用的地方;isStale在中有直接使用。使用的话需要配置staleConnectionCheckEnabled,即每次请求都要去检测(最高耗时30ms)一次,看起来有一点悲观锁的意思,对性能影响比较大。所以4.4版本开始,默认值为false,且功能被标识为过时。这种方法不推荐。

if (()) {
    // validate connection
    if (()) {
        ("Stale connection check");
        if (()) {
            ("Stale connection detected");
            ();
        }
    }
}
方法三:官方推荐的#getValidateAfterInactivity()

对非活动的永久连接,每个validateAfterInactivity毫秒(默认2s)做一次链路检查,尽量确保在使用的时候是可用的。为什么说尽量呢?本文就是一个例子,2s检查没有问题,但在使用之前的2s内网络出了问题,这就没有办法了。

validateAfterInactivity的使用,在中,getPoolEntryBlocking方法。

if ((())) {
    ();
} else if ( > 0) {
    if (() +  <= ()) {
        if (!validate(entry)) {
            ();
        }
    }
}

详细解释在这里