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

  • 相关阅读:
    【NOIP2007】守望者的逃离
    20200321(ABC)题解 by 马鸿儒 孙晨曦
    20200320(ABC)题解 by 王一帆
    20200319(ABC)题解 by 王一帆 梁延杰 丁智辰
    20200314(ABC)题解 by 董国梁 蒋丽君 章思航
    20200309(ABC)题解 by 梁延杰
    20200307(DEF)题解 by 孙晨曦
    20200306(ABC)题解 by 孙晨曦
    20200305(DEF)题解 by 孙晨曦
    20200303(ABC)题解 by 王锐,董国梁
  • 原文地址:https://www.cnblogs.com/fnlingnzb-learner/p/10833004.html
Copyright © 2011-2022 走看看