微信小程序和小游戏网络相关 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
- 证书过期或无效
可以通过 https://myssl.com/ssl.html 或其他在线工具验证,因为 Android 手机的兼容性问题,验证结果并不保证对所有 Android 机器都有效。
- 证书链不完整
Android 的根证书不全,如果服务器是使用中间证书,而 Android 手机上又找不到相应的根证书,就会出现相关的 SSL 错误,此时需要服务器配置完整证书链。
- wss 协议走 80 端口不成功
80 端口对应 http 默认不做证书校验,wss 应当选用 443 端口。
not in domain url
请求 url 不在域名列表中,遇到这个问题有几种可能:
- 请求 url 不在 mp 配置的域名列表里
- 重定向后的 url 不在域名列表里
- websocket 请求的端口没有配置
- 配置的域名未生效(极低概率)
network is down
iOS 14 系统新增了本地网络开关,如果关闭则局域网不通,系统接口报错 network is down,目前系统未提供检测开关方法,开发者需要根据错误信息提示用户打开权限。