更新时间:2015年1月4日
我仍然有这些问题。我们的应用程序的用户增加了,我明白了 各种网络错误。我们的应用程序每次都会发送电子邮件 是app上与网络相关的错误。
我们的应用程序进行金融交易 - 因此重新提交并非如此 幂等 - 非常害怕启用HttpClient的重试功能。 我们在服务器上做了某种响应缓存来处理 用户明确重新提交。但是,仍然没有解决方案 没有糟糕的用户体验。
原始问题
我有一个Android应用程序,它将数据作为用户操作的一部分发布。该数据包括少量图像和数据。我将它们打包为Protobuf消息(字节数组,实际上)并通过HTTPS连接将其发布到服务器。
虽然应用程序在大多数情况下都能正常工作,但我们偶尔会看到连接错误。由于我们在相对较慢的网络区域(2G连接)中有一些用户,因此问题变得更加明显。但是,问题不仅限于连接缓慢的区域,客户使用WiFi和3G连接也会出现问题。
以下是我们在App日志中注意到的一些例外
在5分钟后发生以下情况,因为我已将Socket超时设置为5分钟。该应用程序试图在这种情况下发布145kb的数据
堆栈跟踪java.net.SocketTimeoutException:读取超时 at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_read(Native 方法) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $ SSLInputStream.read(OpenSSLSocketImpl.java:662) 在org.apache.http.impl.io.AbstractSessionInputBuffer.fillBuffer(AbstractSessionInputBuffer.java:103) at org.apache.http.impl.io.AbstractSessionInputBuffer.readLine(AbstractSessionInputBuffer.java:191)
下面发生了2.5分钟(套接字超时设置为5分钟),客户端发送了144kb的数据
javax.net.ssl.SSLException:写入错误:ssl = 0x5e4f4640:I / O错误 在系统调用期间,断管 at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_write(Native 方法) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $ SSLOutputStream.write(OpenSSLSocketImpl.java:704) 在org.apache.http.impl.io.AbstractSessionOutputBuffer.write(AbstractSessionOutputBuffer.java:109) at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113)
1分钟后发生了一次。
堆栈跟踪javax.net.ssl.SSLException:由peer关闭的连接 at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native 方法) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:378) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $ SSLInputStream。(OpenSSLSocketImpl.java:634) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:605)
在77秒之后发生了一次
堆栈跟踪javax.net.ssl.SSLException:SSL握手已中止: ssl = 0x5e2baf00:系统调用期间的I / O错误,由对等方重置连接 at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native 方法) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:378) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $ SSLInputStream。(OpenSSLSocketImpl.java:634) 在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:605) 在org.apache.http.impl.io.SocketInputBuffer。(SocketInputBuffer.java:70)
15秒后发生一次(连接超时设置为15秒)
拍摄时间:15081叠痕 org.apache.http.conn.ConnectTimeoutException:连接到 /103.xx.xx.xx:443超时 在org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:144) 在org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) 在org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) 在org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:365)
以下是我用于发布请求的源代码片段
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 15000); //15 seconds
HttpConnectionParams.setSoTimeout(params, 300000); // 5 minutes
HttpClient client = getHttpClient(params);
HttpPost post = new HttpPost(uri);
post.setEntity(new ByteArrayEntity(requestByteArray));
HttpResponse httpResponse = client.execute(post);
....
public static HttpClient getHttpClient(HttpParams params) {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new TrustAllCertsSSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
DefaultHttpClient client = new DefaultHttpClient(ccm, params);
// below line of code will disable the retrying of HTTP request when connection is timed
// out.
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
return client;
} catch (Exception e) {
return new DefaultHttpClient();
}
}
我已经阅读了一些论坛,表明我们应该使用HttpUrlConnection类。我确实进行了代码更改以使用https://code.google.com/p/basic-http-client/作为热修复。虽然它可以在我的三星手机上运行,但它似乎在手机客户使用中存在一些问题,甚至无法连接到我们的网站。我不得不回滚它,但如果根本原因可以固定到DefaultHttpClient,我可以重新查看它。
OUr Web服务器是nginx,我们的Web服务在Apache Tomcat上运行。 客户大多使用Android 4.1+手机。从我的手机上面检索到堆栈跟踪的客户正在使用带有Android 4.2.1的Micromax A110Q手机
对此的任何意见都将受到高度赞赏。非常感谢!
更新
if (client != null) { client.getConnectionManager().shutdown(); }
client_max_body_size 5M;
proxy_read_timeout 300;
通过上述更改,错误有所减少。在过去的一周里,我看到了以下两种类型的错误:
org.apache.http.conn.ConnectTimeoutException: Connect to /103.xx.xx.xxx:443 timed out
- 这发生在15秒内,这是我的连接超时。我假设这是因为客户端由于网络速度缓慢而无法访问服务器或@JaySoyer指出,可能是由于网络切换。
java.net.SocketTimeoutException: SSL handshake timed out at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
。这是在套接字超时到期时发生的。我现在使用1分钟作为小型请求的套接字超时,对于高达75 KB及更高的数据包分别使用3分钟和6分钟。
然而,这些错误已经大大减少了,而且我发现100个请求中有1个失败,而我的代码的早期版本则是10个请求中的1个。
答案 0 :(得分:12)
我最近不得不对我公司的应用程序进行详尽的分析,因为我们看到了一堆类似的错误,并且不知道为什么。我们最终发布了自定义应用程序,它们将连接时间,错误,信号质量等记录到文件中。几周之后就这样做了。收集数以千计的数据点。请记住,我们在应用程序打开时保持持久连接。
原来我们的大多数错误来自交换网络。这对普通用户来说实际上很常见。因此,假设用户正在使用EDGE小区网络,然后在WIFI范围内行走,反之亦然。发生这种情况时,Android会逐字地切断单元连接,并与WIFI建立全新的连接。从应用程序的角度来看,它类似于打开飞行模式,然后再次将其重新打开。这甚至在小区网络内切换时发生。例如,LTE到HSPA +。每次发生这种情况,Android都会启动网络连接改变广播。
在您列出的内容中,此行为导致以下类似错误:
有时网络切换很快,有时很慢。事实证明,我们没有使用快速开关及时清理我们的资源。因此,我们尝试使用陈旧/旧的TCP连接重新连接到我们的服务器,这些连接引发了更多奇怪的错误。
所以我猜想,如果你长时间保持连接,就会看到手机不断在网络之间切换,特别是当信号很弱时。当发生网络切换时,您将看到SSLExeptions并且它完全正常。只需要确保清理资源并正确重新连接。
答案 1 :(得分:2)
由于您正在处理看起来网络连接不佳的问题,因此请考虑使用容错的HTTP客户端。我喜欢的是OkHTTP。从他们的描述:
OkHttp坚持网络很麻烦:它会默默无闻 从常见的连接问题中恢复。如果您的服务有多个 IP地址如果是第一个,OkHttp将尝试备用地址 连接失败。这对于IPv4 + IPv6和托管的服务是必需的 在冗余数据中心。 OkHttp启动新的连接 现代TLS功能(SNI,ALPN),如果是,则回退到SSLv3 握手失败。
实施将主要是直接替代。