分享

HttpClient容易忽视的细节

 关平藏书 2019-05-13

Apache commons 系列的HttpClient 相信大家都用过,选择它而非JDK 的java.net.HttpURLConnection ,是为了使用HttpClient 封装的几个实用的功能。

目前使用最多的版本还是httpclient-3.x ,在官网http://hc./httpclient-3.x/tutorial.html 有这么一段示例代码:

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.HttpMethodParams;

import java.io.*;

public class HttpClientTutorial {
  
  private static String url = "http://www./";

  public static void main(String[] args) {
    // Create an instance of HttpClient.
    HttpClient client = new HttpClient();

    // Create a method instance.
    GetMethod method = new GetMethod(url);
    
    // Provide custom retry handler is necessary
    method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
    		new DefaultHttpMethodRetryHandler(3, false));

    try {
      // Execute the method.
      int statusCode = client.executeMethod(method);

      if (statusCode != HttpStatus.SC_OK) {
        System.err.println("Method failed: " + method.getStatusLine());
      }

      // Read the response body.
      byte[] responseBody = method.getResponseBody();

      // Deal with the response.
      // Use caution: ensure correct character encoding and is not binary data
      System.out.println(new String(responseBody));

    } catch (HttpException e) {
      System.err.println("Fatal protocol violation: " + e.getMessage());
      e.printStackTrace();
    } catch (IOException e) {
      System.err.println("Fatal transport error: " + e.getMessage());
      e.printStackTrace();
    } finally {
      // Release the connection.
      method.releaseConnection();
    }  
  }
}

大部分人也是以这个为规范来使用的,但是注意有一段关于“Release the Connection”的说明:
This is a crucial step to keep things flowing. 
We must tell HttpClient that we are done with 
the connection and that it can now be reused. 
Without doing this HttpClient will wait 
indefinitely for a connection to free up so 
that it can be reused.
我看得也不是很明白,意思是我们必须在使用后调用
method.releaseConnection();
来告诉HttpClient 这个连接可以重用了。

这个在串行的处理中或许很有用,但是我所遇到的情况是多线程并发下,不能共享同一个HttpClient 实例,按照官方示例写好代码后,程序跑起来似乎没什么问题,但是随着时间的累计,有一天突然发现这个模块不工作了,查看了一下当前的网络连接,这个java 程序同一个地址保持着200多个CLOSE_WAIT 的连接,好吧,连接没有释放。

为什么没有释放?查看doc,有这样的说明:

Releases the connection being used by this HTTP method. 
In particular the connection is used to read the response
(if there is one) and will be held until the response has 
been read. If the connection can be reused by other HTTP 
methods it is NOT closed at this point.

注意最后一句,如果该连接可以重用则不关闭,是“可以重用”,当然可以重用了,就在那儿等着我去重用,可是我都是新建的实例,怎么重用

查看源码,找到HttpClient 的构造方法,有一个可以指定HttpConnectionManager ,然后这个HttpConnectionManager 又有一个实现的构造:

public SimpleHttpConnectionManager(boolean alwaysClose)
The connection manager created with this constructor will 
try to keep the connection open (alive) between consecutive 
requests if the alwaysClose parameter is set to false. 
Otherwise the connection manager will always close 
connections upon release.
Parameters:
alwaysClose - if set true, the connection manager will 
always close connections upon release.

显然alawaysClose 的默认值是false ,在释放后连接并不总是会关闭。

所以,必须

HttpClient client = new HttpClient(new HttpClientParams(),
new SimpleHttpConnectionManager(true));
Java代码  收藏代码
  1. HttpClient client = new HttpClient();  
  2. HttpMethod method = new GetMethod("http://www.");  
  3. try {  
  4.   client.executeMethod(method);  
  5.   byte[] responseBody = null;  
  6.     
  7.   responseBody = method.getResponseBody();  
  8.     
  9. } catch (HttpException e) {  
  10.   // TODO Auto-generated catch block  
  11.   e.printStackTrace();  
  12. } catch (IOException e) {  
  13.   // TODO Auto-generated catch block  
  14.   e.printStackTrace();  
  15. }finally{  
  16.   method.releaseConnection();  
  17.     
  18. }  

大部分人使用HttpClient都是使用类似上面的事例代码,包括Apache官方的例子也是如此。最近我在使用HttpClient是发现一次循环发送大量请求到服务器会导致APACHE服务器的链接被占满,后续的请求便排队等待。
我服务器端APACHE的配置
Java代码  收藏代码
  1. Timeout 30  
  2. KeepAlive On   #表示服务器端不会主动关闭链接  
  3. MaxKeepAliveRequests 100  
  4. KeepAliveTimeout 180   

因此这样的配置就会导致每个链接至少要过180S才会被释放,这样在大量请求访问时就必然会造成链接被占满,请求等待的情况。
在通过DEBUH后发现HttpClient在method.releaseConnection()后并没有把链接关闭,这个方法只是将链接返回给connection manager。如果使用HttpClient client = new HttpClient()实例化一个HttpClient connection manager默认实现是使用SimpleHttpConnectionManager。SimpleHttpConnectionManager有个构造函数如下
Java代码  收藏代码
  1. /** 
  2.  * The connection manager created with this constructor will try to keep the  
  3.  * connection open (alive) between consecutive requests if the alwaysClose  
  4.  * parameter is set to <tt>false</tt>. Otherwise the connection manager will  
  5.  * always close connections upon release. 
  6.  *  
  7.  * @param alwaysClose if set <tt>true</tt>, the connection manager will always 
  8.  *    close connections upon release. 
  9.  */  
  10. public SimpleHttpConnectionManager(boolean alwaysClose) {  
  11.     super();  
  12.     this.alwaysClose = alwaysClose;  
  13. }  

看方法注释我们就可以看到如果alwaysClose设为true在链接释放之后connection manager 就会关闭链。在我们HttpClient client = new HttpClient()这样实例化一个client时connection manager是这样被实例化的
Java代码  收藏代码
  1. this.httpConnectionManager = new SimpleHttpConnectionManager();  

因此alwaysClose默认是false,connection是不会被主动关闭的,因此我们就有了一个客户端关闭链接的方法。
方法一:
把事例代码中的第一行实例化代码改为如下即可,在method.releaseConnection();之后connection manager会关闭connection 。
Java代码  收藏代码
  1. HttpClient client = new HttpClient(new HttpClientParams(),new SimpleHttpConnectionManager(true) );  

方法二:
实例化代码使用:HttpClient client = new HttpClient();
在method.releaseConnection();之后加上
Java代码  收藏代码
  1. ((SimpleHttpConnectionManager)client.getHttpConnectionManager()).shutdown();  

shutdown源代码很简单,看了一目了然
Java代码  收藏代码
  1. public void shutdown() {  
  2.     httpConnection.close();  
  3. }  

方法三:
实例化代码使用:HttpClient client = new HttpClient();
在method.releaseConnection();之后加上
client.getHttpConnectionManager().closeIdleConnections(0);此方法源码代码如下:
Java代码  收藏代码
  1. public void closeIdleConnections(long idleTimeout) {  
  2.     long maxIdleTime = System.currentTimeMillis() - idleTimeout;  
  3.     if (idleStartTime <= maxIdleTime) {  
  4.         httpConnection.close();  
  5.     }  
  6. }  

将idleTimeout设为0可以确保链接被关闭。
以上这三种方法都是有客户端主动关闭TCP链接的方法。下面再介绍由服务器端自动关闭链接的方法。
方法四:
代码实现很简单,所有代码就和最上面的事例代码一样。只需要在HttpMethod method = new GetMethod("http://www.");加上一行HTTP头的设置即可
Java代码  收藏代码
  1. method.setRequestHeader("Connection", "close");  

看一下HTTP协议中关于这个属性的定义:
HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response. For example,
       Connection: close
现在再说一下客户端关闭链接和服务器端关闭链接的区别。如果采用客户端关闭链接的方法,在客户端的机器上使用netstat –an命令会看到很多TIME_WAIT的TCP链接。如果服务器端主动关闭链接这中情况就出现在服务器端。
参考WIKI上的说明http://wiki./HttpComponents/FrequentlyAskedConnectionManagementQuestions
The TIME_WAIT state is a protection mechanism in TCP. The side that closes a socket connection orderly will keep the connection in state TIME_WAIT for some time, typically between 1 and 4 minutes.
TIME_WAIT的状态会出现在主动关闭链接的这一端。TCP协议中TIME_WAIT状态主要是为了保证数据的完整传输。具体可以参考此文档:
http://www.softlab./facilities/documentation/unix/unix-socket-faq/unix-socket-faq-2.html#ss2.7
另外强调一下使用上面这些方法关闭链接是在我们的应用中明确知道不需要重用链接时可以主动关闭链接来释放资源。如果你的应用是需要重用链接的话就没必要这么做,使用原有的链接还可以提供性能。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多