最近使用 HttpClient 4.5 使用 CloseableHttpClient 发起连接后, 使用 CloseableHttpResponse 接受返回结果, 结果就报错了, 上网查了下, 有位 stackoverflow 的大兄弟说, 只要将:
CloseableHttpClient httpClient = HttpClients.createDefault();
改为:
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connManager).setConnectionManagerShared(true).build();
就可以整正常执行了, 于是, 用之, 果然不报错了, 但是为什么呢? 以下是大兄弟的原文解释:
I was having a similar error when I came across this thread and this seemed to fix the issue for me. I know this is an old question, but adding thoughts for others for future reference.
I'm not 100% sure as to why this fix works as the documentation around this is pretty awful. It was a lot of trial and error with what I was testing to get to this solution. From what I can
gather though, this fix works because it is then using a shared connection pool in the background, which means that connections remain open for use.
关键是最后一句话: 大概意思是, 后台使用一个共享连接池, 供剩下打开的连接去使用
感谢下这位大兄弟
apache 官方的建议是, 创建连接池, 并为每一个接口 URL 分配一个线程, 去执行, 还给出了许多高并发访问的编码技巧
那么, 使用 HttpClient 4.5 连接池的正确姿势是什么呢?
原作者地址: https://my.oschina.net/xlj44400/blog/711341
摘要: HttpClient 是 Apache Jakarta Common 下的子项目, 可以用来提供高效的, 最新的, 功能丰富的支持 HTTP 协议的客户端编程工具包, 并且它支持 HTTP 协议最新的版本和建议
HttpClient 简介
HttpClient 是 Apache Jakarta Common 下的子项目, 可以用来提供高效的, 最新的, 功能丰富的支持 HTTP 协议的客户端编程工具包, 并且它支持 HTTP 协议最新的版本和建议. HttpClient 支持的功能如下:
支持 Http0.9,Http1.0 和 Http1.1 协议.
实现了 Http 全部的方法 (GET,POST,PUT,HEAD 等).
支持 HTTPS 协议.
支持代理服务器.
提供安全认证方案.
提供连接池以便重用连接.
连接管理器支持多线程应用. 支持设置最大连接数, 同时支持设置每个主机的最大连接数, 发现并关闭过期的连接.
在 http1.0 和 http1.1 中利用 KeepAlive 保持长连接.
以前是 commons-httpclient, 后面被 Apache HttpComponents 取代, 目前版本 4.5.x, 我们现在用的就是 4.5 版本
HttpClient 连接池使用
为什么要用 Http 连接池:
, 降低延迟: 如果不采用连接池, 每次连接发起 Http 请求的时候都会重新建立 TCP 连接 (经历 3 次握手), 用完就会关闭连接 (4 次挥手), 如果采用连接池则减少了这部分时间损耗
, 支持更大的并发: 如果不采用连接池, 每次连接都会打开一个端口, 在大并发的情况下系统的端口资源很快就会被用完, 导致无法建立新的连接
默认 http 协议:
- private static final Charset CHAR_SET = Charset.forName("utf-8");
- private static PoolingHttpClientConnectionManager cm;
- public void init() {
- cm = new PoolingHttpClientConnectionManager();
- cm.setMaxTotal(50);
- cm.setDefaultConnectionConfig(ConnectionConfig.custom()
- .setCharset(CHAR_SET).build());
- SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(30000)
- .setSoReuseAddress(true).build();
- cm.setDefaultSocketConfig(socketConfig);
- // HttpProtocolParams.setContentCharset(httpParams, "UTF-8");
- // HttpClientParams.setCookiePolicy(httpParams, "ignoreCookies");
- // HttpConnectionParams.setConnectionTimeout(httpParams, 30000);
- // HttpConnectionParams.setSoTimeout(httpParams, 30000);
- httpClient = HttpClientBuilder.create().setConnectionManager(cm)
- .build();
- }
- public CloseableHttpClient getHttpClient() {
- int timeout=2;
- RequestConfig config = RequestConfig.custom()
- .setConnectTimeout(timeout * 1000) // 设置连接超时时间, 单位毫秒
- //.setConnectionRequestTimeout(timeout * 1000) // 设置从 connect Manager 获取 Connection 超时时间, 单位毫秒
- .setSocketTimeout(timeout * 1000).build(); // 请求获取数据的超时时间, 单位毫秒
- CloseableHttpClient _httpClient = HttpClients.custom()
- .setConnectionManager(cm).setDefaultRequestConfig(config)
- .build();
- if(cm!=null&&cm.getTotalStats()!=null) { // 打印连接池的状态
- LOGGER.info("now client pool {}",cm.getTotalStats().toString());
- }
- return _httpClient;
- }
- public String post(String url, Map<String, String> params) {
- HttpPost post = new HttpPost(url);
- String resp = null;
- try {
- if(params != null){
- List<NameValuePair> nvps = new ArrayList<NameValuePair>();
- for (Map.Entry<String, String> param : params.entrySet()) {
- nvps.add(new BasicNameValuePair(param.getKey(), param.getValue()));
- }
- post.setEntity(new UrlEncodedFormEntity(nvps, CHAR_SET));
- }
- try {
- HttpResponse response = httpClient.execute(post);
- InputStream input = response.getEntity().getContent();
- resp = IOUtils.toString(input);
- } catch (ClientProtocolException e) {
- LOGGER.error(e.getMessage(), e);
- } catch (IOException e) {
- LOGGER.error(e.getMessage(), e);
- } catch (Exception e) {
- LOGGER.error(e.getMessage(), e);
- }
- } finally {
- if (post != null)
- post.releaseConnection();
- }
- return resp;
- }
https 协议:
- public class HttpConnectionManager {
- PoolingHttpClientConnectionManager cm = null;
- public void init() {
- LayeredConnectionSocketFactory sslsf = null;
- try {
- sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
- .register("https", sslsf)
- .register("http", new PlainConnectionSocketFactory())
- .build();
- cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
- cm.setMaxTotal(200);
- cm.setDefaultMaxPerRoute(20);
- }
- public CloseableHttpClient getHttpClient() {
- CloseableHttpClient httpClient = HttpClients.custom()
- .setConnectionManager(cm)
- .build();
- /*// 如果不采用连接池就是这种方式获取连接
- CloseableHttpClient httpClient = HttpClients.createDefault();
- */
- return httpClient;
- }
- }
httpClient 使用
- catch (Exception e) {
- logger.error("ufile send error e:",e);
- try {
- if (resEntity != null && resEntity.getContent() != null) {
- resEntity.getContent().close();
- }
- } catch (IllegalStateException | IOException e1) {
- logger.error("ufile send error e1:",e1);
- } finally {
- if (getMethod!=null) {
- getMethod.releaseConnection();
- }
- /*if (httpClient!=null) { // 连接池使用的时候不能关闭连接, 否则下次使用会抛异常 java.lang.IllegalStateException: Connection pool shut down
- try {
- httpClient.close();
- } catch (IOException e2) {
- logger.error("ufile httpclient close error e2:",e2);
- }
- }*/
- }
- }
连接池使用注意事项:
连接池中连接都是在发起请求的时候建立, 并且都是长连接
HttpResponse input.close(); 作用就是将用完的连接释放, 下次请求可以复用, 这里特别注意的是, 如果不使用 in.close(); 而仅仅使用 httpClient.close(); 结果就是连接会被关闭, 并且不能被复用, 这样就失去了采用连接池的意义.
连接池释放连接的时候, 并不会直接对 TCP 连接的状态有任何改变, 只是维护了两个 Set,leased 和 avaliabled,leased 代表被占用的连接集合, avaliabled 代表可用的连接的集合, 释放连接的时候仅仅是将连接从 leased 中 remove 掉了, 并把连接放到 avaliabled 集合中
打印的状态:
INFO c.m.p.u.h.HttpClientUtils[72] - now client pool [leased: 0; pending: 0; available: 0; max: 50]
leased :the number of persistent connections tracked by the connection manager currently being used to execute requests.
available :the number idle persistent connections.
pending : the number of connection requests being blocked awaiting a free connection.
max: the maximum number of allowed persistent connections.
HttpClient 4.5 超时设置
4.5 版本中, 这两个参数的设置都抽象到了 RequestConfig 中, 由相应的 Builder 构建, 具体的例子如下:
- CloseableHttpClient httpclient = HttpClients.createDefault();
- HttpGet httpGet = new HttpGet("http://stackoverflow.com/");
- RequestConfig requestConfig = RequestConfig.custom()
- .setConnectTimeout(5000).setConnectionRequestTimeout(1000)
- .setSocketTimeout(5000).build();
- httpGet.setConfig(requestConfig);
- CloseableHttpResponse response = httpclient.execute(httpGet);
- System.out.println("得到的结果:" + response.getStatusLine());// 得到请求结果
- HttpEntity entity = response.getEntity();// 得到请求回来的数据
setConnectTimeout: 设置连接超时时间, 单位毫秒.
ConnectTimeoutException
setConnectionRequestTimeout: 设置从 connect Manager 获取 Connection 超时时间, 单位毫秒. 这个属性是新加的属性, 因为目前版本是可以共享连接池的.
ConnectionPoolTimeout
setSocketTimeout: 请求获取数据的超时时间, 单位毫秒. 如果访问一个接口, 多少时间内无法返回数据, 就直接放弃此次调用.
SocketTimeoutException
上面 3 个时间 4.5 版本默认是 - 1, 就是不限, 如果不设置就会一直等待
来源: http://www.bubuko.com/infodetail-2602198.html