HTTP连接池

前言

最早接触HTTP连接池,是在HTTP传输数据过程中,或因网络节点问题,或因网络波动,或因Tomcat连接爆棚等等,导致数据传输失败。想通过HTTP链接池,优化性能,但是因为公司项目架构的问题,最后使用了RocketMQ。下面是早前写的Demo。

正文

HTTP连接池优势

1.复用http连接,省去了tcp的3次握手和4次挥手的时间,极大降低请求响应的时间

2.自动管理tcp连接,不用人为地释放/创建连接

注意点

不要多个应用创建连接池去对应一个应用。嗯,大抵没记错的话,会使被请求方连接数的不到释放,最后服务宕机。

代码正文

package com.taskorder.util;


import org.apache.commons.codec.Charsets;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * 邙邙
 * 连接池,get,post,
 * 2020.6.21
 */
public class HttpClientAgent {
    private static Logger logger = LoggerFactory.getLogger(HttpClientAgent.class);
    private static HttpParams params;
    private static ClientConnectionManager cm;
    private static HttpClient httpClient;
    private static int seeNumber =0;
    static{
        System.out.println("初始化HttpClientTest~~~开始");
        params=new BasicHttpParams();
        /**
        *设置最大连接数,最大连接数就是连接池允许的最大连接数,httpclient设置的最大连接数绝对不能超过tomcat设置的最大连接数,否则tomcat的连接就会被httpclient连接池一直占用,直到系统挂掉。
         * 一般上tomcat最大连接默认为200,排队等待为100
         */
        ConnManagerParams.setMaxTotalConnections(params,180);
        /**说明:设置每个路由最大连接数。这里要特别提到route(路由)最大连接数这个参数呢,
         因为这个参数的默认值为2,如果不设置这个参数值,默认情况下对于同一个目标机器的最大并发连接只有2个
         ,这意味着如果你正在执行一个针对某一台目标机器的抓取任务的时候,哪怕你设置连接池的最大连接数为300
         ,但是实际上还是只有2个连接在工作,其他剩余的298个连接都在等待,都是为别的目标机器服务的。
         */
        ConnPerRouteBean connPerRoute = new ConnPerRouteBean(100);
        /**
         * 设置www.xxx.com这个网站对应的路由连接最大为100,因为此工具只为一个URL提供服务,所以要把此网站的最大连接数设置为100,保持与路由最大连接数一致,不然只会取最小值
         * HttpHost(要访问的域名,要访问的端口);
         * */

        HttpHost localhost = new HttpHost("127.0.0.1", 10086);
        connPerRoute.setMaxForRoute(new HttpRoute(localhost),100);
        ConnManagerParams.setMaxConnectionsPerRoute(params,connPerRoute);
        // 设置访问协议模式
        SchemeRegistry schemeRegistry=new SchemeRegistry();
        schemeRegistry.register( new Scheme("http", PlainSocketFactory.getSocketFactory(),80));
        schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(),443));
//        ClientConnectionManager cm=new ThreadSafeClientConnManager(params,schemeRegistry);
        cm=new ThreadSafeClientConnManager(params,schemeRegistry);
//        httpClient=new DefaultHttpClient(cm,params);
//        httpClient=
        DefaultHttpClient   defaultHttpClient  = new DefaultHttpClient(cm,params);
        //设置连接过期时间
        defaultHttpClient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy(){

            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch(NumberFormatException ignore) {

                        }
                    }
                }
                HttpHost target = (HttpHost) context.getAttribute( ExecutionContext.HTTP_TARGET_HOST);
//                if ("www.xxx.com".equalsIgnoreCase(target.getHostName())) {
                if ("127.0.0.1".equalsIgnoreCase(target.getHostName())) {
                    // 对于xxx这个路由的连接,保持60秒
                    System.out.println("--------xxx---------");
                    return 60 * 1000;
                } else {
                    // 其他路由保持40秒
                    return 40 * 1000;
                }

            }


        });
        /**
         *   使用线程安全的连接管理来创建HttpClient
         */
        httpClient = defaultHttpClient;
        System.out.println("初始化HttpClientTest~~~结束");
    }
    // 获取Http的连接
//    public static HttpClient getHttpClient() {
//        return new DefaultHttpClient(cm, params);
//    }

    public static class IdleConnectionMonitorThread extends Thread{
        private final ClientConnectionManager connMgr;
        private volatile boolean shutdown;
        public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
            super(); this.connMgr = connMgr;
        }

        @Override
        public void run() {
            ++seeNumber;
            System.out.println("进入run()方法"+seeNumber);
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(5000);
                        // 关闭过期的连接
                        connMgr.closeExpiredConnections();
                        // 关闭空闲时间超过30秒的连接
                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                        System.out.println("关闭方法"+seeNumber);
                        shutdown();
                    }
                }
            } catch (InterruptedException ex) {
                System.out.println("异常");
                ex.printStackTrace();
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }


    }



    public static String get(String url, Map<String, String> paramMapTwo,
                           List<Header> headers) throws Exception {

        /**
         * 1.连接的有效性检测是所有连接池都面临的一个通用问题,大部分HTTP服务器为了控制资源开销,并不会
         * 永久的维护一个长连接,而是一段时间就会关闭该连接。放回连接池的连接,如果在服务器端已经关闭,客
         * 户端是无法检测到这个状态变化而及时的关闭Socket的。这就造成了线程从连接池中获取的连接不一定是有效的。
         * 这个问题的一个解决方法就是在每次请求之前检查该连接是否已经存在了过长时间,可能已过期
         * 2.清除连接池中所有过期的连接和关闭一定时间空闲的连接
         * */
        new IdleConnectionMonitorThread(cm).start();

        // 根据URL创建HttpGet实例
        HttpGet httpGet = new HttpGet(url);
        System.out.println("httpGet"+httpGet);
//        HttpGet httpGet = new HttpGet(uriBuilder.build());
        if (headers != null) {
            // 添加请求首部
            for (Header header : headers) {
                httpGet.addHeader(header);
            }
        }
        String data = null;
        HttpResponse response = null;
        try{
            // 执行get请求,得到返回体
            response = httpClient.execute(httpGet);
            System.out.println("response"+response);
            // 判断是否正常返回

            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                // 解析数据
                data = EntityUtils.toString(response.getEntity(), Charsets.UTF_8);
                System.out.println(data);
            }

        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if (response != null){
                EntityUtils.consumeQuietly(response.getEntity());
            }
        }


        return data;
    }

    public static String get(String url, Map<String, String> paramMapTwo)
            throws Exception {

        return get(url, paramMapTwo, null);
    }


    public static String httpGet(String url) {
        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = null;
//        HttpClient client =getHttpClient();
        try {
            response = (CloseableHttpResponse) httpClient.execute(httpGet);
            String result = EntityUtils.toString(response.getEntity());
            int code = response.getStatusLine().getStatusCode();
            if (code == HttpStatus.SC_OK) {
                return result;
            } else {
                logger.error("请求{}返回错误码:{},{}", url, code,result);
                return null;
            }
        } catch (IOException e) {
            logger.error("http请求异常,{}",url,e);
        } finally {
            try {
                if (response != null)
                    response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static String httpPost(String url,Map<String, String> paramsTwo) {

        String result= "";
        new IdleConnectionMonitorThread(cm).start();
//        HttpClient client =new DefaultHttpClient();
        //POST的URL
        HttpPost httppost=new HttpPost(url);
        //建立HttpPost对象
//        List<NameValuePair> params=new ArrayList<NameValuePair>();
        //建立一个NameValuePair数组,用于存储欲传送的参数
//        params.add(new BasicNameValuePair("pwd","2544"));
        List<NameValuePair> nvps = new ArrayList <NameValuePair>();

        Set<String> keySet = paramsTwo.keySet();
        for(String key : keySet) {
            nvps.add(new BasicNameValuePair(key, paramsTwo.get(key)));
        }
        //添加参数
        try {
            httppost.setEntity(new UrlEncodedFormEntity(nvps,HTTP.UTF_8));
        }catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        try {
            //设置编码
            HttpResponse response=httpClient.execute(httppost);
//            HttpResponse response=client.execute(httppost);
            //发送Post,并返回一个HttpResponse对象
            //Header header = response.getFirstHeader("Content-Length");
            //String Length=header.getValue();
            // 上面两行可以得到指定的Header
            /**
             * 得到http响应结果的状态码为200则获取返回值
             */
            if(response.getStatusLine().getStatusCode()==200){//如果状态码为200,就是正常返回
                result=EntityUtils.toString(response.getEntity(),Charsets.UTF_8);
                //得到返回的字符串
                System.out.println(result);
                //打印输出
                //如果是下载文件,可以用response.getEntity().getContent()返回InputStream
            }else{
                System.out.println("result=空");
            }

        }catch(IOException e) {
            e.printStackTrace();
        }


        return result;
    }
    //测试
    public static void main(String[] args) throws Exception {
        Map<String, String> paramMap = new HashMap<String, String>();
        paramMap.put("id","1");

        long start=System.currentTimeMillis();

        for (int i = 0; i < 500; i++) {

            get("https://www.自己随便填个网址.", paramMap);
		}
        System.err.println("---------------------------postTest---------------------------");
        System.err.println(System.currentTimeMillis()-start);

    }

}

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Http连接池工具类是一个用于管理和复用Http连接的工具类。随着互联网的发展和使用场景的增多,使用Http发送请求的需求也越来越多,而每次发送请求都创建一个新的Http连接会造成资源的浪费和效率低下。 Http连接池工具类的作用就是在应用程序初始化时,创建一定数量的Http连接,然后将这些连接保存在一个连接池中,当需要发送请求时,直接从连接池中获取一个可用的连接来发送请求,请求完成后将连接放回连接池中。 这样做的好处是可以减少每次发送请求时创建和销毁连接的开销,提高请求的效率。另外,由于连接是复用的,连接池可以控制连接的数量,避免创建过多的连接导致资源的浪费。并且,连接池还可以对连接进行管理和监控,例如可以设置连接的超时时间,当连接超过一定时间没有使用时,连接池可以自动将其关闭并移除。同时,连接池还可以实现一些高级功能,例如连接的自动重试和故障恢复等。 在使用Http连接池工具类时,需要先初始化连接池,指定最大连接数、每个连接的最大请求次数、连接的最大空闲时间等参数。然后,在发送请求时,只需要从连接池中获取一个连接,使用完毕后将连接放回连接池即可。 总之,Http连接池工具类可以在Http请求频繁的场景下提高请求的效率,减少资源的浪费,并提供一些高级功能,是一个非常实用的工具类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值