使用httpclient实现http链接池与使用HttpURLConnection发送http请求的方法与性能对比

使用httpclient实现http链接池与使用HttpURLConnection发送http请求的方法与性能对比

在项目中需要使用http调用接口,实现了两套发送http请求的方法,一个是使用apache的httpclient提供的http链接池来发送http请求,另一个是使用java原生的HttpURLConnection来发送http请求,并对两者性能进行了对比。


使用httpclient中的链接池发送http请求

使用最新的4.5.2版httpclient进行实现。在maven中引入

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

实现代码如下

package util;

import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
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 org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.Map;

/**
 * Created by xugang on 16/7/11.
 */
public class HttpClientUtil {
    private final static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
    private int maxTotal = 1;//默认最大连接数
    private int defaultMaxPerRoute = 1;//默认每个主机的最大链接数
    private int connectionRequestTimeout = 3000;//默认请求超时时间
    private int connectTimeout = 3000;//默认链接超时时间
    private int socketTimeout = 3000;//默认socket超时时间
    private HttpRequestRetryHandler httpRequestRetryHandler = new DefaultHttpRequestRetryHandler();//默认不进行重试处理
    private CloseableHttpClient httpClient;
    public  HttpClientUtil(){
      init();
    }

    public  String sendGet(String url, Map<String, Object> params) throws Exception {
        StringBuffer sb = new StringBuffer(url);
        if(!CollectionUtils.isEmpty(params)) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                sb.append(entry.getKey())
                        .append("=")
                        .append(entry.getValue())
                        .append("&");
            }
        }
        // no matter for the last ‘&‘ character
        HttpGet httpget = new HttpGet(sb.toString());
        config(httpget);
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpget, HttpClientContext.create());
        } catch (IOException e) {
            logger.error("httpclient error:"+e.getMessage());
            e.printStackTrace();
        }
        HttpEntity entity = response.getEntity();
        return EntityUtils.toString(entity, "utf-8");
    }

    private  void init() {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", plainsf)
                .register("https", sslsf)
                .build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        // 设置最大连接数
        cm.setMaxTotal(maxTotal);
        // 设置每个路由的默认连接数
        cm.setDefaultMaxPerRoute(defaultMaxPerRoute);

        //连接保持时间
        ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                // Honor ‘keep-alive‘ header
                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) {
                        }
                    }
                }
                    return 30 * 1000;
            }
        };

        this.httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .setRetryHandler(httpRequestRetryHandler)
                .setKeepAliveStrategy(myStrategy)
                .build();

    }

    /**
     * http头信息的设置
     *
     * @param httpRequestBase
     */
    private void config(HttpRequestBase httpRequestBase) {
        httpRequestBase.setHeader("User-Agent", "Mozilla/5.0");
        httpRequestBase.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        httpRequestBase.setHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");//"en-US,en;q=0.5");
        httpRequestBase.setHeader("Accept-Charset", "ISO-8859-1,utf-8,gbk,gb2312;q=0.7,*;q=0.7");
        httpRequestBase.setHeader("connection", "Keep-Alive");
        // 配置请求的超时设置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout)
                .build();
        httpRequestBase.setConfig(requestConfig);
    }

    /**
     * 请求重试处理
     * 默认不进行任何重试
     * 如需进行重试可参考下面进行重写
     * if (executionCount >= 5) {// 如果已经重试了5次,就放弃
     return false;
     }
     if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接,那么就重试
     return true;
     }
     if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
     return false;
     }
     if (exception instanceof InterruptedIOException) {// 超时
     return false;
     }
     if (exception instanceof UnknownHostException) {// 目标服务器不可达
     return false;
     }
     if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
     return false;
     }
     if (exception instanceof SSLException) {// ssl握手异常
     return false;
     }

     HttpClientContext clientContext = HttpClientContext.adapt(context);
     HttpRequest request = clientContext.getRequest();
     // 如果请求是幂等的,就再次尝试
     if (!(request instanceof HttpEntityEnclosingRequest)) {
     return true;
     }
     */
    private class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler

    {
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            return false;
        }
    }

}

使用了spring和slf4j,所以要直接用的话还需在你的pom中添加相关依赖,不想添加的话稍微改一下代码也很简单,就不说了。

  <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
         <version>1.7.13</version>
     </dependency>

     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-core</artifactId>
         <version>4.1.1.RELEASE</version>
     </dependency>

     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-beans</artifactId>
         <version>4.1.1.RELEASE</version>
     </dependency>

使用java原生的HttpURLConnection发送http链接

代码如下

package util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;

public class HttpUtils {

    final static Logger logger = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * Post 请求超时时间和读取数据的超时时间均为2000ms。
     *
     * @param urlPath       post请求地址
     * @param parameterData post请求参数
     * @return String json字符串,成功:code=1001,否者为其他值
     * @throws Exception 链接超市异常、参数url错误格式异常
     */
    public static String doPost(String urlPath, String parameterData, String who, String ip) throws Exception {

        if (null == urlPath || null == parameterData) { // 避免null引起的空指针异常
            return "";
        }
        URL localURL = new URL(urlPath);
        URLConnection connection = localURL.openConnection();
        HttpURLConnection httpURLConnection = (HttpURLConnection) connection;

        httpURLConnection.setDoOutput(true);
        if (!StringUtils.isEmpty(who)) {
            httpURLConnection.setRequestProperty("who", who);
        }
        if (!StringUtils.isEmpty(ip)) {
            httpURLConnection.setRequestProperty("clientIP", ip);
        }
        httpURLConnection.setRequestMethod("POST");
        httpURLConnection.setRequestProperty("Accept-Charset", "utf-8");
        httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        httpURLConnection.setRequestProperty("Content-Length", String.valueOf(parameterData.length()));
        httpURLConnection.setConnectTimeout(18000);
        httpURLConnection.setReadTimeout(18000);

        OutputStream outputStream = null;
        OutputStreamWriter outputStreamWriter = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader reader = null;
        StringBuilder resultBuffer = new StringBuilder();
        String tempLine = null;

        try {
            outputStream = httpURLConnection.getOutputStream();
            outputStreamWriter = new OutputStreamWriter(outputStream);

            outputStreamWriter.write(parameterData.toString());
            outputStreamWriter.flush();

            if (httpURLConnection.getResponseCode() >= 300) {
                throw new Exception("HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
            }

            inputStream = httpURLConnection.getInputStream(); // 真正的发送请求到服务端
            inputStreamReader = new InputStreamReader(inputStream);
            reader = new BufferedReader(inputStreamReader);

            while ((tempLine = reader.readLine()) != null) {
                resultBuffer.append(tempLine);
            }

        } finally {

            if (outputStreamWriter != null) {
                outputStreamWriter.close();
            }

            if (outputStream != null) {
                outputStream.close();
            }

            if (reader != null) {
                reader.close();
            }

            if (inputStreamReader != null) {
                inputStreamReader.close();
            }

            if (inputStream != null) {
                inputStream.close();
            }
        }
        return resultBuffer.toString();
    }

    public static String doPost(String url, Map<String, Object> params) throws Exception {
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            sb.append(entry.getKey())
                    .append("=")
                    .append(entry.getValue())
                    .append("&");
        }

        // no matter for the last ‘&‘ character

        return doPost(url, sb.toString(), "", "");
    }

    /**
     * 向指定URL发送GET方法的请求
     *
     * @param url   发送请求的URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param, String who, String ip) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url;
            if (!"".equals(param)) {
                urlNameString = urlNameString + "?" + param;
            }
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            if (!StringUtils.isEmpty(who)) {
                connection.setRequestProperty("who", who);
            }
            if (!StringUtils.isEmpty(ip)) {
                connection.setRequestProperty("clientIP", ip);
            }
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();

            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            logger.warn("发送GET请求出现异常!", e);
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                logger.warn("fail to close inputStream.", e2);
            }
        }
        return result;
    }

    public static String sendGet(String url, Map<String, Object> params) throws Exception {
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            sb.append(entry.getKey())
                    .append("=")
                    .append(entry.getValue())
                    .append("&");
        }

        // no matter for the last ‘&‘ character

        return sendGet(url, sb.toString(), "", "");
    }

}

也使用了sl4j打日志,不需要的话直接删掉吧。


两种链接方式的性能对比

测试代码如下

   HttpClientUtil httpClientUtil = new HttpClientUtil();
        long start1 = System.currentTimeMillis();
        for(int i = 0;i<1000;i++){
            try {
                HttpUtils.sendGet("http://yop-console-qa.s.qima-inc.com/app/list", new HashMap<String, Object>());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        long end1 = System.currentTimeMillis();
        long start2 = System.currentTimeMillis();
        for (int i = 0;i<1000;i++) {
            try {               httpClientUtil.sendGet("http://yop-console-qa.s.qima-inc.com/app/list", null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        long end2 = System.currentTimeMillis();
        System.out.println("时间1:" + (end1 - start1) + "时间2:" + (end2 - start2));

两个都向同一地址发送1000个请求,最后输出为:

时间1:35637时间2:34868

即使用java原生的用时35637ms ,使用httpclient用时 34868ms。是不是感觉区别不大?但是要考虑到网络本来就不稳定,你下个片还时而100k时而50k呢,这种测试不能完全说明问题,下面我们来看看使用http链接池和直接使用java原生的http方式有什么区别


http链接池与直接链接的区别

首先我们要知道http是建立在tcp之上的应用层协议,因此在建立http请求前首先要进行tcp的三次握手过程:

在http传输结束断开链接时要进行tcp的四次握手断开链接:

在看一下抓包得到的一次http请求过程可以更直观展现:

首先3次握手建立链接,链接建立后客服端提交一个http请求到服务器,然后是图中高亮的部分,服务器在接受到http请求后先回复一个tcp的确认请求给客服端,再回复http请求到客服端。最后四次握手断开链接。当使用java原生方式发送http请求时,是不是每一次请求都要经历3次握手建立链接-发送http请求并获取结果-4次握手释放链接这样的过程呢?来看图:

忽略图中的tcp segment和tcp window update。可以看到,第一次http结束后并没有断开链接,直接复用了,为了测试我还特意将HttpUtil中的这段代码注释掉了

//connection.setRequestProperty("connection", "Keep-Alive");

那么。。why?为什么这种情况下http链接还能被复用呢?

细心的读者可能注意到了,使用的http是1.1,在Http /1.1中:

在HTTP/1.1版本中,官方规定的Keep-Alive使用标准和在HTTP/1.0版本中有些不同,默认情况下所在HTTP1.1中所有连接都被保持,除非在请求头或响应头中指明要关闭:Connection:Close ,这也就是为什么Connection:

Keep-Alive字段再没有意义的原因。另外,还添加了一个新的字段Keep-Alive,但是因为这个字段并没有详细描述用来做什么,可忽略它

ok,答案很明显了,默认情况下该http链接便是可以被复用的,并且在java的客服端中有:

HttpURLConnection类自动实现了Keep-Alive,如果程序员没有介入去操作Keep-Alive,Keep-Alive会通过客户端内部的一个HttpURLConnection类的实例对象来自动实现。

在java的服务器端有:

HttpServlet、HttpServletRequest和HttpServletResponse类自动实现 了Keep-Alive

原来jdk已经帮我们做了这么多了^ ^

接下来还是再来看看使用链接池时进行多次http请求的情况吧:

看起来少了不少,但其实主流程一样,少的部分只是tcp segment和tcp window update,但是在使用链接池时几乎不会出现tcp segment和tcp window update,使用原生连接时有大量tcp segment出现,虽然对时间影响很小。

因此,单从时间上来说两种http方式耗时相差不大,如果只需简单的发送http请求用原生方法就够了,需要更强大的功能可考虑使用链接池。

时间: 2024-12-15 07:12:28

使用httpclient实现http链接池与使用HttpURLConnection发送http请求的方法与性能对比的相关文章

Android4种网络连接方式HttpClient、HttpURLConnection、OKHttp和Volley优缺点和性能对比

比较的指标: 1.cpu 2.流量 3.电量 4.内存占用 5.联网时间 功能点: 1.重试机制 2.提供的扩展功能 3.易用性 4.是否https 5.是否支持reflect api,OkHttp有配套方法 6.缓存.重试 7.cookie支持session  id会话支持 8.弱网性能和稳定性 9.超时时间,几种超时时间   连接超时,响应超时 10.适配各种机型.4.4和之前版本  2.3  4.1 5.0 4种网络连接方式提供的功能对比表格: 缓存 重试 Https/Http 稳定性 C

【JAVA】通过HttpClient发送HTTP请求的方法

HttpClient介绍 HttpClient 不是一个浏览器.它是一个客户端的 HTTP 通信实现库.HttpClient的目标是发 送和接收HTTP 报文.HttpClient不会去缓存内容,执行 嵌入在 HTML 页面中的javascript 代码,猜测内容类型,重新格式化请求/重定向URI,或者其它和 HTTP 运输无关的功能. HttpClient使用 使用需要引入jar包,maven项目引入如下: 1 <dependency> 2 <groupId>org.apache

HttpClient服务端发送http请求

本来以为对跨域问题的处理已经比较熟练了.可以通过jsonp.document.domain+iframe.window.name.window.postMessage.服务器上设置代理页面来解决.但还是遇到了难题dva封装的request: 1.robe-ajax用它来调其他网站的api,会报跨域问题怎么设置也没用. 2.fetch可以通过设置mode:no-cors来解决跨域,但到checkStatus时会报错,network能看到response. 3.jq ajax设置dataType:j

spring 链接池

使用链接池比直接链接,提供了更多的数据库配置项,方便管理. 现在常用的开源数据连接池主要有c3p0.dbcp和proxool三种,其中:? hibernate开发组推荐使用c3p0;? spring开发组推荐使用dbcp(dbcp连接池有weblogic连接池同样的问题,就是强行关闭连接或数据库重启后,无法reconnect,告诉连接被重置,这个设置可以解决 <!-- 配置dbcp数据源 --> <bean id="dataSource2" destroy-metho

HttpClient 4.3连接池参数配置及源码解读

目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB->服务端处理请求,查询数据并返回),发现原本的HttpClient连接池中的一些参数配置可能存在问题,如defaultMaxPerRoute.一些timeout时间的设置等,虽不能确定是由于此连接池导致接口查询慢,但确实存在可优化的地方,故花时间做一些研究.本文主要涉及HttpClient连接池.请求的参数

oracle数据库链接池的配置

连接池 在Tomcat的配置文件apache-tomcat-7.0.40-windows-x64\apache-tomcat-7.0.40\confcontext.xml中 <Resource name="jdbc/easybuy" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFac

数据源与链接池

1.tomcat服务器中添加数据库驱动 将Oracle数据库的ojdbc14.jar文件复制到Tomcat安装目录下的bin文件夹 2.配置Tomcat服务 器的配置文件. 在tomcat服务器的conf/context.xml文件最下方添加如下配置信息(在ECLIPSE下的Servers/Tomcat v6.0 Server at localhost-config/CONTEXT.XML下也要复制下面的代码) <Resource name="jdbc/news"auth=&qu

jdbc 链接池的优化

package cn.itcast.jdbc.datasourse; import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.LinkedList; public class MyDataSourse {    private static String url = "jdbc:mysql://localhost:3306/test"; 

jdbc 链接池

package cn.itcast.jdbc.datasourse; import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.LinkedList; public class MyDataSourse {    private static String url = "jdbc:mysql://localhost:3306/test";