从 Android 应用程序与服务器通信时出现各种 HTTPS 错误

Posted

技术标签:

【中文标题】从 Android 应用程序与服务器通信时出现各种 HTTPS 错误【英文标题】:Variety of HTTPs errors while communicating to server from Android App 【发布时间】: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 作为预防措施。但是仍然存在一些问题 - 主要是客户端尝试连接到服务器时的读取超时和偶尔损坏的管道。感谢您的投入。 【参考方案1】:

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

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

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

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

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

所以我想,如果您要长时间保持连接,预计手机会在网络之间不断切换,尤其是在信号较弱的情况下。当发生该网络切换时,您会看到 SSLExeptions,这是完全正常的。只需确保清理资源并正确重新连接即可。

【讨论】:

在连接过程中,任何错误都不需要清理,因为没有建立任何连接。然后我们在成功连接后进行用户身份验证过程,我们尝试...catch。在抛出任何错误时,为了安全起见,我们会尝试断开连接并进行清理。我们使用 SMACK 库将所有​​这些 SLLExceptions 和套接字错误通过管道传输到我们的一个位置。对于任何这些错误,如果可能,我们会尝试断开连接并进行清理。所以我想说你在哪里尝试......捕捉和检测这些错误是你需要处理的地方。 我也在使用 Smack 4.1,并且仅在 Galaxy-s4 (Android 4.4.2) 中遇到类似问题。请您详细说明,就 Smack 4.1 而言,“清理资源”是什么意思【参考方案2】:

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

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

该实施将主要是一种替代品。

【讨论】:

我不同意你的观点;在我的应用程序闲置 30 分钟后(并且可能发生了一些网络切换),SSL 握手异常触发,直到应用程序重新启动或直到 okHTTP 被重新实例化!见***.com/q/37885391/550471

以上是关于从 Android 应用程序与服务器通信时出现各种 HTTPS 错误的主要内容,如果未能解决你的问题,请参考以下文章

与google服务器通信时出现问题

无法登陆Google,与Google服务器通信时出现问题,怎么回事?

Android:从模拟器访问 localhost 时出现“Bad Request-Invalid Hostname”

下载了谷歌Play商店,注册成功却不能登录,与Google服务通信时出现问题。请稍后重试,我是小米

发出跨源请求时出现 SharedArrayBuffer 错误

手机登陆与google服务器通信时出现问题