:wxfrwk:`网络调优 ` ======================================================= 小程序和小游戏网络相关 API 使用方式相同, 所以我们用网络接口来统称 网络接口的构成 -------------------------- 网络接口主要包括四个类型 - request - download - upload - websocket 不同平台的实现 -------------------------- Android ~~~~~~~~~~~~~~~~ - **request** 接口从客户端 7.0.10 版本开始使用 Chromium 内网络相关部分封装的底层组件 (cronet), 之前版本使用 HttpURLConnection 系统组件 (系统组件依赖系统实现会有平台兼容性问题, 我们建议用新版本微信来进行调试) - **download** 接口从客户端 7.0.12 版本开始使用 cronet 组件, 之前版本使用 HttpURLConnection 组件 - **upload** 接口目前仍在使用 HttpURLConnection 组件 - **websocket** 接口从客户端 7.0.4 版本开始使用微信底层组件 wcwss, 并在 7.0.10 版本优化了调用性能 iOS ~~~~~~~~~~~ - **request/download** 接口从客户端 8.0.3 版本开始使用 cronet 组件, 之前版本使用 NSURLSession 系统组件 - **upload** 接口目前仍在使用 NSURLSession 组件 - **websocket** 接口从客户端 7.0.20 版本开始使用微信底层组件 wcwss, 之前版本使用 SRWebSocket 组件 易误解的概念 ~~~~~~~~~~~~ success/fail/complete 回调 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 对于 request/download/upload 接口, 回调代表网络请求的最终结果 - 对于 websocket 接口, 回调仅代表接口调用结果, 应当监听其具体事件来获取真实的网络连接/请求状态 wx.sendSocketMessage/SocketTask.send ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 早期单个小程序只允许同时存在一条 WebSocket 连接, 所以老版本基础库 WebSocket 相关接口都直接设计在了 wx 上: - wx.connectSocket - wx.onSocketOpen - wx.sendSocketMessage - wx.onSocketMessage - wx.closeSocket - wx.onSocketClose - wx.onSocketError 现在单个小程序允许同时存在多个 WebSocket 连接, 原有接口设计并不能满足需求, 于是基础库在 1.7.0 版本之后增加了 SocketTask 的概念, 通过不同的实例来管理多条连接: - wx.connectSocket - SocketTask.onOpen - SocketTask.send - SocketTask.onMessage - SocketTask.close - SocketTask.onClose - SocketTask.onError 原有的 wx.connectSocket 接口在新版本设计中承载了创建实例 new SocketTask 的用途, 所以除了 wx.connectSocket 以外, 不应该使用其它任何挂在 wx 上的 WebSocket 接口; 在 wx.connectSocket 调用后, 请立即同步监听 SocketTask.onOpen, 否则可能会漏掉 onOpen 通知 性能分析 -------------------------- Android ~~~~~~~~~~~~ request/download 接口从客户端 7.0.12 版本开始, 回调中提供了 profile 信息, 给出了网络连接过程中关键时间点的耗时信息, 具体含义如下 +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 名称 | 含义 | +==================================+===================================================================================================================================================================================================================+ | redirectStart | 第一个 HTTP 重定向发生时的时间. 有跳转且是同域名内的重定向才算, 否则值为 0 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | redirectEnd | 最后一个 HTTP 重定向完成时的时间. 有跳转且是同域名内部的重定向才算, 否则值为 0 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | fetchStart | 组件准备好使用 HTTP 请求抓取资源的时间, 这发生在检查本地缓存之前 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | domainLookupStart | DNS 域名查询开始的时间, 如果使用了本地缓存 (即无 DNS 查询) 或持久连接, 则与 fetchStart 值相等 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | domainLookupEnd | DNS 域名查询完成的时间, 如果使用了本地缓存 (即无 DNS 查询) 或持久连接, 则与 fetchStart 值相等 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | connectStart | TCP 开始建立连接的时间, 如果是持久连接, 则与 fetchStart 值相等. 注意如果在传输层发生了错误且重新建立连接, 则这里显示的是新建立的连接开始的时间 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | connectEnd | TCP 完成建立连接的时间 (完成握手), 如果是持久连接, 则与 fetchStart 值相等. 注意如果在传输层发生了错误且重新建立连接, 则这里显示的是新建立的连接完成的时间. 注意这里握手结束, 包括安全连接建立完成、SOCKS 授权通过 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | SSLconnectionStart | SSL 建立连接的时间, 如果不是安全连接, 则值为 0 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | SSLconnectionEnd | SSL 建立完成的时间, 如果不是安全连接, 则值为 0 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | requestStart | HTTP 请求读取真实文档开始的时间 (完成建立连接), 包括从本地读取缓存. 连接错误重连时, 这里显示的也是新建立连接的时间 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | requestEnd | HTTP 请求读取真实文档结束的时间 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | responseStart | HTTP 开始接收响应的时间 (获取到第一个字节), 包括从本地读取缓存 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | responseEnd | HTTP 响应全部接收完成的时间 (获取到最后一个字节), 包括从本地读取缓存 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | rtt | 当次请求连接过程中实时 rtt | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | estimate_nettype | 评估的网络状态 unknown, offline, slow 2g, 2g, 3g, 4g, last/0, 1, 2, 3, 4, 5, 6 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | httpRttEstimate | 协议层根据多个请求评估当前网络的 rtt (仅供参考) | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | transportRttEstimate | 传输层根据多个请求评估的当前网络的 rtt (仅供参考) | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | downstreamThroughputKbpsEstimate | 评估当前网络下载的kbps, 根据最近的几次请求的rtt, 回包情况, 结合当前的网络情况, 进行的一个网络评估结果 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | throughputKbps | 当前网络的实际下载kbps, 根据本次请求实际计算的一个下载值, 从开始请求到 请求结束收到的 字节数 * 8/请求耗时 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | peerIP | 当前请求的目标IP | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | port | 当前请求的目标端口 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | protocol | 当前请求使用的协议 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | socketReused | 是否复用连接 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | sendBytesCount | 发送的字节数 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | receivedBytedCount | 收到字节数 | +----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 整个请求链路为 DNS -> Connect -> SSL -> request -> response; 表中 rtt 是连接过程中实时的 rtt, 每个阶段都会更新, 而 httpRttEstimate 和 transportRttEstimate 是结合前序请求计算的综合值 - websocket 接口从客户端 7.0.12 版本开始, 在 onOpen 回调中提供了 profile 信息, 给出了网络连接过程中关键时间点的耗时信息, 具体含义如下 +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 名称 | 含义 | +===================+===============================================================================================================================================================================================================+ | fetchStart | 组件准备好使用 SOCKET 建立请求的时间, 这发生在检查本地缓存之前 | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | domainLookupStart | DNS 域名查询开始的时间, 如果使用了本地缓存 (即无 DNS 查询) 或持久连接, 则与 fetchStart 值相等 | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | domainLookupEnd | DNS 域名查询完成的时间, 如果使用了本地缓存 (即无 DNS 查询) 或持久连接, 则与 fetchStart 值相等 | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | connectStart | 开始建立连接的时间, 如果是持久连接, 则与 fetchStart 值相等. 注意如果在传输层发生了错误且重新建立连接, 则这里显示的是新建立的连接开始的时间 | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | connectEnd | 完成建立连接的时间 (完成握手), 如果是持久连接, 则与 fetchStart 值相等. 注意如果在传输层发生了错误且重新建立连接, 则这里显示的是新建立的连接完成的时间. 注意这里握手结束, 包括安全连接建立完成、SOCKS 授权通过 | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | rtt | 单次连接的耗时, 包括 connect, tls | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | handshakeCost | 握手耗时 | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | cost | 上层请求到返回的耗时 | +-------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 整个请求链路为 DNS -> Connect; 表中 connectEnd - connectStart 代表纯 tcp 连接耗时, domainEnd - domainStart 代表域名解析耗时; 上述两步耗时加上 handshakeCost 代表单次连接请求的耗时 iOS ~~~~~~~~~~~~ - request/download 接口从客户端 8.0.3 版本开始提供 profile 能力 - websocket 接口从客户端 7.0.20 版本开始提供 profile 能力 提示 ~~~~~~~~~~~~ - 当遇到网络问题时, 除了判断网络状态是否连通外, 还可以通过 rtt 来分析用户当前网络状况, 用以动态调整超时参数 - 网络请求提供 enableProfile 参数, 默认值为 true, 可以通过传入 false 关闭 优化建议 -------------------------- 前后台切换 ~~~~~~~~~~~~~~~~~~ 小程序切后台 5s 后, 会中断网络请求, 开发者会收到 interrupted 的回调, 此时需要做好兼容逻辑 网络状态变化 ~~~~~~~~~~~~~ 当用户网络状态变化时会通过事件 wx.onNetworkStatusChange 进行通知, 不少网络问题是断网引起的, 可以通过此事件给用户更好的提示 弱网状态变化 基础库从 2.19.0 版本开始, 提供 wx.onNetworkWeakChange 弱网变化通知, 很多超时类的问题都是用户处于弱网引起的, 可以通过此事件给用户更好的提示 在最近的八次网络请求中, 出现下列三个现象之一则判定弱网 - 出现三次以上连接超时 - 出现三次 rtt 超过 400 - 出现三次以上的丢包 弱网事件通知规则是: 弱网状态变化时立即通知, 状态不变时 30s 内最多通知一次 request/download 新协议 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 从 Android 7.0.12 / iOS 8.0.3 开始, 提供下面三个新参数 +-------------+------------------------------------+ | 名称 | 含义 | +=============+====================================+ | enableHttp2 | 如果后台支持, 尝试使用 Http2 协议 | +-------------+------------------------------------+ | enableQuic | 如果后台支持, 尝试使用 Quic 协议 | +-------------+------------------------------------+ | enableCache | 缓存内容, 相同请求优先读取本地内容 | +-------------+------------------------------------+ h2 连接速度更快, 建议支持, 这里需要注意 h2 的 header 是需要为全小写, 打开 enableHttp2 开关前需要注意代码逻辑 perMessageDeflate ~~~~~~~~~~~~~~~~~~~~~ 压缩参数目前已在 Android 和 iOS 上全量支持 问题排查 -------------------------- 不同平台的错误返回规则 Android ~~~~~~~~~~~~~~~ cronet 的错误返回可以参考: https://chromium.googlesource.com/chromium/src/+/master/net/base/net_error_list.h WebSocket 接口常见错误 +----------------------------+----------------------------+ | 名称 | 含义 | +============================+============================+ | Underlying Transport Error | 异常, 大概率无网络引起 | +----------------------------+----------------------------+ | Timer Expired | 超时, 弱网或无网 | +----------------------------+----------------------------+ | The total timed out | 超时, 弱网或无网 | +----------------------------+----------------------------+ | TLS handshake failed | tls 协商失败 | +----------------------------+----------------------------+ | TLS handshake timed | tls 协商超时, 可以考虑重试 | +----------------------------+----------------------------+ | Invalid HttpCode | 服务器配置有误 | +----------------------------+----------------------------+ iOS ~~~~~ cronet 的错误返回参考同 Android upload 一般返回汉语信息加上 kcferrordomaincfnetwork 可以直接在苹果开发者官网上搜索到具体的对应错误信息, 协助分析解决 ipv6 慢的问题 ~~~~~~~~~~~~~~~~~ Android HttpURLConnection 是按照 RFC 3484 顺序尝试每个 ip 地址, 这里应该是 v6 优先, 但是系统尝试 v6 连接时超时就会按顺序再去尝试 v4, 虽然最后也有可能在设置的 60s 超时时间内完成, 但是整体耗时还是变长了, 现象就是 request 接口的请求时间很长. 在客户端 7.0.10 版本切换 cronet 后已经解决此问题 证书问题 ~~~~~~~~~~~~ 证书的注意事项已有文档说明: https://developers.weixin.qq.com/minigame/dev/guide/base-ability/network.html 1. 证书过期或无效 可以通过 https://myssl.com/ssl.html 或其他在线工具验证, 因为 Android 手机的兼容性问题, 验证结果并不保证对所有 Android 机器都有效 2. 证书链不完整 Android 的根证书不全, 如果服务器是使用中间证书, 而 Android 手机上又找不到相应的根证书, 就会出现相关的 SSL 错误, 此时需要服务器配置完整证书链 3. wss 协议走 80 端口不成功 80 端口对应 http 默认不做证书校验, wss 应当选用 443 端口 not in domain url ~~~~~~~~~~~~~~~~~~~~~~~~ 请求 url 不在域名列表中, 遇到这个问题有几种可能 1. 请求 url 不在 mp 配置的域名列表里 2. 重定向后的 url 不在域名列表里 3. websocket 请求的端口没有配置 4. 配置的域名未生效 (极低概率) network is down ~~~~~~~~~~~~~~~~~~ iOS 14 系统新增了本地网络开关, 如果关闭则局域网不通, 系统接口报错 network is down, 目前系统未提供检测开关方法, 开发者需要根据错误信息提示用户打开权限