从Android App与服务器通信时出现各种HTTP错误

时间:2014-07-31 12:44:39

标签: java android apache ssl nginx

更新时间: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手机

对此的任何意见都将受到高度赞赏。非常感谢!

更新

  1. 我注意到我们没有关闭Connection Manager。所以在我使用http客户端的代码的finally块中添加了下面的代码。
  2.   if (client != null) {           client.getConnectionManager().shutdown();
      }
    
    1. 更新了nginx配置以接受最大为5M的数据,因为它的默认值为1Mb,一些客户端提交的内容超过1MB,服务器因413错误而切断连接。
    2. client_max_body_size 5M;
      
      1. 还增加了nginx代理读取超时,以便等待从客户端获取数据的时间更长。
      2. 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分钟作为小型请求的套接字超时,对于高达75 KB及更高的数据包分别使用3分钟和6分钟。

        3. 然而,这些错误已经大大减少了,而且我发现100个请求中有1个失败,而我的代码的早期版本则是10个请求中的1个。

2 个答案:

答案 0 :(得分:12)

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

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

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

  • javax.net.ssl.SSLException:写入错误:ssl = 0x5e4f4640
  • javax.net.ssl.SSLException:SSL握手已中止:

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

所以我猜想,如果你长时间保持连接,就会看到手机不断在网络之间切换,特别是当信号很弱时。当发生网络切换时,您将看到SSLExeptions并且它完全正常。只需要确保清理资源并正确重新连接。

答案 1 :(得分:2)

由于您正在处理看起来网络连接不佳的问题,因此请考虑使用容错的HTTP客户端。我喜欢的是OkHTTP。从他们的描述:

  

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

实施将主要是直接替代。