🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[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/)