在写这个问题前,其实我是为了分析项目碰到的一个tcp close wait问题。这个问题就不在这里讲了。
造成的原因很简单,就是很多项目对httpclient的参数和使用都理解有问题,往往随便写一个或者使用网上的代码。导致在一些场景连接关闭有问题
httpclient大致有这些版本
- httpclient3.x
- httpclient4.x到httpclient4.3以下
- httpclient4.3以上
一般会选择4.3以上
https://www.baeldung.com/httpclient-connection-management 这是国外一个教学网站的说明,使用和说明还算比较ok
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
public class HttpClientUtils { private static CloseableHttpClient httpClient; static { // 基本配置 RequestConfig requestConfig = RequestConfig.custom() // 建连超时时间 .setConnectTimeout( 5000 ) // 传输超时时间 .setSocketTimeout( 3000 ) // 从连接池获取连接超时时间 .setConnectionRequestTimeout( 10000 ) .build(); // 连接管理器 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // 最大连接数 cm.setMaxTotal( 80 ); // 同一个路由最大连接数 cm.setDefaultMaxPerRoute( 20 ); // 客户端构造器 HttpClientBuilder clientBuilder = HttpClients .custom() .setConnectionManager(cm) .setConnectionManagerShared( false ) // 影响连接的关闭 .setDefaultRequestConfig(requestConfig); httpClient = clientBuilder.build(); } public static byte [] get(String url, String userAgent) throws IOException { HttpGet httpGet = new HttpGet(url); if (userAgent != null ) { httpGet.setHeader(HTTP.USER_AGENT, userAgent); } CloseableHttpResponse response = httpClient.execute(httpGet, HttpClientContext.create()); System.out.println( "----------------------------------------" ); System.out.println(response.getStatusLine()); HttpEntity entity = response.getEntity(); if (entity != null ) { try { return IOUtils.toByteArray(entity.getContent()); } finally { // 只需在传输完毕关闭流即可,详细见源码 EntityUtils.consumeQuietly(entity); } } return null ; } } |
服务端我简单启动了一个web服务(这里略过这个操作),然后客户端用demo进行请求处理
demo1:客户端单线程多次请求
1
2
3
4
5
6
7
8
9
10
|
for ( int i = 0 ; i < 20 ; i++) { try { } catch (IOException e) { } } CountDownLatch latch = new CountDownLatch( 1 ); |
客户端的tcp连接状况
服务器的tcp连接情况
符合预期,只有一个连接,连接复用
demo2:客户端多线程请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
for ( int i = 0 ; i < 100 ; i++) { new Thread( new Runnable() { @Override public void run() { try { } catch (IOException e) { } }).start(); } } CountDownLatch latch = new CountDownLatch( 1 ); |
客户端tcp连接情况
100个请求,20个连接,符合预期
重点来了,刚好为了测试单线程的close情况,于是把thread去掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
for ( int i = 0 ; i < 100 ; i++) { // new Thread(new Runnable() { // @Override // public void run() { try { } catch (IOException e) { } // } // }).start(); } CountDownLatch latch = new CountDownLatch( 1 ); |
控制台一看,连接呢???一下子可以说是搞不明白的
20正常100就没了,怀疑和100这个数字安息,于是模拟了以下测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
for ( int i = 0 ; i < 101 ; i++) { if (i == 98 || i == 99 || i == 100 ) { } try { } catch (IOException e) { } } CountDownLatch latch = new CountDownLatch( 1 ); |
打断点跟踪发现,reusable这个参数在100的时候,行为是和99和101不同的
于是看源码跟踪这个参数修改的地方
private volatile boolean reusable;
代码中有多处将此参数设置为false,其中在100的时候,命中断点。在MainClientExec.java中有如下使用,那么 reuseStrategy.keepAlive(response, context)是关键
继续看 reuseStrategy.keepAlive(response, context),有个叫Porxy-Connection的当100的时候,值为Close状态,证明这个是服务端的一些设置导致的,于是谷歌关键字keepalive
找到tomcat有如下参数配置,nginx也有,大致意思就是一个连接能被重复使用的次数,当超过他,就会断开。这也可以解释为什么100的时候,连接没了。101又开始建立新的连接
maxKeepAliveRequests |
The maximum number of HTTP requests which can be pipelined until the connection is closed by the server. Setting this attribute to 1 will disable HTTP/1.0 keep-alive, as well as HTTP/1.1 keep-alive and pipelining. Setting this to -1 will allow an unlimited amount of pipelined or keep-alive HTTP requests. If not specified, this attribute is set to 100. |