Android 网络请求超时?可能与连接池和脏连接有关

在IOT开发连接Soft AP后发起网络请求时,发现一个奇怪的现象,经常有网络请求超时,一开始以为是硬件(提供Web服务)的原因,因为客户端代码看到网络请求已经出去了,但是硬件端反馈就是没收到。那问题出在哪里呢?

一、连接池与脏连接

先介绍一下基础概念。

1、为什么需要连接池

因为连接复用带来的性能收益非常大,具体对比如下:

项目 无连接池 有连接池
每次请求 建立 TCP + TLS 握手(几十~上百 ms) 直接发送请求(几 ms)
服务器负载 每次都新建 Socket、TLS 握手 复用已有连接,减少 CPU
网络流量 多次三次握手、证书传输 复用连接减少控制包
用户体验 慢、延迟高 快、流畅

我们在Android开发中一般使用的是Okhttp,Okhttp的源码中连接池的大小是5,连接的保活时间是5分钟,源码如下:

kotlin 复制代码
class ConnectionPool internal constructor(
  internal val delegate: RealConnectionPool
) {
  constructor(
    maxIdleConnections: Int,
    keepAliveDuration: Long,
    timeUnit: TimeUnit
  ) : this(RealConnectionPool(
      taskRunner = TaskRunner.INSTANCE,
      maxIdleConnections = maxIdleConnections,
      keepAliveDuration = keepAliveDuration,
      timeUnit = timeUnit
  ))
   
   //连接池默认构造
  constructor() : this(5, 5, TimeUnit.MINUTES)
...略...
}

2、什么是脏连接(stale connection)?

当客户端复用一个已经空闲的 TCP 连接时,如果 服务端已经关闭了这个连接(可能是超时、服务器主动断开、网络抖动),客户端尝试用这个连接发送请求就会失败:

kotlin 复制代码
java.io.EOFException: unexpected end of stream
java.net.SocketException: Connection reset by peer

这个连接就是所谓的 "脏连接"。因为连接池本身不知道服务端已经关闭了连接,所以当它尝试重用就会出现上面的问题。

二、遇到的问题与解决办法

当时是IOT开发,硬件侧是一个低功耗摄像头,硬件有开启一个Web服务供客户端请求,然后我发现总是有接口请求超时,我就很纳闷,硬件端抓日志说我的请求没收到???

一开始是怀疑硬件端网络请求队列的问题,因为有些页面有高并发,有些请求会不会在队列中等待时间过长就超时了? 后面放宽了超时时间,发现还是有超时现象,这就很奇怪了!!!

后面注意力才转移到脏连接上来,一开始使用了Okhtto的重试机制:

kotlin 复制代码
.retryOnConnectionFailure(true) // 保留自动重试

发现并不能解决问题,正如这个函数描述的那样:

kotlin 复制代码
/**
 * ...略...
 * Stale pooled connections. The [ConnectionPool] reuses sockets
 *   to decrease request latency, but these connections will occasionally time out.
 * ...略...
 */
fun retryOnConnectionFailure(retryOnConnectionFailure: Boolean) = apply {
  this.retryOnConnectionFailure = retryOnConnectionFailure
}

复用连接池偶尔会请求超时...

后面跟硬件工程师讨论,他说硬件端往往资源比较紧张,硬件用的这个三方网络库比较轻量,可能没有连接池这些东西或者Socket keep-alive的时间很短会及时释放。硬件工程师比较忙,也没办法继续协助我排查问题,后面查资料发现

Nginx/Tomcat 默认 Keep-Alive 超时时间可能只有 1~5 秒

这样的话使用Okhttp的连接池就没什么意义了,直接禁用了连接池解决了上面的问题:

kotlin 复制代码
.connectionPool(ConnectionPool(0, 1, TimeUnit.SECONDS))   //不复用连接

三、总结

很多看着奇奇怪怪的问题,其实很有可能是底层没有完全弄懂,要保持一定的探索欲,能解决现实问题也能让我们更加明白深层次的原理。

相关推荐
执明wa14 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio
404_coder14 小时前
源码视角下的 Android 开机流程:从 Zygote、SystemServer 到 Launcher
android
二流小码农15 小时前
鸿蒙开发:以登录案例了解代码架构MVVM
android·ios·harmonyos
用户693717500138415 小时前
从代码生产者到 AI 协作者:软件工程师的角色重构
android·前端·后端
GitLqr16 小时前
别在 Flutter 的 main() 里乱锁屏幕方向,小心 iPad 分屏功能被你搞没了
android·flutter·ios
sg_knight16 小时前
MySQL 存储过程详解:从入门到实战
android·数据库·mysql·database·dba·关系型数据库·db
爱笑鱼16 小时前
Binder(四):ioctl(BINDER_WRITE_READ) 之后,事务怎样到达目标进程?
android
AFinalStone16 小时前
Android 7系统休眠唤醒(二)开机全链路—BootROM到Launcher
android·电源管理·休眠唤醒
Mr YiRan16 小时前
Android NDK开发之统计到未被回收的图片
android