Android 网络调用有时会卡在 IOException :java.net.SocketTimeoutException: timeout

Posted

技术标签:

【中文标题】Android 网络调用有时会卡在 IOException :java.net.SocketTimeoutException: timeout【英文标题】:Android network calls sometimes get stuck at IOException : java.net.SocketTimeoutException: timeout 【发布时间】:2021-05-18 08:44:17 【问题描述】:

我有一个应用程序可以上传一些文件(png、mp4)。 完整的堆栈是它背后的 nginx + Node 服务器。 有时,当互联网出现问题时,似乎有些用户在生产中遇到了奇怪的行为,然后 android 应用无法发送进一步的上传请求,因为他们都得到: IOException : java.net.SocketTimeoutException: timeout.

然而,最奇怪的是,我们看到服务器确实发送了响应(409 冲突,意味着正在上传的文件已经存在并已完成上传),但 Android 应用根本没有收到这些响应,只是继续尝试上传这些文件,但连接不断失败,因为 SocketTimeoutException 一次又一次,而不是收到 409 响应。

然后,一段时间后,它恢复正常,突然可以收到 409 响应并继续,好像一切都很好。

我尝试使用最基本的 HttpUrlConnection,以及 Square 的 OkHttp 库,但结果相同。

当我使用 Okhttp + HTTP/2 + 代理对其进行调试以监视请求时,在帧日志记录中我看到(启用了代理):

02-16 13:30:16.047  2719  6597 D okhttp.Http2: << 0x00000003     4 RST_STREAM    
02-16 13:30:16.049  2719  3758 D okhttp.TaskRunner: Q10117 canceled              : OkHttp ConnectionPool
02-16 13:30:16.051  2719  6597 D okhttp.Http2: >> 0x00000000     8 GOAWAY        
02-16 13:30:16.051  2719  6597 D okhttp.TaskRunner: Q10121 finished run in  60 s : <our server>

此时我的代码已经收到来自我的服务器实现的错误。然后我看到了:

02-16 13:30:20.129  2719  3758 D okhttp.Http2: >> CONNECTION 505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
02-16 13:30:20.131  2719  3758 D okhttp.Http2: >> 0x00000000     6 SETTINGS      
02-16 13:30:20.132  2719  3758 D okhttp.Http2: >> 0x00000000     4 WINDOW_UPDATE 
02-16 13:30:20.133  2719  3758 D okhttp.TaskRunner: Q10157 scheduled after   0 µs: OkHttp <our server>
02-16 13:30:20.135  2719  3758 D okhttp.TaskRunner: Q10153 scheduled after   0 µs: OkHttp ConnectionPool
02-16 13:30:20.135  2719  7381 D okhttp.TaskRunner: Q10157 starting              : OkHttp <our server>
02-16 13:30:20.136  2719  7365 D okhttp.TaskRunner: Q10153 starting              : OkHttp ConnectionPool
02-16 13:30:20.137  2719  7365 D okhttp.TaskRunner: Q10153 run again after 300 s : OkHttp ConnectionPool
02-16 13:30:20.137  2719  7365 D okhttp.TaskRunner: Q10153 finished run in   1 ms: OkHttp ConnectionPool
02-16 13:30:20.138  2719  3758 D okhttp.Http2: >> 0x00000003  1063 HEADERS       END_HEADERS
02-16 13:30:20.145  2719  7381 D okhttp.Http2: << 0x00000000    18 SETTINGS      
02-16 13:30:20.146  2719  7381 D okhttp.TaskRunner: Q10154 scheduled after   0 µs: OkHttp <our server> applyAndAckSettings
02-16 13:30:20.147  2719  7381 D okhttp.Http2: << 0x00000000     4 WINDOW_UPDATE 
02-16 13:30:20.147  2719  7365 D okhttp.TaskRunner: Q10154 starting              : OkHttp <our server> applyAndAckSettings
02-16 13:30:20.149  2719  7365 D okhttp.TaskRunner: Q10156 scheduled after   0 µs: OkHttp <our server> onSettings
02-16 13:30:20.151  2719  6597 D okhttp.TaskRunner: Q10156 starting              : OkHttp <our server> onSettings
02-16 13:30:20.151  2719  6597 D okhttp.TaskRunner: Q10156 finished run in   1 ms: OkHttp <our server> onSettings
02-16 13:30:20.152  2719  7365 D okhttp.Http2: >> 0x00000000     0 SETTINGS      ACK
02-16 13:30:20.153  2719  3758 D okhttp.Http2: >> 0x00000003 65535 DATA          
02-16 13:30:20.153  2719  7365 D okhttp.TaskRunner: Q10154 finished run in   6 ms: OkHttp <our server> applyAndAckSettings
02-16 13:30:20.156  2719  3758 D okhttp.Http2: >> 0x00000003     1 DATA          
02-16 13:30:20.231  2719  7381 D okhttp.Http2: << 0x00000000     0 SETTINGS      ACK

按照@Yuri Schimke 的建议,我添加了 PrintingEventListener,这就是我得到的:

2021-02-17 09:18:50.056 28203-28286/<package name> I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
2021-02-17 09:18:53.378 28203-28286/<package name>  I/System.out: 0.000 callStart
2021-02-17 09:18:53.460 28203-28286/<package name>  I/System.out: 0.081 proxySelectStart
2021-02-17 09:18:53.462 28203-28286/<package name>  I/System.out: 0.084 proxySelectEnd
2021-02-17 09:18:53.462 28203-28286/<package name>  I/System.out: 0.084 dnsStart
2021-02-17 09:18:53.463 28203-28286/<package name>  I/System.out: 0.085 dnsEnd
2021-02-17 09:18:53.475 28203-28286/<package name>  I/System.out: 0.096 connectStart
2021-02-17 09:18:53.650 28203-28286/<package name>  I/System.out: 0.271 secureConnectStart
2021-02-17 09:18:53.796 28203-28286/<package name>  I/System.out: 0.418 secureConnectEnd
2021-02-17 09:18:53.834 28203-28286/<package name>  I/System.out: 0.456 connectEnd
2021-02-17 09:18:53.839 28203-28286/<package name>  I/System.out: 0.461 connectionAcquired
2021-02-17 09:18:53.870 28203-28286/<package name>  I/System.out: 0.492 requestHeadersStart
2021-02-17 09:18:53.891 28203-28286/<package name>  I/System.out: 0.513 requestHeadersEnd
2021-02-17 09:18:53.891 28203-28286/<package name>  I/System.out: 0.513 requestBodyStart
2021-02-17 09:19:03.382 28203-28286/<package name>  I/System.out: 10.003 requestFailed
2021-02-17 09:19:03.391 28203-28311/<package name>  I/System.out: 10.012 canceled
2021-02-17 09:19:03.399 28203-28286/<package name>  I/System.out: 10.021 connectionReleased
2021-02-17 09:19:03.400 28203-28286/<package name>  I/System.out: 10.022 callFailed

【问题讨论】:

可能是第一次上传被不良网络中断,然后服务器在上传尝试时仅返回 409,因为文件仍然被中断的上传保留。您应该检查上传中断时服务器的期望(可能是它实现了某种恢复)?否则这可能是一个服务器错误,没有考虑到 uoloads 的网络中断。 感谢@Robert 的回复。我们尝试检查服务器,甚至添加了包含文件字节数组大小的 Content-Length 标头,以便服务器端(nginx/后端)更好地知道文件是否确实已完全上传。此外,这个错误不会发生在 ios 上,这基本上排除了我们在服务器端出现错误的可能性(虽然我现在不确定了..)除此之外,即使请求被 bad 中断网络,以下所有请求都会产生 IOException 是什么? Android端的网络层似乎已经损坏了一段时间 SocketTimeoutException 通常表示与服务器的连接已建立,但在指定的 socketTimeout 内没有收到数据。确保该值不会太短(例如 5000 毫秒或更多)。如果仍然发生,可能是Android的网络系统在处理非常糟糕的网络连接时出现问题,最终数据连接被限制为每秒0字节。 我明白你的意思.. 不幸的是,设置更高的超时时间在这方面似乎没有什么区别,当涉及到发布请求时。 (顺便说一句:当 POST 请求开始获取异常时,GET 请求确实有效)。那么最终,万一发生后一种情况(Android的网络系统在处理不良网络时出现问题),是否真的有任何可用的解决方案至少可以缓解这个问题? 超时在网络级别,因此如果它在 GET 和 POST 之间产生差异,那么服务器上会发生一些奇怪的事情。顺便说一句:我们是在谈论 Wifi 或移动网络连接变坏吗? 【参考方案1】:

暂时忽略您的具体问题。第一个挑战是提出一个 *** 响应者可以轻松帮助您取得进展的问题。这里的每个人都想帮助您解决问题。

阅读https://***.com/help/how-to-ask,也许还有http://sscce.org

对于 OkHttp,使用它提供的调试工具来获取解释正在发生的事情的额外数据,即使它不是立即清楚。您是否返回多条路线,而第一条路线很慢,然后被标记为坏路线?

事件记录 - https://square.github.io/okhttp/events/

HTTP/2 帧记录(如果可以启用)-https://square.github.io/okhttp/debug_logging/#http2-frame-logging

运行调试器并捕获正在发生的事情的堆栈跟踪? IO 线程是否还在读取和阻塞网络?

【讨论】:

感谢尤里的回复。关于帧记录,我不确定我是否做得正确,但我已经用我在使用 okhttp + http2 调试时看到的内容编辑了帖子。但是 - 这是使用代理。如果不使用代理,okhttp + http2 似乎可以工作。所以,我编辑帖子时使用的是代理。 另外,只是想提一下,至少在设计上,我们不执行并行请求。我们等待上传请求完成(或失败),然后如果需要,我们会在一段时间后打开另一个请求。使用 HttpUrlConnection 我们在最后的连接上调用disconnect。使用 Okhttp,我们基本上就像 client.newCall(requestBuilder).execute().use response -&gt; val res = response.body?.string() .. 一样使用它,并且不调用任何关闭方法 并且重新考虑 IO 线程读取/阻塞我不知道如何检查。但是,我确实注意到使用代理监控有时在请求失败后,当我尝试发送新请求时,我看到前一个失败的请求仍然是“发送正文......”(至少这是代理显示的内容) 我想知道客户端请求是否仍在发送,并在客户端或此处的代理中导致某些线路阻塞。您可以尝试包装请求正文,以便在您知道请求失败或类似情况后强制终止。我认为没有快速解决方法。尝试启用github.com/square/okhttp/blob/master/samples/guide/src/main/… 并查看是否出现其他任何内容。对不起! 我已经用 PrintEventListener 日志更新了帖子。顺便说一句,我注意到我多次尝试上传的图像,最终确实得到了 200 响应,然后视频文件又卡住了。所以我假设现在网络无法正常工作,它只是非常非常慢。大约 50KB/s

以上是关于Android 网络调用有时会卡在 IOException :java.net.SocketTimeoutException: timeout的主要内容,如果未能解决你的问题,请参考以下文章

java 有一行语句是调用外部接口的,但该接口不稳定,有时候请求时间会很长,程序就会卡在这一行很长时间

为什么LCC充电会卡在一个固定的电压值呢?

qt打开.pro文件问啥会卡在configure project上

为什么下载小电影会卡在99% ?你懂的

为啥 JS WebSocket 在 sparkjava 中会卡在 CONNECTING 上?

当我尝试运行我的 Flutter 程序时,它会卡在“Running Gradle task 'assembleDebug”