java重新连接tcp_java – 使用HttpsUrlConnection重新使用TCP连接

执行摘要:我在Android应用程序中使用HttpsUrlConnection类,以串行方式通过TLS发送多个请求。所有请求的类型相同,并发送到同一台主机。起初我会为每个请求获得一个新的TCP连接。我能够解决这个问题,但不会在与readTimeout有关的某些Android版本上引起其他问题。我希望有一个更强大的实现TCP连接重用的方法。

背景

当检查Android应用程序的网络流量时,我正在使用Wireshark我观察到每个请求都导致建立了一个新的TCP连接,并执行了一个新的TLS握手。这导致相当多的延迟,特别是如果你在3G / 4G上,每次往返都可能需要相当长的时间。

然后我尝试了没有TLS(即HttpUrlConnection)的相同场景。在这种情况下,我只看到一个TCP连接正在建立,然后重新用于后续请求。因此,建立新TCP连接的行为特定于HttpsUrlConnection。

以下是一些示例代码来说明问题(真正的代码显然有证书验证,错误处理等):

class NullHostNameVerifier implements HostnameVerifier {

@Override

public boolean verify(String hostname, SSLSession session) {

return true;

}

}

protected void testRequest(final String uri) {

new AsyncTask() {

protected void onPreExecute() {

}

protected Void doInBackground(Void... params) {

try {

URL url = new URL("https://www.ssllabs.com/ssltest/viewMyClient.html");

try {

sslContext = SSLContext.getInstance("TLS");

sslContext.init(null,

new X509TrustManager[] { new X509TrustManager() {

@Override

public void checkClientTrusted( final X509Certificate[] chain, final String authType ) {

}

@Override

public void checkServerTrusted( final X509Certificate[] chain, final String authType ) {

}

@Override

public X509Certificate[] getAcceptedIssuers() {

return null;

}

} },

new SecureRandom());

} catch (Exception e) {

}

HttpsURLConnection.setDefaultHostnameVerifier(new NullHostNameVerifier());

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

conn.setSSLSocketFactory(sslContext.getSocketFactory());

conn.setRequestMethod("GET");

conn.setRequestProperty("User-Agent", "Android");

// Consume the response

BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));

String line;

StringBuffer response = new StringBuffer();

while ((line = reader.readLine()) != null) {

response.append(line);

}

reader.close();

conn.disconnect();

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

protected void onPostExecute(Void result) {

}

}.execute();

}

注意:在我的实际代码中,我使用POST请求,所以我使用输出流(写请求体)和输入流(读取响应体)。但我想保持这个例子简单而简单。

如果我重复调用testRequest方法,最后在Wireshark(abridged)中得到以下结果:

TCP 61047 -> 443 [SYN]

TLSv1 Client Hello

TLSv1 Server Hello

TLSv1 Certificate

TLSv1 Server Key Exchange

TLSv1 Application Data

TCP 61050 -> 443 [SYN]

TLSv1 Client Hello

TLSv1 Server Hello

TLSv1 Certificate

... and so on, for each request ...

我是否调用conn.disconnect对行为没有影响。

所以我最初虽然“好的,我将创建一个HttpsUrlConnection对象池,并尽可能重用已建立的连接”。没有骰子,不幸的是,由于Http(UrlConnection)实例显然不是要重复使用的。实际上,读取响应数据会导致输出流关闭,并且尝试重新打开输出流将触发java.net.ProtocolException,并显示错误消息“读取响应后无法写请求体”。

我做的下一件事是考虑设置HttpsUrlConnection的方式与设置HttpUrlConnection是不同的,即创建一个SSLContext和一个SSLSocketFactory。所以我决定让这两个静态的,并分享他们的所有请求。

在连接重用的意义上,这似乎正常工作。但在某些Android版本中出现了一个问题,除了第一个请求之外,所有请求都需要很长时间才能执行。进一步检查后,我注意到getOutputStream的调用将阻塞等于setReadTimeout设置的超时时间。

我第一次尝试修复它是在读完响应数据后,再加上一个非常小的值的setReadTimeout调用,但这似乎没有任何效果。

然后我做了一个更短的读取超时(几百毫秒),并实现了自己的重试机制,尝试重复读取响应数据,直到所有数据被读取或者达到了原来的预期超时。

唉,现在我在一些设备上得到TLS握手超时。那么我当时所做的就是在调用getOutputStream之前添加一个具有相当大值的setReadTimeout调用,然后在读取响应数据之前将读取超时更改为几百ms。这实际上似乎是稳固的,我在8或10个不同的设备上测试了它们,运行不同的Android版本,并且获得了所有这些所需的行为。

快进几周,我决定在Nexus 5上测试我的代码,运行最新的工厂图像(6.0.1(MMB29S))。现在我看到同样的问题,除了第一个,getOutputStream将在每个请求的readTimeout持续时间内阻塞。

更新1:正在建立的所有TCP连接的副作用是在某些Android版本(4.1 – 4.3 IIRC)上,可能遇到操作系统(?)中的错误,您的进程最终用完了文件描述符。这不可能在现实世界的条件下发生,但可以通过自动测试来触发。

更新2:OpenSSLSocketImpl class具有一个公共setHandshakeTimeout方法,可用于指定与readTimeout分离的握手超时。但是,由于该方法存在于套接字而不是HttpsUrlConnection,所以调用它有点棘手。即使可以这样做,在这一点上,您依赖于打开HttpsUrlConnection可能使用也可能不被使用的类的实现细节。

问题

对我来说似乎不太可能,连接重用不应该“只是工作”,所以我猜我做错了。有没有人设法可靠地获得HttpsUrlConnection来重用Android上的连接,并可以发现我所犯的任何错误?我真的很想避免使用任何第三方图书馆,除非这是完全不可避免的。

请注意,无论您想到的任何想法需要使用16的minSdkVersion。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值