解决使用HttpsUrlConnection请求https出现连接未复用的问题.

背景

最近通过网络测试, 发现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抓包,已经可以复用连接了.

总结

当没有现成的答案时, 通过源码排查问题会让你恍然大悟. 处理问题原来就是这么的简单.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值