HttpClient使用和详解

文章目录

  • 一、关于HttpClient
  • 二、HttpClient使用步骤详解
    • 1、创建一个HttpClient对象
      • A、HttpCLientConnectionManager
      • B、HttpRoutePlanner
      • C、RequestConfig
    • 2、创建一个Request对象
    • 3、执行Request请求
    • 4、处理response
      • 1)关闭和entity相关的content stream
      • 2)关闭response
    • 5、关闭HttpClient
  • 三、相关知识补充
    • 1、关于keep-alive
    • 2、连接池管理
    • 3、连接回收策略
  • 四、总结

一、关于HttpClient

HttpClient是Apache中的一个开源的项目。它实现了HTTP标准中Client端的所有功能,使用它能够很容易地进行HTTP信息的传输。

img

HttpCLient最关键的方法是执行HTTP请求的方法execute。只要把HTTP请求传入,就可以得到HTTP响应。

// apche httpclient的maven依赖
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.2</version>
</dependency>

使用HttpClient请求一个Http请求的步骤为:

  1. 创建一个HttpClient对象
  2. 创建一个Request对象
  3. 使用HttpClient来执行Request请求,得到对方的response
  4. 处理response
  5. 关闭HttpClient

代码示例:

package com.test.httpmethod;import org.apache.http.NameValuePair;
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.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;public class HttpClientUtil {/*** 带参数的get请求** @param url* @param param* @return String*/public static String doGet(String url, Map<String, String> param) {// 创建Httpclient对象CloseableHttpClient httpclient = HttpClients.createDefault();String resultString = "";CloseableHttpResponse response = null;try {// 创建uriURIBuilder builder = new URIBuilder(url);if (param != null) {for (String key : param.keySet()) {builder.addParameter(key, param.get(key));}}URI uri = builder.build();// 创建http GET请求HttpGet httpGet = new HttpGet(uri);// 执行请求response = httpclient.execute(httpGet);// 判断返回状态是否为200if (response.getStatusLine().getStatusCode() == 200) {resultString = EntityUtils.toString(response.getEntity(), "UTF-8");}} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}httpclient.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}/*** 不带参数的get请求** @param url* @return String*/public static String doGet(String url) {return doGet(url, null);}/*** 带参数的post请求** @param url* @param param* @return String*/public static String doPost(String url, Map<String, String> param) {// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建参数列表if (param != null) {List<NameValuePair> paramList = new ArrayList<NameValuePair>();for (String key : param.keySet()) {paramList.add(new BasicNameValuePair(key, param.get(key)));}// 模拟表单UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);httpPost.setEntity(entity);}// 执行http请求response = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), "utf-8");} catch (Exception e) {e.printStackTrace();} finally {try {response.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}/*** 不带参数的post请求** @param url* @return String*/public static String doPost(String url) {return doPost(url, null);}/*** 传送json类型的post请求** @param url* @param json* @return String*/public static String doPostJson(String url, String json) {// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建请求内容StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);httpPost.setEntity(entity);// 执行http请求response = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), "utf-8");} catch (Exception e) {e.printStackTrace();} finally {try {response.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}
}

下面就针对这几个步骤进行分析和学习。

二、HttpClient使用步骤详解

1、创建一个HttpClient对象

目前最新版的HttpClient的实现类为CloseableHttpClient。创建CloseableHttpClient实例有两种方式:

(1)使用CloseableHttpClient的工厂类HttpClients的方法来创建实例。HttpClients提供了根据各种默认配置来创建CloseableHttpClient实例的快捷方法。最简单的实例化方式是调用HttpClients.createDefault()。

// 创建HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();

(2)使用CloseableHttpClient的builder类HttpClientBuilder,先对一些属性进行配置(采用装饰者模式,不断的.setxxx().setxxx()就行了),再调用build方法来创建实例。

// 通过bulider来创建
CloseableHttpClient build = HttpClientBuilder.create().build();

上面的HttpClients.createDefault()实际上调用的也就是HttpClientBuilder.create().build()。源码如下:

public static CloseableHttpClient createDefault() {return HttpClientBuilder.create().build();
}

build()方法最终是根据各种配置来new一个InternalHttpClient实例(CloseableHttpClient实现类)。

InternalHttpClient类中,我们重点关注HttpCLientConnectionManagerHttpRoutePlannerRequestConfig这三个对象。

A、HttpCLientConnectionManager

HttpClientConnectionManager是一个HTTP连接管理器。它负责新HTTP连接的创建、管理连接的生命周期还有**保证一个HTTP连接在某一时刻只被一个线程使用。**在内部实现的时候,manager使用一个ManagedHttpClientConnection的实例来作为一个实际connection的代理,负责管理connection的状态以及执行实际的I/O操作。

如果一个被监管的connection被释放或者被明确关闭,尽管此时manager仍持有该连接的代理,但是这个connection的状态不会被改变也不能再执行任何的I/O操作。

HttpClientConnectionManager有两种具体实现:

1)BasicHttpClientConnectionManager

BasicHttpClientConnectionManager每次只管理一个connection。不过,虽然它是thread-safe的,但由于它只管理一个连接,所以只能被一个线程使用。它在管理连接的时候如果发现有相同route的请求,会复用之前已经创建的连接,如果新来的请求不能复用之前的连接,它会关闭现有的连接并重新打开它来响应新的请求。

2)PoolingHttpClientConnectionManager

PoolingHttpClientConnectionManager与BasicHttpClientConnectionManager不同,它管理着一个连接池。它可以同时为多个线程服务。每次新来一个请求,如果在连接池中已经存在route相同并且可用的connection,连接池就会直接复用这个connection;当不存在route相同的connection,就新建一个connection为之服务;如果连接池已满,则请求会等待直到被服务或者超时。

默认不对HttpClientBuilder进行配置的话,new出来的CloeableHttpClient实例使用的是PoolingHttpClientConnectionManager。这种情况下HttpClientBuilder创建出的HttpClient实例就可以被多个连接和多个线程共用,在应用容器起来的时候实例化一次,在整个应用结束的时候再调用httpClient.close()就行了。

PoolingHttpClientConnectionManager的配置中有两个最大连接数量,分别控制着总的最大连接数量每个route的最大连接数量

如果没有显式设置,默认每个route只允许最多2个connection,总的connection数量不超过20。这个值对于很多并发度高的应用来说是不够的,必须根据实际的情况设置合适的值,思路和线程池的大小设置方式是类似的,如果所有的连接请求都是到同一个url,那可以把MaxPerRoute的值设置成和MaxTotal一致,这样就能更高效地复用连接。

HttpClient设置最大连接数和每个route的最大连接数示例:

private final static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();poolingHttpClientConnectionManager.setMaxTotal(MAX_CONNECTION);poolingHttpClientConnectionManager.setDefaultMaxPerRoute(MAX_CONNECTION);CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager).build();

B、HttpRoutePlanner

HttpClient不仅支持简单的直连、复杂的路由策略以及代理。HttpRoutePlanner是基于http上下文情况下,客户端到服务器的路由计算策略,一般没有代理的话,就不用设置这个东西。这里有一个很关键的概念—Route:在HttpClient中,一个Route指运行环境机器->目标机器host的一条线路,也就是如果目标url的host是同一个,那么它们的route也是一样的。

C、RequestConfig

RequestConfig是对request的一些配置。里面比较重要的有三个超时时间,默认的情况下这三个超时时间都为0(如果不设置request的Config,会在execute的过程中使用HttpClientParamConfiggetRequestConfig中用默认参数进行设置),这也就意味着无限等待,很容易导致所有的请求阻塞在这个地方无限期等待。这三个超时时间为:

1)connectionRequestTimeout—从连接池中取连接的超时时间

这个时间定义的是从ConnectionManager管理的连接池中取出连接的超时时间, 如果连接池中没有可用的连接,则request会被阻塞,最长等待connectionRequestTimeout的时间,如果还没有被服务,则抛出ConnectionPoolTimeoutException异常,不继续等待。

2)connectTimeout—连接超时时间

这个时间定义了通过网络与服务器建立连接的超时时间,也就是取得了连接池中的某个连接之后到接通目标url的连接等待时间。发生超时,会抛出ConnectionTimeoutException异常。

3)socketTimeout—请求超时时间

这个时间定义了socket读数据的超时时间,也就是连接到服务器之后到从服务器获取响应数据需要等待的时间,或者说是连接上一个url之后到获取response的返回等待时间。发生超时,会抛出SocketTimeoutException异常。

下面是一个设置各个超时时间的例子。这样设置的是该HttpClientc处理的所有request的默认配置,如果在构造request实例的时候不特别设置,则会使用默认配置。

注意:4.3.5版本超时设置方法和之前的版本不同

RequestConfig requestConfig = 
RequestConfig.custom().setConnectionRequestTimeout(CON_RST_TIME_OUT)
.setConnectTimeout(CON_TIME_OUT).setSocketTimeout(SOCKET_TIME_OUT).build();CloseableHttpClient httpClient = 
HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();

2、创建一个Request对象

HttpClient支持所有的HTTP1.1中的所有定义的请求类型:GET、HEAD、POST、PUT、DELETE、TRACE和OPTIONS。对使用的类为HttpGet、HttpHead、HttpPost、HttpPut、HttpDelete、HttpTrace和HttpOptions。Request的对象建立很简单,一般用目标url来构造就好了。下面是一个HttpPost的创建代码:

// 创建Http请求
HttpPost httpPost = new HttpPost(url);

一个Request还可以addHeader、setEntity、setConfig等,一般这三个用的比较多。

RequestConfig这个类比较关键,就是request的配置,除了上面说到的三个超时时间外,还有一些可能有助于理解处理过程的配置。

staleConnectionCheckEnabled:这个配置默认为true,HttpClient的execute方法中有下面的代码(**MainClientExec类中,**它是ClientExecChain接口中execute方法的实现类之一),也就是说如果这个设置为true的话,是会自动关闭那些状态为stale的managedConnection(HttpClientConnection)所管理的connection和socket(和remote ip)。

if (config.isStaleConnectionCheckEnabled() && managedConn.isOpen()) {this.log.debug("Stale connection check");if (managedConn.isStale()) {this.log.debug("Stale connection detected");managedConn.close();}}

3、执行Request请求

执行Request请求就是调用HttpClient的execute方法。最简单的使用方法是调用execute(final HttpUriRequest request)。

HttpClient允许http连接在特定的Http上下文中执行,HttpContext是跟一个连接相关联的,所以它也只能属于一个线程,如果没有特别设定,在execute的过程中,HttpClient会自动为每一个Connection new 一个HttpClientHttpContext。

在InternalHttpClient类的doExecute()方法中,有如下一行代码:

HttpClientContext localcontext = HttpClientContext.adapt((HttpContext)(context != null ? context : new BasicHttpContext()));

整个execute执行的常规流程为:

  1. new一个HttpContext
  2. 取出Request和URL
  3. 根据HttpRoute的配置看是否需要重写URL
  4. 根据URL的host、port和scheme设置target
  5. 在发送前用http协议拦截器处理request的各个部分
  6. 取得验证状态、user token来验证身份
  7. 从连接池中取一个可用的连接
  8. 根据request的各种配置参数以及取得的connection构造一个connManaged
  9. 打开managed的connection(包括创建route、dns解析、绑定socket、socket连接等)
  10. 请求数据(包括发送请求和接收response两个阶段)
  11. 查看keepAlive策略,判断连接是否要复用,并设置相应标识
  12. 返回response
  13. 用http协议拦截器处理response的各个部分

4、处理response

HttpReaponse是将服务端发回的Http响应解析后的对象。CloseableHttpClient的execute方法返回的response都是CloseableHttpResponse类型。可以getFirstHeader(String)、getLastHeader(String)、headerIterator(String)取得某个Header name对应的迭代器、getAllHeaders()、getEntity、getStatus等,一般这几个方法比较常用。

在这个部分中,对于entity的处理需要特别注意一下。一般来说一个response中的entity只能被使用一次它是一个流,这个流被处理完就不再存在了。先response.getEntity()再使用HttpEntity#getContent()来得到一个java.io.InputStream,然后再对内容进行相应的处理。

在HttpEntity接口的实现类HttpEntityWrapper中的源码如下:

public InputStream getContent() throws IOException {return this.wrappedEntity.getContent();
}

有一点非常重要,想要复用一个connection就必须要让它占有的系统资源得到正确释放。释放资源有两种方法:

1)关闭和entity相关的content stream

如果是使用outputStream就要保证整个entity都被write out,如果是inputStream,则再最后要记得调用inputStream.close()。或者使用EntityUtils.consume(entity)EntityUtils.consumeQuietly(entity)来让entity被完全耗尽(后者不抛异常)来做这一工作。EntityUtils中有个toString方法也很方便的(调用这个方法最后也会自动把inputStream close掉的),不过只有在可以确定收到的entity不是特别大的情况下才能使用。

如果没有让整个entity被fully consumed,则该连接是不能被复用的,很快就会因为在连接池中取不到可用的连接超时或者阻塞在这里(因为该连接的状态将会一直是leased的,即正在被使用的状态)。所以如果想要复用connection,一定一定要记得把entity fully consume掉,只要检测到stream的eof,是会自动调用ConnectionHolder的releaseConnection方法进行处理的(注意,ConnectionHolder并不是一个public class,虽然里面有一些跟释放连接相关的重要操作,但是却无法直接调用)。

2)关闭response

执行response.close()虽然会正确释放掉该connection占用的所有资源,但是这是一种比较暴力的方式,采用这种方式之后,这个connection就不能被重复使用了。

public void abortConnection() {if (this.released.compareAndSet(false, true)) {synchronized(this.managedConn) {try {this.managedConn.shutdown();this.log.debug("Connection discarded");} catch (IOException var8) {if (this.log.isDebugEnabled()) {this.log.debug(var8.getMessage(), var8);}} finally {this.manager.releaseConnection(this.managedConn, (Object)null, 0L, TimeUnit.MILLISECONDS);}}}}

从源代码中可以看出,response.close()调用了connectionHolder的abortConnection方法(4.5.2版本中调用的是**releaseConnection()**方法),它会close底层的socket,并且release当前的connection,并把reuse的时间设为0。这种情况下的connection称为expired connection,也就是client端单方面把连接关闭。还要等待closeExpiredConnections方法将它从连接池中清除掉(从连接池中清除掉的含义是把它所对应的连接池的entry置为无效,并且关掉对应的connection,shutdown对应socket的输入和输出流。这个方法的调用时间是需要设置的)。

关闭stream和关闭response的区别在于前者会尝试保持底层的连接alive,而后者会直接shut down并且丢弃connection。

socket是和ip以及port绑定的,但是host相同的请求会尽量复用连接池里已经存在的connection(因为在连接池里会另外维护一个route的子连接池,这个子连接池中每个connection的状态有三种:leased、available和pending,只有available状态的connection才能被使用,而fully consume entity就可以让该连接变为available状态),如果host地址一样,则优先使用该connection

如果希望重复读取entity中的内容,就需要把entity缓存下来。最简单的方式是用entity来new一个BufferedHttpEntity,这一操作会把内容拷贝到内存中,之后使用这个BufferedHttpEntity就可以了。

5、关闭HttpClient

调用httpClient.close()会先shut down connection manager,然后再释放该HttpClient所占用的所有资源,关闭所有在使用或者空闲的connection包括底层socket。由于这里把它所使用的connection manager关闭了,所以在下次还要进行http请求的时候,要重新new一个connection manager来build一个HttpClient(也就是在需要关闭和新建Client的情况下,connection manager不能是单例的)。

三、相关知识补充

1、关于keep-alive

在HttpClient.execute得到response之后的相关代码中,它会先取出response的keep-alive头来设置connection是否resuable以及存活的时间。如果服务器返回的响应中包含了Connection:Keep-Alive(默认有的),但没有包含Keep-Alive时长的头消息HttpClient认为这个连接可以永远保持

不过,很多服务器都会在不通知客户端的情况下,关闭一定时间内不活动的连接,来节省服务器资源。在这种情况下默认的策略显得太乐观,我们可能需要自定义连接存活策略,也就是在创建HttpClient的实例的时候用下面的代码。(xxx为自己写的保活策略)

ClosableHttpClient client = HttpClients.custom().setKeepAliveStrategy(xxx).build();

2、连接池管理

前面也有说到关于从连接池中取可用连接的部分逻辑。**完整的逻辑是:**在每收到一个route请求后,连接池都会建立一个以这个route为key的子连接池。当有一个新的连接请求到来的时候,它会优先匹配已经存在的子连接池们,如果之前已经有过以这个route为key的子连接池,那么就会去试图取这个子连接池中状态为available的连接。如果此时有可用的连接,则将取得的available连接状态改为leased的,取连接成功。如果此时子连接池没有可用连接,那再看是否达到了所设置的最大连接数和每个route所允许的最大连接数的上限,如果还有余量则new一个新的连接,或者取得lastUsedConnection,关闭这个连接、把连接从原来所在的子连接池删除,再lease取连接成功。如果此时的情况不允许再new一个新的连接,就把这个请求连接的请求放入一个queue中排队等待,直到得到一个连接或者超时才会从queue中删去。

一个连接被release之后,会从等待连接的queue中唤醒等待连接的服务进行处理。

3、连接回收策略

当连接被管理器收回后,这个连接仍然存活,但是却无法监控socket的状态,也无法对I/O事件做出反馈。如果连接被服务器端关闭了,客户端监测不到连接的状态变化(也就无法根据连接状态的变化,关闭本地的socket)。HttpClient为了缓解这一问题造成的影响,会在使用某个连接前,监测这个连接是否已经过时如果服务器端关闭了连接,那么连接就会失效。前面提到的RequestConfig中的staleConnectionCheckEnabled就是用来控制是否进行上述操作,相关代码:

if (config.isStaleConnectionCheckEnabled() && managedConn.isOpen()) {this.log.debug("Stale connection check");if (managedConn.isStale()) {this.log.debug("Stale connection detected");managedConn.close();}}

下边是此文章原著作者的一些分析思路,我觉得非常不错,Copy一下:

其中的managedConn.isStale()就是检查取出的连接是否失效,需要注意的是这种过时检查并不是100%有效,并且会给每个请求增加10到30毫秒额外开销。isStale()有一点比较奇怪的是,如果抛出SocketTimeoutException的时候会返回false,即意味着此managedConn并不是失效的(如果此managedConn是长连接的,那么没失效是可理解的,但为什么会抛SocketTimeoutException异常就不懂了)。而这里SocketTimeoutException的发生与我们前面设置的RequestConfig.sotimeout是没有关系的,它实现的机制是先设置1ms的超时时间,看在这1ms内是否能从inputBuffer里面读到数据,如果读到的数据长度为-1(即没有数据),说明此连接失效。但是很经常随机会发生SocketTimeoutException,这时会返回false,并且此时managedConn是open的状态,这样就会跳过后面的dns解析及socket重新建立和绑定的过程,直接再次重用之前的connection以及它绑定的socket。

在这里遇到的一个很纠结的问题:

Http1.1默认进行的长连接并不适用于我们的应用场景,我们的httpClient是用在服务端代替客户端sdk去请求另一个应用的服务端,并且调用量非常大,在这种情况下,如果使用默认的长连接就会一直只去请求对方的某一台服务器,不管怎么说,虽然调用的确实是相同host的主机对功能来说是没有问题的,但万一对方服务器被这样弄挂了呢?并且这种情况下要是使用了dns负载均衡技术,那么**dns的负载均衡将不能被执行到!**这显然不是我们所希望的。
并且通过测试发现,只要是长连接的connection,在代码中调用各种close或者release方法都不能把connection真正关掉,除非把整个httpClient.close。

对于这个问题查了一些资料,里面提到的一个可行的解决办法,**是建立一个监控线程,来专门回收由于长时间不活动而被判定为失效的连接。**这个监控线程可以周期性的调用ClientConnectionManager类的closeExpiredConnections()方法来关闭过期的连接,回收连接池中被关闭的连接。它也可以选择性的调用ClientConnectionManager类的closeIdleConnections()方法来关闭一段时间内不活动的连接。由于这个解决方案对于我们的应用来说太复杂了,所以这个方案的有效性没有验证过。

原先采用的解决方式是:在每次连接请求到来的时候都build一个新的HttpClient对象,并且使用BasicHttpClientConnectionManager作为connectionManager。然后在处理完http response之后 close掉这个HttpClient。目前本地自测来看,这种做法不会出现上面的奇怪问题。但是很忧伤的是,新建一个HttpClient的逻辑很重,并且连接不能复用,会浪费很多时间。

由于这个日常需求本身做的就是优化性质的工作,加上每个请求都新建HttpClient这一大坨代码,心里总是有点难受。继续找解决办法。

在尝试了改系统的各种tcp配置参数还有其他的socket、系统配置无果后,最终找到的解决方式却异常简单。简单来说,其实我们的应用场景下需要的是短连接,这样只要在request中添加Connection:close的头部,就可以保证这个链接在这次请求完成之后就被关掉,只用一次。同时发现,如果头中既有Connection:Keep-Alive又有Connection:close的话,Connection:close并不会有更高的优先级,依旧会保持长连。

四、总结

使用HttpClient的时候特别需要注意的有下面几个地方:

  • 连接池最大连接数,默认为20
  • 同个route的最大连接数,默认为2
  • 去连接池中取连接的超时时间,不配置则无限期等待
  • 与目标服务器建立连接的超时时间,不配置则无限期等待
  • 去目标服务器取数据的超时时间,不配置则无限期等待
  • 要fully consumed entity,才能正确释放底层资源
  • 同个host但ip有多个的情况,请谨慎使用单例的HttpClient和连接池
  • HTTP1.1默认支持的是长连接,如果想使用短连接,要在request上加Connection:close的header,不然长连接是不可能自动被关掉的!

一定要结合实际情况来看是否需要设置,不然可能导致严重的问题。

HttpClient的内容远不止我上面说到的这些,还包括Cookie管理,Fluent API等内容,由于没有实际使用,理解的并不透彻,后续继续学习后再来补充。

问题:连接池里的连接是长连接吗?还是说调用方拿到这个连接还要与server三次握手?

原文作者的分析逻辑,还没有来得及验证,先摘录如下:

TCP的三次握手是发生在socket的connect方法被调用的时候,从代码里看,这部分的调用链路是MainClientExec#execute->(条件if (!managedConn.isOpen()) )MainClientExec#establishRoute->PoolingHttpClientConnectionManager#connect->HttpClientConnectionOperator#connect->PlainConnectionSocketFactory#connectSocket->Socket#connect。也就是文中第四点讲的“打开managed的connection(包括创建route、dns解析、绑定socket、socket连接等)”这部分实现。

如果某个连接在response的header中带了keep-alive,那么它是以长连接的形式存在的,下次有相同目标host的请求,它会优先取得这个连接(包括底层socket的ip和post),如果底层的socket依然可用,那么就用它直接进行通信,不会再进行三次握手的过程。

关于如何让一个放回pool的connection以长连接存在,这是在MainClientExec#execute中有if (reuseStrategy.keepAlive(response, context)) 里的相关逻辑给connection打上reusable的标并设置有效时间。然后在response.entity被fully consumed之后,会自动调用EofSensorInputStream#close,这个方法中会对connection进行release操作,最终会调用到ConnectionHolder#releaseConnection(),在这个方法中对是否reusable的连接进行不同的release操作,对于reusable的类型,并不会去close底层的socket。所以它就一直保持长连接。

不过为什么会出现明明是长连接,间隔时间较长的话调用isStable()却返回true,然后把socket关掉呢?个人猜测有可能是由于链接空闲了一段时间对方把长连接关掉了,这种情况下是会重新进行三次握手的

当然短连接的情况下,socket也是关掉了的。

注:文章大部分为Copy,小部分为自己订正,原文已经找不到了,剽窃了别人转载的_

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/554756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你还在 Docker 中跑 MySQL?

容器的定义&#xff1a;容器是为了解决“在切换运行环境时&#xff0c;如何保证软件能够正常运行”这一问题。 目前&#xff0c;容器和 Docker依旧是技术领域最热门的词语&#xff0c;无状态的服务容器化已经是大势所趋&#xff0c;同时也带来了一个热点问题被大家所争论不以&…

使用mybatis-plus来自定义排序

需求&#xff1a; 先时间升序排序&#xff0c;相同的时间在按状态排序&#xff0c;状态的顺序为1 在线 4 潜伏 2 隐身 3 离开&#xff0c;状态相同在按姓名升序排序对排序好的数据进行分页运用mybatis-plus中QueryWrapper 1.导入依赖 <dependencies><dependency>…

Postman实现接口测试(附项目实战)

文章目录Postman实现接口测试1.Postman介绍和安装2. Postman安装2.1 安装方式2.2 安装步骤3. Postman入门示例Postman基本用法Postman高级用法1. 管理用例2. Postman断言3. 全局变量与环境变量5. Postman关联6. 批量执行测试用例7. 读取外部文件实现参数化Postman测试报告目标项…

Postman 使用教程详解

Postman页面 2、新建一个项目 直接点击左边栏上面的添加目录图标来新增一个根目录&#xff0c;这样就等于新建了一个项目&#xff0c;我们可以把一个项目或一个模块的用例都存放在这个目录之下&#xff0c;并且在根目录之下我们还可以在建立子目录来进行功能用例的细分&#…

Springboot dubbo @Service @Transactional 无法提供服务或者无法提供事务的解决办法

问题场景&#xff1a; 今天在springboot中集成spring事务的时候&#xff0c;遇到了一个大坑。如果&#xff08;springbootdubbo&#xff09;中添加 Service、Transactional 两个注解的时候&#xff0c;就不能进行dubbo服务注册了。 解决历程&#xff1a; 1&#xff0c;先是在…

什么是 serialVersionUID ? 序列化对象时必须提供 serialVersionUID 吗?

什么是 serialVersionUID &#xff1f; 序列化对象时必须提供 serialVersionUID 吗&#xff1f; 1&#xff0c;什么是 serialVersionUID &#xff1f; 顾名思义&#xff0c;serialVersionUID是序列化版本号。所有可序列化的类&#xff0c;都有一个静态serialVersionUID属性&a…

谈Java集合类的toArray()的小bug

谈Java集合类的toArray()的小bug toArray()方法 它的作用是将集合转换成数组。但是这个方法有一个弊端&#xff0c;当toArray()方法使用不当时会产生ClassCastException&#xff08;类转换异常&#xff09; public static void main(String[] args) {List<Integer> li…

Dubbo系统里面MultipartFile文件传输问题Dubbo文件上传/传输服务

今天遇到一个问题&#xff0c;在Controller 层里面&#xff0c;直接使用MultipartFile 来接收上传的图片&#xff0c;遇到几个坑。 一、在spring配置文件里面配置文件上传 <!--文件上传--><bean name"multipartResolver"class"org.springframework.web…

Dubbo2.7文档详解

本篇博文参考dubbo官方文档 本编博文参考javaguide之rpc 文章目录一.RPC1.1 什么是 RPC?1.2 为什么要用 RPC?1.3 RPC 能帮助我们做什么呢&#xff1f;1.4 RPC 的原理是什么?1.5 常见的 RPC 框架总结二.既有 HTTP ,为啥用 RPC 进行服务调用?2.1 RPC只是一种设计而已2.2 HTTP…

12nm 制程、Zen+ 微架构 AMD Ryzen 7 2700X 处理器详细测试 - 电脑领域 HKEPC Hard

12nm 制程、Zen 微架构 AMD Ryzen 7 2700X 处理器详细测试 代号 Pinnacle Ridge、AMD 第二代 Ryzen 处理器正式登场&#xff0c;基于经改良的 Zen 微架构&#xff0c;改善了 Cache 及记忆体延迟表现&#xff0c;更先进的 12nm LP 制程&#xff0c;令核心时脉进一步提升&#…

Java之Serializable接口实现序列化和反序列化实例以及部分序列化的四种方法

首先需要明确的概念: 序列化&#xff1a;将数据结构或对象转换成二进制字节流的过程反序列化&#xff1a;将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程持久化&#xff1a;将数据写入文件中长久保存的过程称之为持久化序列化主要目的&#xff1a;是…

win10死机频繁怎么解决

Windows 10已经推出好几年&#xff0c;系统趋于稳定&#xff0c;但依旧不是完美&#xff0c;蓝屏、死机状态还是会出现&#xff0c;只是概率降低了很多&#xff0c;如果你的电脑遇到了突然死机或者频繁卡死的情况&#xff0c;或许你应该考虑对电脑进行重置了。系统自带的恢复重…

Jar包常见的反编译工具介绍与使用

反编译JAR能干什么: 排查问题、分析商业软件代码逻辑&#xff0c;学习优秀的源码思路。 反编译工具介绍 JD-GUI 下载地址&#xff1a;http://java-decompiler.github.io/ 点评&#xff1a;支持的java版本不会太高&#xff0c;中文注释能够正常显示。 Luyten 下载地址&#…

400 bad request的原因意思和解决方法

我们的电脑在使用的过程中&#xff0c;有的小伙伴在上网的时候可能就遇到过系统提示&#xff1a;400 bad request的情况。据小编所知这种情况&#xff0c;大致意思就是出现了错误的请求或者请求不能满足。原因是因为我们请求的语法格式出现呢错误或者其他情况等等。我们可以通过…

重装系统win10提示磁盘布局不受UEFI固件支持怎么办

原因分析&#xff1a; Win10系统新增UEFI检测机制&#xff0c;在BIOS开启了UEFI时&#xff0c;如果硬盘分区表格式不是GPT&#xff0c;则会提示无法重装系统win10&#xff0c;也就是说UEFIGPT或LegacyMBR才能安装win10。 解决方法一&#xff1a;关闭UEFI 1、重启系统时按Del…

win7按f8后没有进入安全模式怎么解决

win7f8后没有进入安全模式&#xff0c;在正确操作按F8没有进入安全模式之后&#xff0c;不知道怎么解决win7按f8后没有进入安全模式怎么解决&#xff0c;其实非常的简单&#xff0c;下面来看看详细的解决方法吧。 win7按f8后没有进入安全模式怎么解决 第一种方法&#xff1a;…

电脑开机就进入bios的解决方法

最近很多人反映自己的电脑一开机就直接进入bios里&#xff0c;无法正常进入系统。这是怎么回事呢?开机进入bios无法进入系统怎么办呢?别着急&#xff0c;今天就为大家带来电脑开机就进入bios的解决方法。 电脑开机就进入bios的解决方法&#xff1a; 1、如果是电脑的硬盘出了…

Serializable序列化和Externalizable序列化与反序列化的使用

1、序列化是干啥用的&#xff1f; 序列化的原本意图是希望对一个Java对象作一下“变换”&#xff0c;变成字节序列&#xff0c;这样一来方便持久化存储到磁盘&#xff0c;避免程序运行结束后对象就从内存里消失&#xff0c;另外变换成字节序列也更便于网络运输和传播&#xff…

Win10系统如何查看电脑是否是UEFI启动模式

Win10系统如何查看电脑是否是UEFI启动模式?现在越来越多的新电脑都采用UEFI来引导电脑系统&#xff0c;UEFI提高了开机后操作系统的启动速度&#xff0c;使电脑更加流畅&#xff0c;安全性更强&#xff0c;而传统的BIOS则没有UEFI用起来那么好。如何查看自己Win10系统电脑是否…

win10如何关闭Windows Defender安全保护程序

win10如何关闭Windows Defender安全保护程序&#xff1f;win10如何关闭Windows Defender安全保护程序?在安装的windows操作系统中&#xff0c;自带着安全保护程序“Windows Defender应用”&#xff0c;其实主要是为了保护好其他运行的软件&#xff0c;那该怎么来关闭这个软件呢…