更新时间:2015年1月4日
我还有这些问题.我们的应用程序的用户增加了,我看到了所有类型的网络错误.每当应用程序出现网络相关错误时,我们的应用程序就会发送电子邮件.
我们的应用程序进行了金融交易 - 因此重新提交并不是真正的幂等 - 因此非常害怕启用HttpClient的重试功能.我们在服务器上做了某种响应缓存来处理用户明确完成的重新提交.但是,仍然没有解决方案,没有糟糕的用户体验.
原始问题
我有一个Android应用程序,它发布数据作为用户操作的一部分.数据包含少量图像,我将它们打包为Protobuf消息(实际上是字节数组),并通过HTTPS连接将其发布到服务器.
尽管应用程序在大多数情况下都能正常工作,但我们偶尔会看到连接错误.由于我们在相对较慢的网络区域(2G连接)中有一些用户,因此问题变得更加明显.然而,问题不仅限于连接速度慢的区域,客户使用WiFi和3G连接也会出现问题.
以下是我们在App日志中注意到的一些例外情况
下面的一个发生在5分钟后,因为我已将Socket超时设置为5分钟.该应用程序试图在这种情况下发布145kb的数据
堆栈跟踪java.net.SocketTimeoutException:在org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_read(本机方法)中读取超时时间,位于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错误,org.apache中的org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_write(本机方法)中的管道损坏. harmony.xnet.provider.jsse.OpenSSLSocketImpl $ SSLOutputStream.write(OpenSSLSocketImpl.java:704)位于org.apache.http.impl的org.apache.http.impl.io.AbstractSessionOutputBuffer.write(AbstractSessionOutputBuffer.java:109). io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113)
1分钟后发生了一次.
堆栈跟踪javax.net.ssl.SSLException:org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake上org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(本地方法)的对等关闭连接(OpenSSLSocketImpl.java:378)org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $ SSLInputStream.(OpenSSLSocketImpl.java:634)at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl. Java的:605)
77秒后发生了一次
堆栈跟踪javax.net.ssl.SSLException:SSL握手中止:ssl = 0x5e2baf00:系统调用期间的I/O错误,org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(本机方法)中的对等连接重置在org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:378)org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $ SSLInputStream.(OpenSSLSocketImpl.java:634)at org. org.apache.http.impl.io.SocketInputBuffer上的apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:605).(SocketInputBuffer.java:70)
15秒后发生一次以下(连接超时设置为15秒)
拍摄时间:15081堆栈跟踪org.apache.http.conn.ConnectTimeoutException:连接到org.apache.http.conn.scheme.PlainSocketFactory.connectSocket上的/103.xx.xx.xx:443超时(PlainSocketFactory.java:121 )org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:144)位于org.apache.http的org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164). impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)at 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手机
对此的任何意见都将受到高度赞赏.非常感谢!
更新:
我注意到我们没有关闭Connection Manager.所以在我使用http客户端的代码的finally块中添加了下面的代码.
if (client != null) { client.getConnectionManager().shutdown(); }
更新了nginx配置以接受最大为5M的数据,因为它的默认值为1Mb,而一些客户端提交的数据超过1MB,服务器正在切断与413错误的连接.
client_max_body_size 5M;
还增加了nginx代理读取超时,以便等待从客户端获取数据的时间更长.
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个.
我最近不得不对我公司的应用程序进行详尽的分析,因为我们看到了一堆类似的错误而且不知道为什么.我们最终发布了自定义应用程序,它们将连接时间,错误,信号质量等记录到文件中.几周之后就这样做了.收集数以千计的数据点.请记住,我们在应用程序打开时保持持久连接.
事实证明,我们的大多数错误来自交换网络.这对普通用户来说实际上很常见.因此,假设用户正在使用EDGE小区网络,然后在WIFI范围内行走,反之亦然.发生这种情况时,Android会逐字地切断单元连接,并与WIFI建立全新的连接.从应用程序的角度来看,它类似于打开飞行模式然后再次将其重新打开.这甚至在小区网络内切换时发生.例如,LTE到HSPA +.每次发生这种情况,Android都会关闭网络连接改变广播.
在您列出的那些中,此行为导致以下类似错误:
javax.net.ssl.SSLException:写入错误:ssl = 0x5e4f4640
javax.net.ssl.SSLException:SSL握手中止:
有时网络交换机很快,有时很慢.事实证明,我们没有使用快速开关及时清理我们的资源.因此,我们尝试使用陈旧/旧的TCP连接重新连接到我们的服务器,这些连接引发了更多奇怪的错误.
所以我猜想,如果你长时间保持连接,那么预计会看到手机不断在网络之间切换,尤其是在信号较弱时.当发生网络切换时,您将看到SSLExeptions,这是完全正常的.只需要确保清理资源并正确重新连接.