【发布时间】:2014-09-23 09:58:27
【问题描述】:
更新:2015 年 1 月 4 日
我仍然有这些问题。我们应用程序的用户增加了,我明白了 各种网络错误。我们的应用程序每次在那里发送电子邮件 是应用程序上的网络相关错误。
我们的应用程序进行金融交易 - 所以重新提交并不是真的 幂等 - 非常害怕启用 HttpClient 的重试功能。 我们在服务器上做了某种响应缓存来处理 重新提交由用户明确完成。但是,仍然没有解决方案 在没有不良用户体验的情况下工作。
原始问题
我有一个 android 应用程序,它作为用户操作的一部分发布数据。数据包含少量图像,我将它们打包为 Protobuf 消息(实际上是字节数组)并通过 HTTPS 连接将其发布到服务器。
虽然该应用程序在大多数情况下都可以正常运行,但我们偶尔会看到连接错误。现在这个问题变得更加明显,因为我们在相对较慢的网络区域(2G 连接)中有一些用户。但是,问题不仅限于连接速度较慢的区域,使用 WiFi 和 3G 连接的客户也会出现问题。
以下是我们在应用日志中注意到的一些例外情况
5 分钟后发生以下情况,因为我已将 Socket 超时设置为 5 分钟。在这种情况下,该应用试图发布 145kb 的数据
堆栈跟踪 java.net.SocketTimeoutException:读取超时 在 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) 在 org.apache.http.impl.io.AbstractSessionInputBuffer.readLine(AbstractSessionInputBuffer.java:191)
下面发生了 2.5 分钟(套接字超时设置为 5 分钟),客户端正在发送 144kb 的数据
javax.net.ssl.SSLException:写入错误:ssl=0x5e4f4640:I/O 错误 在系统调用期间,断管 在 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) 在 org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113)
1 分钟后发生以下情况。
堆栈跟踪 javax.net.ssl.SSLException:连接被对等方关闭 在 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 错误,对等方重置连接 在 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) 在 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)
这是我用来发布请求的源代码 sn-ps
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,我可以重新审视它。
我们的网络服务器是 nginx,我们的网络服务在 Apache Tomcat 上运行。 客户大多使用 Android 4.1+ 手机。我从上面的堆栈跟踪中检索到其手机的客户正在使用装有 Android 4.2.1 的 Micromax A110Q 手机
对此的任何意见将不胜感激。非常感谢!
更新:
- 我注意到我们没有关闭连接管理器。所以在我使用 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 个失败。
【问题讨论】:
-
那可能是因为服务器不稳定并且会建立连接..你的服务器的最大命中是多少?
-
SSLException 在已经建立连接但在 SSL 握手期间连接超时时发生。因为在 SSL 握手开始后发生超时,所以 SSLException 作为更高级别的异常被抛出。因此,我希望您将超时时间再增加 20-25 分钟 ..我认为应该这样做
-
@adcom 我们每 5 到 10 分钟就有 1 次点击......我们目前的流量非常少。我们的应用只有 25 到 30 个用户
-
好的尝试增加你的超时我认为它应该工作
-
@adcom 是的,我已经通过增加超时(读取超时和套接字超时)来减少它 - 另外,我注意到 nginx 拒绝了一些超过 1 MB 的消息,所以我将请求大小增加到5 MB 作为预防措施。但是仍然存在一些问题 - 主要是客户端尝试连接到服务器时的读取超时和偶尔损坏的管道。感谢您的投入。
标签: java android apache ssl nginx