zoukankan      html  css  js  c++  java
  • HttpClient实战二:单线程和多线程连接池实例

    为什么使用HTTP连接池?

    随着系统架构风格逐渐向前后端分离架构,微服务架构转变,RestFul风格API的开发与设计,同时SpringMVC也很好的支持了REST风格接口。各个系统之间服务的调用大多采用HTTP+JSONHTTPS+JSON方式。
    HTTP1.1默认是持久连接,HTTP1.0也可以通过在请求头中设置Connection:keep-alive使得连接成为长连接。既然HTTP协议支持长连接,那么HTTP连接同样可以使用连接池技术来管理和维护连接建立和销毁。 但是由于每次HTTP连接请求实际上都是在传输层建立的TCP连接,利用的socket进行通信,HTTP连接的保持和关闭实际上都同TCP连接的建立和关闭有关,所有每次HTTP请求都有经过TCP连接的三次握手(建立连接)和四次挥手(释放连接)的过程。所以采用HTTP连接池有以下优势:

    • 降低了频繁建立HTTP连接的时间开销,减少了TCP连接建立和释放时socket通信服务器端资源的浪费;
    • 支持更高的并发量;

    常用HttpClient连接池API

    本文使用的是目前最新版本的HttpClient4.5.3,所以下文的内容都是基于该版本书写。

    • PoolingHttpClientConnectionManager连接池管理实现类
      PoolingHttpClientConnectionManager是一个HttpClient连接池实现类,实现了HttpClientConnectionManagerConnPoolControl接口。类层次结构如下图所示:
     
    PoolingHttpClientConnectionManager类层次结构

    构造方法:
    PoolingHttpClientConnectionManager():无参构造方法,从源码中可以看到该方法调用了getDefaultRegistry()来注册http协议和https协议。
    常用方法:
    public void setMaxTotal(int max):该方法定义在ConnPoolControl接口中,表示设置最大连接数为max。
    public void setDefaultMaxPerRoute(int max):该方法也是定义在ConnPoolControl接口中,表示将每个路由的默认最大连接数设置为max
    public void setMaxPerRoute(HttpRoute route,int max):设置某个指定路由的最大连接数,这个配置会覆盖setDefaultMaxPerRoute的某个路由的值。

    • RequestConfig请求参数配置类
    RequestConfig方法与内部类Builder

    常用方法
    static RequestConfig.Builder custom():静态方法,用于构建Builder 对象,然后设置相应的参数;
    int getConnectionRequestTimeout():获取从连接池获取连接的最长时间,单位是毫秒;
    int getConnectTimeout():获取创建连接的最长时间,单位是毫秒;
    int getSocketTimeout():获取数据传输的最长时间,单位是毫秒;

    RequestConfig有一个静态内部类Builder,用于构建RequestConfig对象并设置请求参数,该类有以下常用方法:
    public RequestConfig.Builder setConnectionRequestTimeout(int connectionRequestTimeout):设置从连接池获取连接的最长时间,单位是毫秒;
    public RequestConfig.Builder setConnectTimeout(int connectTimeout):设置创建连接的最长时间,单位是毫秒;
    public RequestConfig.Builder setSocketTimeout(int socketTimeout):设置数据传输的最长时间,单位是毫秒;

    • HttpRequestRetryHandler请求重试接口
      boolean retryRequest(IOException exception, int executionCount, org.apache.http.protocol.HttpContext context):实现该接口的,必须实现该方法,决定了如果一个方法执行时发生了IO异常,是否应该重试,重试executionCount次。

    单线程-使用连接池管理HTTP请求

    主要步骤:

    1. 创建HTTP的连接池管理对象cm,如下所示
    1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();

    2.设置最大连接数和每个路由的默认最大连接数等参数,如下所示

    1 //将最大连接数增加到200
    2  cm.setMaxTotal(200);
    3 //将每个路由的默认最大连接数增加到20
    4  cm.setDefaultMaxPerRoute(20);

    3.模拟发送HttpGet请求或者HttpPost请求,注意这里创建HttpClients对象的时候需要从连接池中获取,即要设置连接池对象,当执行发生IO异常需要处理时,要实现HttpRequestRetryHandler接口;需要注意的是这里处理完请求响应后,不能关闭HttpClient对象,如果关闭连接池也会销毁。HttpClients对象创建对象所示:

    1  CloseableHttpClient httpClient = HttpClients.custom()
    2                 .setConnectionManager(connectionManager)
    3                 .setRetryHandler(retryHandler(5)).build();

    4.可以开启线程来监听连接池中空闲连接,并清理无效连接,线程监听频率可以自行设置。
    Java实现源码:

      1 package com.liangpj.develop.httpclient;
      2 import org.apache.http.HttpEntityEnclosingRequest;
      3 import org.apache.http.HttpRequest;
      4 import org.apache.http.NoHttpResponseException;
      5 import org.apache.http.client.HttpRequestRetryHandler;
      6 import org.apache.http.client.config.RequestConfig;
      7 import org.apache.http.client.methods.CloseableHttpResponse;
      8 import org.apache.http.client.methods.HttpGet;
      9 import org.apache.http.client.protocol.HttpClientContext;
     10 import org.apache.http.conn.ConnectTimeoutException;
     11 import org.apache.http.conn.HttpClientConnectionManager;
     12 import org.apache.http.impl.client.CloseableHttpClient;
     13 import org.apache.http.impl.client.HttpClients;
     14 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
     15 import org.apache.http.protocol.HttpContext;
     16 import org.apache.http.util.EntityUtils;
     17 import javax.net.ssl.SSLException;
     18 import javax.net.ssl.SSLHandshakeException;
     19 import java.io.IOException;
     20 import java.io.InterruptedIOException;
     21 import java.net.UnknownHostException;
     22 
     23 /**
     24  * 单线程-使用连接池管理HTTP请求
     25  * @author: liangpengju
     26  * @version: 1.0
     27  */
     28 public class HttpConnectManager {
     29 
     30     public static void main(String[] args) throws Exception {
     31         //创建HTTP的连接池管理对象
     32         PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
     33         //将最大连接数增加到200
     34         connectionManager.setMaxTotal(200);
     35         //将每个路由的默认最大连接数增加到20
     36         connectionManager.setDefaultMaxPerRoute(20);
     37         //将http://www.baidu.com:80的最大连接增加到50
     38         //HttpHost httpHost = new HttpHost("http://www.baidu.com",80);
     39         //connectionManager.setMaxPerRoute(new HttpRoute(httpHost),50);
     40 
     41         //发起3次GET请求
     42         String url ="https://www.baidu.com/s?word=java";
     43         long start = System.currentTimeMillis();
     44         for (int i=0;i<100;i++){
     45             doGet(connectionManager,url);
     46         }
     47         long end = System.currentTimeMillis();
     48         System.out.println("consume -> " + (end - start));
     49 
     50         //清理无效连接
     51         new IdleConnectionEvictor(connectionManager).start();
     52     }
     53 
     54     /**
     55      * 请求重试处理
     56      * @param tryTimes 重试次数
     57      * @return
     58      */
     59     public static HttpRequestRetryHandler retryHandler(final int tryTimes){
     60 
     61         HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
     62             @Override
     63             public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
     64                 // 如果已经重试了n次,就放弃
     65                 if (executionCount >= tryTimes) {
     66                     return false;
     67                 }
     68                 // 如果服务器丢掉了连接,那么就重试
     69                 if (exception instanceof NoHttpResponseException) {
     70                     return true;
     71                 }
     72                 // 不要重试SSL握手异常
     73                 if (exception instanceof SSLHandshakeException) {
     74                     return false;
     75                 }
     76                 // 超时
     77                 if (exception instanceof InterruptedIOException) {
     78                     return false;
     79                 }
     80                 // 目标服务器不可达
     81                 if (exception instanceof UnknownHostException) {
     82                     return true;
     83                 }
     84                 // 连接被拒绝
     85                 if (exception instanceof ConnectTimeoutException) {
     86                     return false;
     87                 }
     88                 // SSL握手异常
     89                 if (exception instanceof SSLException) {
     90                     return false;
     91                 }
     92                 HttpClientContext clientContext = HttpClientContext .adapt(context);
     93                 HttpRequest request = clientContext.getRequest();
     94                 // 如果请求是幂等的,就再次尝试
     95                 if (!(request instanceof HttpEntityEnclosingRequest)) {
     96                     return true;
     97                 }
     98                 return false;
     99             }
    100         };
    101         return httpRequestRetryHandler;
    102     }
    103 
    104     /**
    105      * doGet
    106      * @param url 请求地址
    107      * @param connectionManager
    108      * @throws Exception
    109      */
    110     public static void doGet(HttpClientConnectionManager connectionManager,String url) throws Exception {
    111         //从连接池中获取client对象,多例
    112         CloseableHttpClient httpClient = HttpClients.custom()
    113                 .setConnectionManager(connectionManager)
    114                 .setRetryHandler(retryHandler(5)).build();
    115 
    116         // 创建http GET请求
    117         HttpGet httpGet = new HttpGet(url);
    118         // 构建请求配置信息
    119         RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) // 创建连接的最长时间
    120                 .setConnectionRequestTimeout(500) // 从连接池中获取到连接的最长时间
    121                 .setSocketTimeout(10 * 1000) // 数据传输的最长时间10s
    122                 .setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用
    123                 .build();
    124         // 设置请求配置信息
    125         httpGet.setConfig(config);
    126 
    127         CloseableHttpResponse response = null;
    128         try {
    129             // 执行请求
    130             response = httpClient.execute(httpGet);
    131             // 判断返回状态是否为200
    132             if (response.getStatusLine().getStatusCode() == 200) {
    133                 String content = EntityUtils.toString(response.getEntity(), "UTF-8");
    134                 System.out.println("内容长度:" + content.length());
    135             }
    136         } finally {
    137             if (response != null) {
    138                 response.close();
    139             }
    140             // 此处不能关闭httpClient,如果关闭httpClient,连接池也会销毁
    141             // httpClient.close();
    142         }
    143     }
    144 
    145     /**
    146      * 监听连接池中空闲连接,清理无效连接
    147      */
    148     public static class IdleConnectionEvictor extends Thread {
    149 
    150         private final HttpClientConnectionManager connectionManager;
    151 
    152         private volatile boolean shutdown;
    153 
    154         public IdleConnectionEvictor(HttpClientConnectionManager connectionManager) {
    155             this.connectionManager = connectionManager;
    156         }
    157 
    158         @Override
    159         public void run() {
    160             try {
    161                 while (!shutdown) {
    162                     synchronized (this) {
    163                         //3s检查一次
    164                         wait(3000);
    165                         // 关闭失效的连接
    166                         connectionManager.closeExpiredConnections();
    167                     }
    168                 }
    169             } catch (InterruptedException ex) {
    170                 // 结束
    171                 ex.printStackTrace();
    172             }
    173         }
    174 
    175         public void shutdown() {
    176             shutdown = true;
    177             synchronized (this) {
    178                 notifyAll();
    179             }
    180         }
    181     }
    182 }

    多线程-HttpClient连接池管理HTTP请求实例

    主要步骤:

    1. 创建HTTP的连接池管理对象cm,如下所示
    1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();

    2.设置最大连接数和每个路由的默认最大连接数等参数,如下所示

    1  //将最大连接数增加到200
    2  cm.setMaxTotal(200);
    3 //将每个路由的默认最大连接数增加到20
    4  cm.setDefaultMaxPerRoute(20);

    3.创建HttpClients对象的并设置连接池对象,HttpClients对象创建对象所示:

    1 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();

    4.继承Thread类实现一个执行Get请求线程类GetThread,重载run()方法,执行HttpGet请求;
    5.定义要请求的URI地址为数组形式,为每一个URI创建一个GetThread线程,并启动所有线程;
    Java实现源码:

     1 package com.liangpj.develop.httpclient;
     2 import org.apache.http.HttpEntity;
     3 import org.apache.http.client.ClientProtocolException;
     4 import org.apache.http.client.methods.CloseableHttpResponse;
     5 import org.apache.http.client.methods.HttpGet;
     6 import org.apache.http.client.protocol.HttpClientContext;
     7 import org.apache.http.impl.client.CloseableHttpClient;
     8 import org.apache.http.impl.client.HttpClients;
     9 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
    10 import org.apache.http.protocol.HttpContext;
    11 import java.io.IOException;
    12 
    13 /**
    14  * 多线程-HttpClient连接池管理HTTP请求实例
    15  */
    16 public class MultiThreadHttpConnManager {
    17     public static void main(String[] args) {
    18         //连接池对象
    19         PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    20       //将最大连接数增加到200
    21         connectionManager.setMaxTotal(200);
    22         //将每个路由的默认最大连接数增加到20
    23         connectionManager.setDefaultMaxPerRoute(20);
    24         //HttpClient对象
    25         CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
    26         //URIs to DoGet
    27         String[] urisToGet = {
    28                 "https://www.baidu.com/s?word=java",
    29                 "https://www.baidu.com/s?word=java",
    30                 "https://www.baidu.com/s?word=java",
    31                 "https://www.baidu.com/s?word=java"
    32         };
    33         //为每一个URI创建一个线程
    34         GetThread[] threads = new GetThread[urisToGet.length];
    35         for (int i=0;i<threads.length;i++){
    36             HttpGet httpGet = new HttpGet(urisToGet[i]);
    37             threads[i] = new GetThread(httpClient,httpGet);
    38         }
    39         //启动线程
    40         for (int j=0;j<threads.length;j++){
    41             threads[j].start();
    42         }
    43         //join 线程
    44         for(int k=0;k<threads.length;k++){
    45             try {
    46                 threads[k].join();
    47             } catch (InterruptedException e) {
    48                 e.printStackTrace();
    49             }
    50         }
    51     }
    52 
    53     /**
    54      * 执行Get请求线程
    55      */
    56     public static class GetThread extends Thread{
    57         private final CloseableHttpClient httpClient;
    58         private final HttpContext context;
    59         private final HttpGet httpget;
    60         public GetThread(CloseableHttpClient httpClient, HttpGet httpget) {
    61             this.httpClient = httpClient;
    62             this.context = HttpClientContext.create();
    63             this.httpget = httpget;
    64         }
    65         @Override
    66         public void run() {
    67             try {
    68                 CloseableHttpResponse response = httpClient.execute(httpget,context);
    69                 try {
    70                     HttpEntity entity = response.getEntity();
    71                 }finally {
    72                     response.close();
    73                 }
    74             }catch (ClientProtocolException ex){
    75                 //处理客户端协议异常
    76             }catch (IOException ex){
    77                 //处理客户端IO异常
    78             }
    79         }
    80     }
    81 }

    想要获取HttpClient实战的所有实例代码,可以关注Java技术日志订阅号后,在消息框回复关键字:httpclient可以获取代码的地址。



    转自:https://www.jianshu.com/p/187dde35336d

  • 相关阅读:
    20155303 2016-2017-2 《Java程序设计》第四周学习总结
    20155303 2016-2017-2 《Java程序设计》第三周学习总结
    20155303 2016-2017-2 《Java程序设计》第二周学习总结
    20155303 2016-2017-2 《Java程序设计》第一周学习总结
    20155303狄惟佳预备作业三Linux学习笔记
    《做中学》读后有感
    路行致远,砥砺前行
    如何使用Git和码云Git@OSC
    2017《Java技术》预备作业01
    C语言程序设计第十一次作业
  • 原文地址:https://www.cnblogs.com/fnlingnzb-learner/p/10833004.html
Copyright © 2011-2022 走看看