[TOC]
## 网络优化三个方向
* **速度**。在网络正常或者良好的时候,怎样更好地利用带宽,进一步提升网络请求速度。
* **弱网络**。移动端网络复杂多变,在出现网络连接不稳定的时候,怎样最大程度保证网络的连通性。
* **安全**。网络安全不容忽视,怎样有效防止被第三方劫持、窃听甚至篡改。
## DNS
DNS 的解析是我们网络请求的第一项工作,默认我们使用运营商的 LocalDNS 服务。这块耗时在 3G 网络下可能是 200~300ms,4G 网络也需要 100ms。
解析慢并不是默认 LocalDNS 最大的“原罪”,它还存在一些其他问题:
* **稳定性**。UDP 协议,无状态,容易域名劫持(难复现、难定位、难解决),每天至少几百万个域名被劫持,一年至少十次大规模事件。
* **准确性**。LocalDNS 调度经常出现不准确,比如北京的用户调度到广东 IP,移动的运营商调度到电信的 IP,跨运营商调度会导致访问慢,甚至访问不了。
* **及时性**。运营商可能会修改 DNS 的 TTL,导致 DNS 修改生效延迟。不同运营商的服务实现不一致,我们也很难保证 DNS 解析的耗时。
为了解决这些问题,就有了 HTTPDNS。简单来说自己做域名解析的工作,通过 HTTP 请求后台去拿到域名对应的 IP 地址,直接解决上述所有问题。
微信有自己部署的 NEWDNS,阿里云和腾讯云也有提供自己的 HTTPDNS 服务。对于大网络平台来说,我们会有统一的 HTTPDNS 服务,并将它和运维系统打通。在传统的 DNS 基础上,还会增加精准的流量调度、网络拨测 / 灰度、网络容灾等功能。
关于 HTTPDNS 的更多知识,你可以参考百度的[《DNS 优化》](https://mp.weixin.qq.com/s/iaPtSF-twWz-AN66UJUBDg)。对客户端来说,我们可以通过预请求的方法,提前拿到一批域名的 IP,不过这里需要注意 IPv4 与 IPv6 协议栈的选择问题。
## IP的选择
### 并发 串行 复合连接
串行连接:例如4秒。我们知道移动互联网具有不稳定的特征,超时时间设置过短,会导致在弱网络的情况下,connect 总是失败,导致不可用。
并行连接:服务器需要提供的连接能力是串行连接的N倍,对服务器连接资源是极大的浪费。同时,并发连接是否会引起连接资源的竞争。
微信【复合连接】:
![](https://img.kancloud.cn/1d/d0/1dd0fdaedba4e25653ae53fd3489f769_640x296.png)
初始阶段,应用发起对 IP1 &Port1 的 connect 调用。在第4秒的时候,如果第一个 connect 还没有返回,则发起对 IP2 &Port2 的 connect 调用。以此类推,直至发起了5组 IP&Port 的 connect 调用。
对比串行连接与并行连接,复合连接有以下特点:
* 常规情况下,服务器负载与串行连接策略相同,实现了低负载的目标;
* 异常情况下,每4s发起新(IP,Port)组合的 connect 调用,使得应用可以快速的查找可用 IP&Port,实现高性能的目标;
* 在超时时间的选择上,复合方式的“并发”已经实现了高性能、低负载的目标,因此在超时时间的选择上可以相对宽松,以保障高可用为重。
综合对比,复合连接能够维持低资源消耗的情况下,能同时实现低负载、高性能、高可用的目标。
### IP选择策略
* 优先上次成功IP选择
* 定期遗忘
当一个最优服务端出现问题时,会由于灾备系统会切换到次优ip,但是由于又第一条【优先上次成功IP选择】原则的存在,导致当最优服务端恢复时,大家无法恢复到仍然选择次优ip,因此有了定时遗忘的逻辑
## 安全
### Kerberos
使用**Kerberos**作为**用户和服务的强身份验证和身份传播**的基础。**Kerberos 是一种计算机网络认证协议,它允许某实体在非安全网络环境下通信,向另一个实体以一种安全的方式证明自己的身份。**
![](https://img.kancloud.cn/5e/2a/5e2ac1173910f5820abfcd2acc24f6ac_538x548.png)
#### 主要角色
* 用户鉴权后台AS (Authentication Server)
* 票据后台TGS(Ticket-Granting Server)
* 业务后台App Server
#### 流程
* 密码校验、安全通道的建立,获取大票TGT
* 获取业务票据,TGT换取相应的业务小票ST
* 使用业务小票ST去访问业务服务器
### ECDH
![](https://img.kancloud.cn/7e/d4/7ed4dc7ca17a0e8fc4ff39c2ee08285e_808x488.png)
ECC+DH 密钥交换算法
#### 算法原理
* A 随机生成一对公私钥 PrivateA 和 PublicA
* B 随机生成一对公私钥 PrivateB 和 PublicB
* A 和 B 把自己的公钥(公开参数)发给对方
* A 和 B 使用自己的私钥和对方公钥生成 ShareKey
* ShareKeyA \= ShareKeyB
### 优化
轮盘
## 心跳包
### 为什么需要心跳机制
长连接就是大家建立连接之后, 不主动断开. 双方互相发送数据, 发完了也不主动断开连接, 之后有需要发送的数据就继续通过这个连接发送.
TCP连接在默认的情况下就是所谓的长连接, 也就是说连接双方都不主动关闭连接, 这个连接就应该一直存在.
但是网络中的情况是复杂的, 这个连接可能会被切断. 比如客户端到服务器的链路因为故障断了, 或者服务器宕机了, 或者是你家网线被人剪了, 这些都是一些莫名其妙的导致连接被切断的因素, 还有几种比较特殊的。
### 影响TCP连接寿命的思素
在Android下,不管是GCM,还是微信,都是通过TCP长连接来进行消息收发的,TCP长连接存活,消息收发就及时,所以要对影响TCP连接寿命的因素进行研究。
#### 1、NAT超时(主要)
大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断(NAT超时的更多描述见附录9.1)。NAT超时是影响TCP连接寿命的一个重要因素(尤其是国内),所以客户端自动测算NAT超时时间,来动态调整心跳间隔,是一个重要的优化点。
#### 2、DHCP的租期(lease time)
目前测试发现安卓系统对DHCP的处理有Bug,DHCP租期到了不会主动续约并且会继续使用过期IP,这个问题会造成TCP长连接偶然的断连。(租期问题的具体描述见附录9.2)。
#### 3、网络状态变化
手机网络和WIFI网络切换、网络断开和连上等情况有网络状态的变化,也会使长连接变为无效连接,需要监听响应的网络状态变化事件,重新建立Push长连接。
### 心跳包的作用
TCP长连接本质上不需要心跳包来维持,。
心跳机制就是客户端隔一段时间向服务端发送心跳包,发现连接断了, 还可以尝试重连服务器。(如果让服务器来发送心跳包给客户端, 万一连接断了, 服务器就再也联系不上客户)
### 固定心跳包频率
建议:4-5分钟
| | WhatsApp | Line | GCM |
| --- | --- | --- | --- |
| WIFI | 4分45秒 | 3分20秒 | 15分钟 |
| 手机网络 | 4分45秒 | 7分钟 | 28分钟 |
### 智能心跳方案
[Android微信智能心跳方案](https://mp.weixin.qq.com/s/ghnmC8709DvnhieQhkLJpA?)
#### 前后台区分处理
为了保证微信收消息及时性的体验,当微信处于前台活跃状态时,使用固定心跳。
微信进入后台(或者前台关屏)时,先用几次最小心跳维持长链接。然后进入后台自适应心跳计算。这样做的目的是尽量选择用户不活跃的时间段,来减少心跳计算可能产生的消息不及时收取影响。
## 监控网络
### 第一种方法:插桩
为了兼容性考虑,我首先想到的还是插桩。360 开源的性能监控工具[ArgusAPM](https://github.com/Qihoo360/ArgusAPM)就是利用 Aspect 切换插桩,实现监控系统和 OkHttp 网络库的请求。
系统网络库的插桩实现可以参考[TraceNetTrafficMonitor](https://github.com/Qihoo360/ArgusAPM/blob/bc03d63c65019cd3ffe2cbef9533c9228b3f2381/argus-apm/argus-apm-aop/src/main/java/com/argusapm/android/aop/TraceNetTrafficMonitor.java),主要利用[Aspect](http://www.shouce.ren/api/spring2.5/ch06s02.html)的切面功能,关于 OkHttp 的拦截可以参考[OkHttp3Aspect](https://github.com/Qihoo360/ArgusAPM/blob/bc03d63c65019cd3ffe2cbef9533c9228b3f2381/argus-apm/argus-apm-okhttp/src/main/java/com/argusapm/android/okhttp3/OkHttp3Aspect.java),它会更加简单一些,因为 OkHttp 本身就有代理机制。
~~~
@Pointcut("call(public okhttp3.OkHttpClient build())")
public void build() {
}
@Around("build()")
public Object aroundBuild(ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
if (target instanceof OkHttpClient.Builder && Client.isTaskRunning(ApmTask.TASK_NET)) {
OkHttpClient.Builder builder = (OkHttpClient.Builder) target;
builder.addInterceptor(new NetWorkInterceptor());
}
return joinPoint.proceed();
}
~~~
插桩的方法看起来很好,但是并不全面。如果使用的不是系统和 OkHttp 网络库,又或者使用了 Native 代码的网络请求,都无法监控到。
### 第二种方法:Native Hook。
跟 I/O 监控一样,这个时候我们想到了强大的 Native Hook。网络相关的我们一般会 Hook 下面几个方法 :
* 连接相关:connect。
* 发送数据相关:send 和 sendto。
* 接收数据相关:recv 和 recvfrom
Android 在不同版本 Socket 的逻辑会有那么一些差异,以 Android 7.0 为例,Socket 建连的堆栈如下:
~~~
java.net.PlainSocketImpl.socketConnect(Native Method)
java.net.AbstractPlainSocketImpl.doConnect
java.net.AbstractPlainSocketImpl.connectToAddress
java.net.AbstractPlainSocketImpl.connect
java.net.SocksSocketImpl.connect
java.net.Socket.connect
com.android.okhttp.internal.Platform.connectSocket
com.android.okhttp.Connection.connectSocket
com.android.okhttp.Connection.connect
~~~
“socketConnect”方法对应的 Native 方法定义在[PlainSocketImpl.c](http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/PlainSocketImpl.c),查看makefile可以知道它们会编译在 libopenjdk.so 中。不过在 Android 8.0,整个调用流程又完全改变了。为了兼容性考虑,我们直接 PLT Hook 内存的所有 so,但是需要排除掉 Socket 函数本身所在的 libc.so。
~~~
hook_plt_method_all_lib("libc.so", "connect", (hook_func) &create_hook);
hook_plt_method_all_lib("libc.so, "send", (hook_func) &send_hook);
hook_plt_method_all_lib("libc.so", "recvfrom", (hook_func) &recvfrom_hook);
...
~~~
这种做法不好的地方在于会把系统的 Local Socket 也同时接管了,需要在代码中增加过滤条件。在今天的 Sample 中,我给你提供了一套简单的实现。其实无论是哪一种 Hook,如果熟练掌握之后你会发现它并不困难。我们需要耐心地寻找,梳理清楚整个调用流程。
### 第三种方法:统一网络库。
尽管拿到了所有的网络调用,想想会有哪些使用场景呢?模拟网络数据、统计应用流量,或者是单独代理 WebView 的网络请求。
![](https://img.kancloud.cn/84/e7/84e7ca3a73979060b8c9baecb4d9867e_1652x852.png)
一般来说,我们不会非常关心第三方的网络请求情况,而对于我们应用自身的网络请求,最好的监控方法还是统一网络库。**不过我们可以通过插桩和 Hook 这两个方法,监控应用中有哪些地方使用了其他的网络库,而不是默认的统一网络库。**
# 参考资料
[微信终端跨平台组件 Mars 系列(三)连接超时与IP&Port排序](https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286458&idx=1&sn=320f690faa4f97f7a49a291d4de174a9&chksm=8334c3b8b4434aae904b6d590027b100283ef175938610805dd33ca53f004bd3c56040b11fa6#rd)
[Android微信智能心跳方案](https://mp.weixin.qq.com/s/ghnmC8709DvnhieQhkLJpA?)
[看完您如果还不明白 Kerberos 原理,算我输](https://juejin.cn/post/6844903955416219661)
[16 | 网络优化(中):复杂多变的移动网络该如何优化?](https://blog.yorek.xyz/android/paid/master/network_2/)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台