http://blog.csdn.net/angjunqiang/article/details/54340398

背景

服务端以及客户端在开发过程中不可避免的会使用到网络请求,网络请求可以使用Java原生的URLConnection,也可以使用HttpClient。在日常工作中建议大家使用HttpClient。URLConnection需要自己去实现相关的方法,而HttpClient使用起来方便,且提供许多的API。

HttpClient

1、httpClient的特性

  • 实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)

  • 支持自动转向

  • 支持 HTTPS 协议

  • 支持代理服务器等

2、httpClient版本

目前httpClient的最新版本为4.5.2,可以通过如下maven获取Jar包,这里以本人常用的4.2.2为例;

  1. <span style="font-size:14px;">   <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
  2. <dependency>
  3. <groupId>org.apache.httpcomponents</groupId>
  4. <artifactId>httpclient</artifactId>
  5. <version>4.2.2</version>
  6. </dependency></span>

3、使用方法

  1. package com.netease.mail.be.appads.web.controller;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.net.URI;
  5. import java.net.URISyntaxException;
  6. import java.util.List;
  7. import org.apache.commons.lang.StringUtils;
  8. import org.apache.http.HttpEntity;
  9. import org.apache.http.HttpResponse;
  10. import org.apache.http.client.ClientProtocolException;
  11. import org.apache.http.client.HttpClient;
  12. import org.apache.http.client.entity.UrlEncodedFormEntity;
  13. import org.apache.http.client.methods.HttpPost;
  14. import org.apache.http.entity.StringEntity;
  15. import org.apache.http.impl.client.DefaultHttpClient;
  16. import org.apache.http.message.BasicNameValuePair;
  17. import org.apache.http.protocol.HTTP;
  18. import org.apache.http.util.EntityUtils;
  19. public class PostSample {
  20. public String setPost(String url, String reqBody,
  21. List<BasicNameValuePair> urlParams) {
  22. // step1: 构造HttpClient的实例,类似于打开浏览器
  23. HttpClient client = new DefaultHttpClient();
  24. // step2: 创建POST方法的实例,类似于在浏览器地址栏输入url
  25. HttpPost postMethod = new HttpPost(url);
  26. String returnValue = "";
  27. try {
  28. // step3:设置url相关参数
  29. if (null != urlParams && !urlParams.isEmpty()) {
  30. String params = EntityUtils.toString(new UrlEncodedFormEntity(
  31. urlParams, HTTP.UTF_8));
  32. postMethod.setURI(new URI(postMethod.getURI().toString() + "?"
  33. + params));
  34. }
  35. // step4:设置请求body参数及头部相关参数
  36. if (StringUtils.isNotBlank(reqBody)) {
  37. StringEntity se = new StringEntity(reqBody, "UTF-8");
  38. se.setContentType("application/text; charset=utf-8");
  39. postMethod.setEntity(se);
  40. }
  41. // step5:执行发送请求
  42. HttpResponse response = client.execute(postMethod);
  43. int statusCode = response.getStatusLine().getStatusCode();
  44. if (statusCode == 200) {
  45. // 校验码
  46. HttpEntity he = response.getEntity();
  47. returnValue = new String(EntityUtils.toByteArray(he), "UTF-8");
  48. System.out.println("Bid Request Send Succeed." + returnValue);
  49. return returnValue;
  50. } else if (statusCode == 204) {
  51. System.out.println("Bid Request Send Error, No Content");
  52. } else {
  53. System.out.println("Error happens,http status code is "
  54. + statusCode + ".");
  55. }
  56. } catch (UnsupportedEncodingException e) {
  57. System.out.println(Thread.currentThread().getName()
  58. + "发送BidRequest请求错误,编码错误:" + e.getMessage());
  59. } catch (ClientProtocolException e) {
  60. System.out.println(Thread.currentThread().getName()
  61. + "发送BidRequest请求错误,协议错误错误:" + e.getMessage());
  62. } catch (IOException e) {
  63. System.out.println(Thread.currentThread().getName()
  64. + "发送BidRequest请求错误,网络IO错误:" + e.getMessage());
  65. } catch (URISyntaxException e) {
  66. System.out.println(Thread.currentThread().getName()
  67. + "发送BidRequest请求错误,URL语法错误:" + e.getMessage());
  68. } finally {
  69. // 释放连接,很关键
  70. postMethod.releaseConnection();
  71. }
  72. return returnValue;
  73. }
  74. }

注:此方法可以应付并发量不高的网络请求,但是对于高并发的网络请求,此方法存在很大的缺陷及性能问题。具体原因如下:

  • 此方法每次都会new一个httpClient对象及HttpPost对象,new的时候需要申请系统资源会消耗一定的时间;
  • 虽然请求执行完毕后都执行了postMethod.releaseConnection方法去释放连接,但是这只是应用层的释放,而真正的释放端口需等待一段时间, 这个时间由系统决定。所以在高并发的时候,端口不断的被占用达到系统的最大值,导致后面的网络请求会直接抛出 Cannot assign requested address[不能分配端口]的异常],从而不能进行请求。

高并发的网络请求

在高并发时,需要用到HttpClient的连接池,连接的作用主要是减少创建链接的次数,请求可以复用之前的连接,所以就可以指定一定数量的连接数让连接池去创建,避免了多次创建新的连接并在系统回收连接时不能及时释放端口的情况下达到可用端口的最大值的问题。具体实现如下:

  1. package com.netease.mail.be.appads.web.controller;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.net.URI;
  5. import java.net.URISyntaxException;
  6. import java.nio.charset.Charset;
  7. import java.security.cert.CertificateException;
  8. import java.security.cert.X509Certificate;
  9. import java.util.List;
  10. import javax.net.ssl.SSLContext;
  11. import javax.net.ssl.TrustManager;
  12. import javax.net.ssl.X509TrustManager;
  13. import org.apache.commons.lang.StringUtils;
  14. import org.apache.http.HttpEntity;
  15. import org.apache.http.HttpResponse;
  16. import org.apache.http.HttpVersion;
  17. import org.apache.http.client.ClientProtocolException;
  18. import org.apache.http.client.entity.UrlEncodedFormEntity;
  19. import org.apache.http.client.methods.HttpGet;
  20. import org.apache.http.client.methods.HttpPost;
  21. import org.apache.http.conn.scheme.PlainSocketFactory;
  22. import org.apache.http.conn.scheme.Scheme;
  23. import org.apache.http.conn.scheme.SchemeRegistry;
  24. import org.apache.http.conn.ssl.SSLSocketFactory;
  25. import org.apache.http.entity.StringEntity;
  26. import org.apache.http.impl.client.DefaultHttpClient;
  27. import org.apache.http.impl.conn.PoolingClientConnectionManager;
  28. import org.apache.http.message.BasicNameValuePair;
  29. import org.apache.http.params.BasicHttpParams;
  30. import org.apache.http.params.CoreConnectionPNames;
  31. import org.apache.http.params.CoreProtocolPNames;
  32. import org.apache.http.params.HttpParams;
  33. import org.apache.http.util.EntityUtils;
  34. import org.slf4j.Logger;
  35. import org.slf4j.LoggerFactory;
  36. import org.springframework.stereotype.Service;
  37. /**
  38. * HTTP请求对象,支持post与get方法
  39. */
  40. @Service("httpRequesterService")
  41. public class HttpClientPoolSample {
  42. private Logger logger = LoggerFactory.getLogger(HttpClientPoolSample.class);
  43. private static int socketTimeout = 1000;// 设置等待数据超时时间5秒钟 根据业务调整
  44. private static int connectTimeout = 2000;// 连接超时
  45. private static int maxConnNum = 4000;// 连接池最大连接数
  46. private static int maxPerRoute = 1500;// 每个主机的并发最多只有1500
  47. private static PoolingClientConnectionManager cm;
  48. private static HttpParams httpParams;
  49. private static final String DEFAULT_ENCODING = Charset.defaultCharset()
  50. .name();
  51. static {
  52. SchemeRegistry sr = new SchemeRegistry();
  53. sr.register(new Scheme("http", 80, PlainSocketFactory
  54. .getSocketFactory()));
  55. SSLSocketFactory sslFactory;
  56. try {
  57. SSLContext sslContext = SSLContext.getInstance("SSL");
  58. X509TrustManager tm = new X509TrustManager() {
  59. @Override
  60. public void checkClientTrusted(X509Certificate[] chain,
  61. String authType) throws CertificateException {
  62. }
  63. @Override
  64. public void checkServerTrusted(X509Certificate[] chain,
  65. String authType) throws CertificateException {
  66. }
  67. @Override
  68. public X509Certificate[] getAcceptedIssuers() {
  69. return null;
  70. }
  71. };
  72. sslContext.init(null, new TrustManager[] { tm },
  73. new java.security.SecureRandom());
  74. sslFactory = new SSLSocketFactory(sslContext,
  75. SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
  76. sr.register(new Scheme("https", 443, sslFactory));
  77. } catch (Exception e) {
  78. e.printStackTrace();
  79. }
  80. // 初始化连接池
  81. cm = new PoolingClientConnectionManager(sr);
  82. cm.setMaxTotal(maxConnNum);
  83. cm.setDefaultMaxPerRoute(maxPerRoute);
  84. httpParams = new BasicHttpParams();
  85. httpParams.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
  86. HttpVersion.HTTP_1_1);
  87. httpParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
  88. connectTimeout);// 请求超时时间
  89. httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,
  90. socketTimeout);// 读取数据超时时间
  91. // 如果启用了NoDelay策略,httpclient和站点之间传输数据时将会尽可能及时地将发送缓冲区中的数据发送出去、而不考虑网络带宽的利用率,这个策略适合对实时性要求高的场景
  92. httpParams.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);
  93. httpParams.setBooleanParameter(
  94. CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
  95. }
  96. public DefaultHttpClient getHttpClient() {
  97. return new DefaultHttpClient(cm, httpParams);
  98. }
  99. public String httpGet(String url, List<BasicNameValuePair> parameters) {
  100. DefaultHttpClient client = getHttpClient();// 默认会到池中查询可用的连接,如果没有就新建
  101. HttpGet getMethod = null;
  102. String returnValue = "";
  103. try {
  104. getMethod = new HttpGet(url);
  105. if (null != parameters) {
  106. String params = EntityUtils.toString(new UrlEncodedFormEntity(
  107. parameters, DEFAULT_ENCODING));
  108. getMethod.setURI(new URI(getMethod.getURI().toString() + "?"
  109. + params));
  110. logger.debug("httpGet-getUrl:{}", getMethod.getURI());
  111. }
  112. HttpResponse response = client.execute(getMethod);
  113. int statusCode = response.getStatusLine().getStatusCode();
  114. if (statusCode == 200) {
  115. HttpEntity he = response.getEntity();
  116. returnValue = new String(EntityUtils.toByteArray(he),
  117. DEFAULT_ENCODING);
  118. return returnValue;
  119. }
  120. } catch (UnsupportedEncodingException e) {
  121. logger.error(Thread.currentThread().getName()
  122. + "httpGet Send Error,Code error:" + e.getMessage());
  123. } catch (ClientProtocolException e) {
  124. logger.error(Thread.currentThread().getName()
  125. + "httpGet Send Error,Protocol error:" + e.getMessage());
  126. } catch (IOException e) {
  127. logger.error(Thread.currentThread().getName()
  128. + "httpGet Send Error,IO error:" + e.getMessage());
  129. } catch (URISyntaxException e) {
  130. logger.error(Thread.currentThread().getName()
  131. + "httpGet Send Error,IO error:" + e.getMessage());
  132. } finally {// 释放连接,将连接放回到连接池
  133. getMethod.releaseConnection();
  134. }
  135. return returnValue;
  136. }
  137. public String httpPost(String url, List<BasicNameValuePair> parameters,
  138. String requestBody) {
  139. DefaultHttpClient client = getHttpClient();// 默认会到池中查询可用的连接,如果没有就新建
  140. HttpPost postMethod = null;
  141. String returnValue = "";
  142. try {
  143. postMethod = new HttpPost(url);
  144. if (null != parameters) {
  145. String params = EntityUtils.toString(new UrlEncodedFormEntity(
  146. parameters, DEFAULT_ENCODING));
  147. postMethod.setURI(new URI(postMethod.getURI().toString() + "?"
  148. + params));
  149. logger.debug("httpPost-getUrl:{}", postMethod.getURI());
  150. }
  151. if (StringUtils.isNotBlank(requestBody)) {
  152. StringEntity se = new StringEntity(requestBody,
  153. DEFAULT_ENCODING);
  154. postMethod.setEntity(se);
  155. }
  156. HttpResponse response = client.execute(postMethod);
  157. int statusCode = response.getStatusLine().getStatusCode();
  158. if (statusCode == 200) {
  159. HttpEntity he = response.getEntity();
  160. returnValue = new String(EntityUtils.toByteArray(he),
  161. DEFAULT_ENCODING);
  162. return returnValue;
  163. }
  164. } catch (UnsupportedEncodingException e) {
  165. logger.error(Thread.currentThread().getName()
  166. + "httpPost Send Error,Code error:" + e.getMessage());
  167. } catch (ClientProtocolException e) {
  168. logger.error(Thread.currentThread().getName()
  169. + "httpPost Send Error,Protocol error:" + e.getMessage());
  170. } catch (IOException e) {
  171. logger.error(Thread.currentThread().getName()
  172. + "httpPost Send Error,IO error:" + e.getMessage());
  173. } catch (URISyntaxException e) {
  174. logger.error(Thread.currentThread().getName()
  175. + "httpPost Send Error,IO error:" + e.getMessage());
  176. } finally {// 释放连接,将连接放回到连接池
  177. postMethod.releaseConnection();
  178. // 释放池子中的空闲连接
  179. // client.getConnectionManager().closeIdleConnections(30L,
  180. // TimeUnit.MILLISECONDS);
  181. }
  182. return returnValue;
  183. }
  184. }

一些参数的解释:

  • socketTimeout:socket超时时间,这个超时时间不是连接的超时时间,而是读取返回值的超时时间,即响应超时时间
  • connectTimeout:请求连接对方的超时时间
  • maxConnNum: 连接池最大并发数。这个设置与应用的并发量有关。比如应用的1s的请求量1000,每个请求需要10ms,则需要的最大并发数为 1000/(1s/10ms)=10,理论上设置的值最好比这个稍大一点点。
  • maxPerRoute:请求后端的最大连接数,比如说后端服务有2台机器,而maxConnNum=10,则maxPerRoute=5

根据如上的配置理论上最多只可能会存在4000个连接,对于空闲的连接连接池会根据默认的设置进行回收,当然也可以在程序中进行回收,回收方法client.getConnectionManager().closeIdleConnections(30000L,TimeUnit.MILLSECONDS)表示这个连接维持30秒的空闲时间后进行回收。如果我们将30000L改为0L,则表示立即进行回收,那就失去了连接池的意义,不建议这样做。

【转】同步的HttpClient使用详解的更多相关文章

  1. HttpClient使用详解(转)

     HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们再讨论),它不仅是客户 ...

  2. HttpClient使用详解

    http://itindex.net/detail/52566-httpclient HttpClient使用详解 标签: httpclient | 发表时间:2015-01-22 12:07 | 作 ...

  3. 剑指Offer——线程同步volatile与synchronized详解

    (转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...

  4. Java进阶(三十二) HttpClient使用详解

    Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...

  5. gvoory脚本中关于HttpClient使用详解实例

    一.gvoory脚本中关于HttpClient使用详解实例 HttpClient:是一个接口 首先需要先创建一个DefaultHttpClient的实例 HttpClient httpClient=n ...

  6. 无锁的同步策略——CAS操作详解

    目录 1. 从乐观锁和悲观锁谈起 2. CAS详解 2.1 CAS指令 2.3 Java中的CAS指令 2.4 CAS结合失败重试机制进行并发控制 3. CAS操作的优势和劣势 3.1 CAS相比独占 ...

  7. HttpClient请求详解

    HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的.最新的.功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建 ...

  8. rsync文件同步、Inotify-tools参数详解

    inotifywait用于等待文件或文件集上的一个待定事件,可以监控任何文件和目录设置,并且可以递归地监控整个目录树: inotifywatch用于收集被监控的文件系统计数据,包括每个inotify事 ...

  9. HttpClient类详解

    文章链接:https://blog.csdn.net/justry_deng/article/details/81042379 HTTP 协议可能是现在 Internet 上使用得最多.最重要的协议了 ...

随机推荐

  1. [转]NetCat简介

    NetCat是一个非常简单的Unix工具,可以读.写TCP或UDP网络连接(network connection).它被设计成一个可靠的后端(back-end) 工具,能被其它的程序或脚本直接地或容易 ...

  2. HikariCP配置使用spring结合--Java数据库连接池

    我的个人德州扑克项目https://github.com/mingzijian/pokers,欢迎给星星.maven引入: Java 8 maven artifact: <dependency& ...

  3. Beam概念学习系列之Pipeline 数据处理流水线

    不多说,直接上干货! Pipeline 数据处理流水线 Pipeline将Source PCollection ParDo.Sink组织在一起形成了一个完整的数据处理的过程. Beam概念学习系列之P ...

  4. 60分钟内从零起步驾驭Hive实战学习笔记(Ubuntu里安装mysql)

    本博文的主要内容是: 1. Hive本质解析 2. Hive安装实战 3. 使用Hive操作搜索引擎数据实战 SparkSQL前身是Shark,Shark强烈依赖于Hive.Spark原来没有做SQL ...

  5. git命令(转载学习)

    Git使用教程 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是 ...

  6. adb调试安卓

    http://blog.csdn.net/liushida00/article/details/49797239

  7. FZU 2216——The Longest Straight——————【二分、枚举】

    Problem 2216 The Longest Straight Accept: 17    Submit: 39Time Limit: 1000 mSec    Memory Limit : 32 ...

  8. SpringBoot | 第三十章:Spring-data-jpa的集成和使用

    前言 在前面的第九章:Mybatis-plus的集成和使用章节中,介绍了使用ORM框架mybatis-plus进行数据库的访问.今天,我们来简单学习下如何使用spring-data-jpa进行数据库的 ...

  9. ubuntu系统操作

    给ubuntu配置解析主机名 vim  /etc/hosts 192.168.23.44  Evelyn

  10. jrebel + myeclipse 实现热部署

    1.什么是jrebel JRebel是一套JavaEE开发工具.JRebel允许开发团队在有限的时间内完成更多的任务修正更多的问题,发布更高质量的软件产品. JRebel是收费软件. Jrebel 可 ...