PoolingHttpClientConnectionManager实现http连接池

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

 

  • 10
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java自带的HTTP库是通过URLConnection实现的,它并没有提供连接池的功能。但是,我们可以通过自己编写代码实现连接池。 具体实现方法如下: 1. 创建一个连接池类,用于管理HTTP连接。 2. 在连接池类中创建一个队列,用于存储HTTP连接。 3. 在连接池类中提供一个方法,用于从队列中获取HTTP连接。 4. 在连接池类中提供一个方法,用于将HTTP连接放回队列中。 5. 在使用HTTP连接时,先从连接池中获取连接,使用完毕后再将连接放回连接池中。 6. 当连接池中没有可用连接时,需要创建新的连接。 7. 为了防止连接空闲时间过长而失效,需要定期检查连接的可用性,将失效的连接从连接池中移除。 具体的代码实现可以参考以下例子: ```java import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedList; public class HttpConnectionPool { private static final int MAX_CONNECTIONS = 5; private final LinkedList<HttpURLConnection> connections = new LinkedList<>(); public synchronized HttpURLConnection getConnection(String url) throws IOException { if (connections.isEmpty()) { return (HttpURLConnection) new URL(url).openConnection(); } return connections.removeFirst(); } public synchronized void releaseConnection(HttpURLConnection connection) { if (connections.size() >= MAX_CONNECTIONS) { try { connection.disconnect(); } catch (Exception e) { // ignore } } else { connections.addLast(connection); } } public synchronized void closeAll() { for (HttpURLConnection connection : connections) { try { connection.disconnect(); } catch (Exception e) { // ignore } } connections.clear(); } } ``` 使用方法如下: ```java HttpConnectionPool pool = new HttpConnectionPool(); try { HttpURLConnection connection = pool.getConnection("http://www.example.com"); // use connection pool.releaseConnection(connection); } catch (IOException e) { // handle exception } finally { pool.closeAll(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值