在写这个问题前,其实我是为了分析项目碰到的一个tcp close wait问题。这个问题就不在这里讲了。
造成的原因很简单,就是很多项目对httpclient的参数和使用都理解有问题,往往随便写一个或者使用网上的代码。导致在一些场景连接关闭有问题
httpclient大致有这些版本
- httpclient3.x
- httpclient4.x到httpclient4.3以下
- httpclient4.3以上
一般会选择4.3以上
https://www.baeldung.com/httpclient-connection-management 这是国外一个教学网站的说明,使用和说明还算比较ok
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. |






