背景
最近通过网络测试, 发现https连接未复用的问题.自己通过wireshark抓包确实是有连接未复用的问题.网上搜寻答案,也没找到结果.于是自己阅读源码找寻答案.
一开始我的代码是这样的:
/**
* 处理https请求
*
* @param conn
*/
private void handleHttps(HttpURLConnection conn) {
if (conn instanceof HttpsURLConnection) {
SSLContext sslContext = SSLContextUtil.getSSLContext();
if (sslContext != null) {
final String hostName = SSLContextUtil.getHostName(httpParams.ip);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
((HttpsURLConnection) conn).setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
if (SSLContextUtil.isSecureHost(hostname, hostName)) {
return true;
} else {
HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
return hostnameVerifier.verify(hostname, session);
}
}
});
}
}
}
如何分析连接复用问题
由于我用的是mac电脑, 我就用mac电脑抓包来分析.Mac电脑需安装charls、wireshark等抓包软件.
1、首先手机Wi-Fi设置代理,输入电脑IP及charls抓包端口.
2、打开charls、wireshark, 手机发送请求,如果每次请求都发生了syn包,则说明连接未复用
解决思路
通过阅读系统源码发现HttpUrlConnection底层使用OKHttp来实现的,OKHttp使用的版本还是2.0时代.如是找了2.7.5的版本的OKHttp来分析.
OKHttp从连接池获取连接的代码如下:
/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
RealConnection get(Address address, StreamAllocation streamAllocation) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
// TODO(jwilson): this is awkward. We're already holding a lock on 'this', and
// connection.allocationLimit() may also lock the FramedConnection.
if (connection.allocations.size() < connection.allocationLimit()
&& address.equals(connection.getRoute().address)
&& !connection.noNewStreams) {
streamAllocation.acquire(connection);
return connection;
}
}
return null;
}
复用的其中一个条件是address.equals(connection.getRoute().address), Address的equals方法有重写,逻辑如下
@Override public boolean equals(Object other) {
if (other instanceof Address) {
Address that = (Address) other;
return this.url.equals(that.url)
&& this.dns.equals(that.dns)
&& this.authenticator.equals(that.authenticator)
&& this.protocols.equals(that.protocols)
&& this.connectionSpecs.equals(that.connectionSpecs)
&& this.proxySelector.equals(that.proxySelector)
&& equal(this.proxy, that.proxy)
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier)
&& equal(this.certificatePinner, that.certificatePinner);
}
return false;
}
从我的代码看出,我有设置hostnameVerifier、sslSocketFactory.但是它们请求都是new一个出来. 于是我将hostnameVerifier、sslSocketFactory提出来,设置成全局的,每次请求时都使用它们. 通过wireshark抓包,已经可以复用连接了.
总结
当没有现成的答案时, 通过源码排查问题会让你恍然大悟. 处理问题原来就是这么的简单.