PoolingHttpClientConnectionManager是通过租用连接和收回链接的方式来实现的。解决了http请求的多线程问题。

依赖: 

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

实现

首先需要初始化连接池。设定链接数量等,定义清理连接池的方法,并定时调用这个方法清理无效或者长期未使用的链接。

public class HttpClientTest {
    //全局参数
    private static PoolingHttpClientConnectionManager connectionManager = null;
    //设置请求参数
    private RequestConfig config;

    private CloseableHttpClient client;

    //单例模式创建
    private void init(){
        synchronized (HttpClientTest.class) {
            if (client == null) {
                connectionManager = new PoolingHttpClientConnectionManager();
                // http请求线程池,最大连接数
                int requestMaxNum = 5000;
                ConnectionConfig connConfig = ConnectionConfig.custom().setCharset(Charset.forName("utf-8")).build();
                SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(5000).build();
                connectionManager.setDefaultConnectionConfig(connConfig);
                connectionManager.setDefaultSocketConfig(socketConfig);
                // 连接池最大生成连接数
                connectionManager.setMaxTotal(requestMaxNum);
                // 默认设置route最大连接数
                connectionManager.setDefaultMaxPerRoute(requestMaxNum);
                //设置请求参数
                config = RequestConfig.custom().setConnectTimeout(5000) //连接超时时间
                        .setConnectionRequestTimeout(500) //从线程池中获取线程超时时间
                        .setSocketTimeout(5000) //设置数据超时时间
                        .build();
                // 创建builder
                HttpClientBuilder builder = HttpClients.custom();
                //管理器是共享的,它的生命周期将由调用者管理,并且不会关闭
                //否则可能出现Connection pool shut down异常
                builder.setConnectionManager(connectionManager).setConnectionManagerShared(true);
                // 长连接策略
                builder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
                // 创建httpClient
                client = builder.setDefaultRequestConfig(config).setRetryHandler(new MyRetryHandle()).build();
            }
        }
    }

    /**
     * 从池子中获取连接
     * @return CloseableHttpClient
     */
    private CloseableHttpClient getClientFromHttpPool() {
        if(client == null) {
            init();
        }
        return client;
    }

}

定时回收链接

// 启动定时器,定时回收过期的连接
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
            // 关闭过期的链接
            connectionManager.closeExpiredConnections();
            // 选择关闭 空闲30秒的链接
            connectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
            }
        }, 10 * 1000, 5 * 1000);

实现两个方法,执行httpqing请求。 

public String getTest(String url) {
        CloseableHttpClient client = getClientFromHttpPool();
        HttpGet method = new HttpGet(url);
        //设置请求头
        //method.setRequestHeader();
        long start = System.currentTimeMillis();
        BufferedReader reader = null;
        InputStream inputStream = null;
        StringBuilder result = new StringBuilder("");
        try {
            long startTime = System.currentTimeMillis();
            CloseableHttpResponse response = client.execute(method);
            if(response.getStatusLine().getStatusCode() == 200){
                HttpEntity httpEntity = response.getEntity();
                inputStream = httpEntity.getContent();
                //解析方式1
                reader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                String ret = sb.toString();
                System.out.println(ret);

                //解析方式二
                byte[] bytes = new byte[1024];
                int length = 0;
                while(-1 != (length = inputStream.read(bytes))) {
                    result.append(new String(bytes, 0, length));
                }
            }else {
                System.out.println("请求失败,返回" + response.getStatusLine().getStatusCode());
            }
            System.out.println("用时:" + (System.currentTimeMillis() - startTime));
            return result.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                }catch (Exception e){
                }
            }
            if(reader != null){
                try {
                reader.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            //释放连接  
            // httpClient必须releaseConnection,但不是abort。因为releaseconnection是归还连接到到连接池,而abort是直接抛弃这个连接,而且占用连接池的数目。
            method.releaseConnection();
        }
        System.out.println("end..Duration MS:" + (System.currentTimeMillis() - start));
        return null;
    }

    public void postTest(String url, String param) {
        CloseableHttpClient client = getClientFromHttpPool();
        HttpPost method = new HttpPost(url);
        //设置请求头
        //method.setRequestHeader();
        //设置请求参数--可以用各种方式传入参数
        try {
            method.setEntity(new StringEntity(param));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        long start = System.currentTimeMillis();
        BufferedReader reader = null;
        InputStream inputStream = null;
        try {
            CloseableHttpResponse response = client.execute(method);
            if(response.getStatusLine().getStatusCode() == 200){
                HttpEntity httpEntity = response.getEntity();
                inputStream = httpEntity.getContent();
                //解析方式1
                reader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                String ret = sb.toString();
                System.out.println(ret);

                //解析方式二
                byte[] bytes = new byte[1024];
                int length = 0;
                StringBuilder result = new StringBuilder("");
                while(-1 != (length = inputStream.read(bytes))) {
                    result.append(new String(bytes, 0, length));
                }
                System.out.println(result);
            }else {
                System.out.println("请求失败,返回" + response.getStatusLine().getStatusCode());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                }catch (Exception e){
                }
            }
            if(reader != null){
                try {
                    reader.close();
                }catch (Exception e){
                }
            }
            //关闭池子
           /* if(client != null){
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
            //释放连接
            method.releaseConnection();
        }
        System.out.println("end..Duration MS:" + (System.currentTimeMillis() - start));
    }

重试策略:

加入重试策略:

// 创建httpClient
        return builder.setDefaultRequestConfig(config).setRetryHandler(new MyRetryHandle()).build();
/**
 * 请求连接池失败重试策略
 */
public class MyRetryHandle implements HttpRequestRetryHandler {
    Logger logger = LoggerFactory.getLogger(MyRetryHandle.class);
    //请求失败时,进行请求重试
    @Override
    public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
        if (i > 3) {
            //重试超过3次,放弃请求
            logger.error("retry has more than 3 time, give up request");
            return false;
        }
        if (e instanceof NoHttpResponseException) {
            //服务器没有响应,可能是服务器断开了连接,应该重试
            logger.error("receive no response from server, retry");
            return true;
        }
        if (e instanceof SSLHandshakeException) {
            // SSL握手异常
            logger.error("SSL hand shake exception");
            return false;
        }
        if (e instanceof InterruptedIOException) {
            //超时
            logger.error("InterruptedIOException");
            return false;
        }
        if (e instanceof UnknownHostException) {
            // 服务器不可达
            logger.error("server host unknown");
            return false;
        }
        if (e instanceof ConnectTimeoutException) {
            // 连接超时
            logger.error("Connection Time out");
            return false;
        }
        if (e instanceof SSLException) {
            logger.error("SSLException");
            return false;
        }

        HttpClientContext context = HttpClientContext.adapt(httpContext);
        HttpRequest request = context.getRequest();
        if (!(request instanceof HttpEntityEnclosingRequest)) {
            //如果请求不是关闭连接的请求
            return true;
        }
        return false;
    }
}

 

三、原理及注意事项

连接池中连接都是在发起请求的时候建立,并且都是长连接

HaoMaiClient.java中的in.close();作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。

连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中

http://hc.apache.org/ 文档以及源码

https://blog.csdn.net/u014133299/article/details/80676147

https://segmentfault.com/a/1190000012343705

https://blog.csdn.net/szwandcj/article/details/51291967

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐