从安卓应用程序与服务器通信时出现各种HTTP错误

2022-09-03 14:57:52

最后更新: 04 01 2015

我仍然有这些问题。我们应用程序的用户增加了,我看到了各种网络错误。我们的应用程序每次应用程序上出现与网络相关的错误时都会发送电子邮件。

我们的应用程序执行金融交易 - 因此重新提交并不是真正幂等的 - 所以非常害怕启用HttpClient的重试功能。我们已经在服务器上进行了某种响应缓存,以处理用户显式完成的重新提交。但是,仍然没有解决方案可以在没有不良用户体验的情况下工作。

原始问题

我有一个Android应用程序,它将数据作为用户操作的一部分发布。数据包括一些图像,我将它们打包为Protobuf消息(实际上是字节数组),并通过HTTPS连接将其发布到服务器。

尽管该应用程序在大多数情况下运行良好,但我们偶尔会看到连接错误。现在,由于我们在相对较慢的网络区域(2G连接)中有一些用户,因此问题变得更加明显。但是,问题不仅限于连接速度慢的区域,使用WiFi和3G连接的客户可以看到问题。

以下是我们在应用程序日志中注意到的一些例外情况

以下情况发生在5分钟后,因为我已将Socket超时设置为5分钟。在这种情况下,该应用程序试图发布145kb的数据

Stack trace java.net.SocketTimeoutException: Read timed out at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_read(Native Method) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:662) at 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: Write error: ssl=0x5e4f4640: I/O error during system call, Broken pipe at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_write(Native Method) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLOutputStream.write(OpenSSLSocketImpl.java:704) at org.apache.http.impl.io.AbstractSessionOutputBuffer.write(AbstractSessionOutputBuffer.java:109) at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113)

1分钟后发生了以下一个。

Stack trace javax.net.ssl.SSLException: Connection closed by peer at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:378) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.(OpenSSLSocketImpl.java:634) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:605)

以下一个发生在77秒后

Stack trace javax.net.ssl.SSLException: SSL handshake aborted: ssl=0x5e2baf00: I/O error during system call, Connection reset by peer at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:378) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.(OpenSSLSocketImpl.java:634) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:605) at org.apache.http.impl.io.SocketInputBuffer.(套接字输入缓冲器.java:70)

15 秒后发生以下 1 次(连接超时设置为 15 秒)

时间 : 15081 Stack trace org.apache.http.conn.ConnectTimeoutException: Connect to /103.xx.xx.xx:443 time out out at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:144) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:365)

以下是我用于发布reqeust的源代码片段

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手机

对此的任何投入将不胜感激。多谢!

更新:

  1. 我注意到我们没有关闭连接管理器。所以在代码块中添加了下面的代码,我使用http客户端。
  if (client != null) {           client.getConnectionManager().shutdown();
  }
  1. 更新了nginx配置以接受最大为5M的数据,因为其默认值为1Mb,并且某些客户端提交的数据超过1MB,并且服务器正在切断连接并出现413错误。
client_max_body_size 5M;
  1. 还增加了nginx代理读取超时,以便它等待更长的时间从客户端获取数据。
proxy_read_timeout 300;

通过上述更改,错误有所减少。在过去的一周里,我看到以下两种类型的错误:

  1. org.apache.http.conn.ConnectTimeoutException: Connect to /103.xx.xx.xxx:443 timed out- 这发生在15秒内,这是我的连接超时。我假设发生这种情况是因为客户端由于网络速度慢而无法访问服务器,或者正如@JaySoyer指出的那样,可能是由于网络切换。

  2. java.net.SocketTimeoutException: SSL handshake timed out at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method).这发生在套接字超时到期时。我现在使用1分钟作为小请求的套接字超时,分别使用3分钟和6分钟作为最大75 KB及以上的数据包。

但是,这些错误已大大减少,我看到100个请求中有1个失败,而早期版本的代码是10个请求中的1个。


答案 1

我最近不得不对我公司的应用程序进行详尽的分析,因为我们看到了一堆类似的错误,不知道为什么。我们最终分发了自定义应用程序,这些应用程序将其连接时间,错误,信号质量等记录到文件中。这样做了几个星期。收集数千个数据点。请记住,在应用打开时,我们会保持持久连接。

事实证明,我们的大多数错误都来自交换网络。对于普通用户来说,这实际上很常见。因此,假设用户正在使用EDGE蜂窝网络,然后在WIFI范围内行走,反之亦然。当这种情况发生时,Android会切断蜂窝连接,并与WIFI建立全新的连接。从应用程序的角度来看,这类似于打开飞行模式,然后再次将其重新拂出。这甚至在小区网络内切换时也会发生。例如,LTE到HSPA+。每次发生这种情况时,Android都会触发网络连接更改广播。

在您列出的错误中,此行为导致了以下类似的错误:

  • javax.net.ssl.SSLException: Write error: ssl=0x5e4f4640
  • javax.net.ssl.SSLException: SSL handshake abored:

有时网络切换速度很快,有时速度很慢。事实证明,我们没有通过快速切换及时清理资源。因此,我们试图使用陈旧/旧的TCP连接重新连接到我们的服务器,这些连接会引发更多奇怪的错误。

所以我想带走的是,如果你长时间保持连接,希望看到手机不断在网络之间切换,特别是当信号很弱的时候。当该网络切换发生时,您将看到 SSLExeptions,这是完全正常的。只需确保清理资源并重新正确连接即可。


答案 2

由于您正在处理看似较差的网络连接,因此请考虑使用容错能力更强的 HTTP 客户端。我喜欢的是OkHTTP。从他们的描述:

OkHttp在网络麻烦时会坚持下去:它会从常见的连接问题中静默地恢复。如果您的服务有多个 IP 地址,则 OkHttp 将在第一次连接失败时尝试备用地址。这对于 IPv4+IPv6 和冗余数据中心托管的服务是必需的。OkHttp 使用现代 TLS 功能(SNI、ALPN)启动新连接,如果握手失败,则回退到 SSLv3。

该实现将主要是直接替换。