优化篇|网络预连接

一个示例

前后分别去请求同一个域名下的接口,通过 Charles 抓包,可以看到 Timing 下面的时间:

  • 第二次请求时,DNS、Connect 和 TLS Handshake 部分都是 -,说明没有这部分的耗时,对比第一次请求的这三个部分,节省了 1 + 35 + 97 = 133ms
  • 当然第一次请求的 Request 和 Response 的 Size 比第二次要大一丢丢,且 Speed 低一些,忽略这些差异,在其他条件都一致的情况下,第二次请求比第一次请求能快 133ms。
第一次 第二次

优化篇|网络预连接_第1张图片

优化篇|网络预连接_第2张图片

这就是 http(s) 的连接复用。

连接复用

在此之前先简单复习一下发起网络 Request ->收到 Response 的粗略过程:

  1. 客户端发起网络请求
  2. 通过 DNS 服务解析域名,获取 IP 地址(一般是 UDP 协议)
  3. 建立 TCP 连接(三次握手)
  4. 建立 TLS 连接(Https)
  5. 发送网络请求 Request
  6. 服务器接收 Request 到后,执行逻辑并返回 Response
  7. 关闭 TCP 连接(四次挥手)

通过连接复用,上面的 2、3、4 步都不需要重新走了。使用 RTT 来定义这个时长,RTT(Round-Trip Time, 往返时间) 是网络请求从起点到目的地然后再回到起点所花费的时长。那么节省的时间是:

  • DNS 一般使用 UDP 协议,最近重新复习了下 DNS 的内容,如果 DNS 响应报文的长度大于 512 字节,则会使用 TCP 协议。事实上,很多 DNS 服务器进行配置时,也仅支持 UDP。因此这一步可以看成节省了 1 个 RTT。
  • 建立 TCP 连接,三次握手,需要 2 个 RTT。
  • 建立 TLS 连接,根据 TLS 版本,有不同的 RTT。

HTTP1.1 版本开始默认就是持久连接,可以复用,通过在报文头部加上 Connection:Close 来关闭连接。另外空闲的持久连接也可以随时被关闭,即使不发送 Connection:Close,也不意味着服务器连接永远保持打开。

预连接

常用的网络框架如 OkHttp 等,都支持 HTTP1.1 和 HTTP2 的功能,那也支持连接复用。

我们可以利用这个机制来做一个预连接,比如说在 APP 闪屏等待时,预先建立起关键页面域名的连接,这样在用户进入相应页面后可以更快的获取到网络请求结果,提升用户体验。

可以简单的对域名链接提前发起一个 HEAD 请求(没有Body),这样就能提前建立好连接,下次同域名的请求可以直接复用。

private val client by lazy { OkHttpClient() }

btn.setOnClickListener {
    // 正式请求
    launch(Dispatchers.IO) {
        request()
    }
}

// 预连接
launch(Dispatchers.IO) {
    preRequest()
}

fun preRequest() {
    val request = Request.Builder()
        .head()
        .url("xxx")
        .build()
    client.newCall(request).execute()
}

fun request() {
    val request = Request.Builder()
        .get()
        .url("xxx/yyy")
        .build()
    client.newCall(request).execute()
}

可以抓包看到首次进入时发送的 head 请求和实际上点击发送的 get 请求:

预连接 正式请求

优化篇|网络预连接_第3张图片

优化篇|网络预连接_第4张图片

可以看到正式请求时,确实少了上述三个步骤的耗时。还可以分别看下 Connection 和 TLS 的信息:

预连接 正式请求

优化篇|网络预连接_第5张图片

优化篇|网络预连接_第6张图片

能看出来正式请求时,这俩是复用的(关注 TLS 的 Session Resumed 和 Connection 的 Server Connection 部分)。

另外 OkHttp 中有个 ConnectionPool 连接池,在使用 Connect 连接时,会优先复用已有的连接,无可用时才创建新连接。连接池里容纳的连接数是限定的(貌似默认是 5 个),如果业务比较复杂,请求比较多的话,可能会导致连接池占满,这样就会释放之前做好的预连接。因此一个比较简单的方式就是适当调大连接池的容量和超时时间。

总结

通过 http(s) 的连接复用机制,我们可以考虑使用预连接来优化 APP 中某些场景的网络请求速度,这需要我们根据实际业务场景以及服务器压力来判断是否进行预连接。

另外我们可以适当调大连接池的容量和超时时间,由于连接是双向的,即使客户端把 Connection 一直保留,服务端也会根据实际连接数量和时长来自动关闭连接的,所以调大连接池一般不会增大服务器压力。

预连接的效果实际和服务器配置有关,如果服务器把连接超时设置得很小,那每次请求可能都需要重新建立连接,这样客户端的预连接会失效,且服务器也需要不断创建和销毁 TCP 连接而浪费更多资源;如果服务器把连接超时设置得很大,那之前的连接长时间都不会释放,导致服务器服务的并发数受到影响,影响新的请求。因此调优需要多端协调,综合考虑

Android视图系统:Android 视图系统相关的底层原理解析,看完定有收获。

Kotlin专栏:Kotlin 学习相关的博客,包括协程, Flow 等。

Android架构学习之路:架构不是一蹴而就的,希望我们有一天的时候,能够从自己写的代码中找到架构的成就感,而不是干几票就跑路。工作太忙,更新比较慢,大家有兴趣的话可以一起学习。

Android实战系列:记录实际开发中遇到和解决的一些问题。

Android优化系列:记录Android优化相关的文章,持续更新。

作者:苍耳叔叔
链接:https://juejin.cn/post/7257410068646658085
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(https,http,webview,网络,php,开发语言)