[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倍,對服務器連接資源是極大的浪費。同時,并發連接是否會引起連接資源的競爭。
微信【復合連接】:

初始階段,應用發起對 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 是一種計算機網絡認證協議,它允許某實體在非安全網絡環境下通信,向另一個實體以一種安全的方式證明自己的身份。**

#### 主要角色
* 用戶鑒權后臺AS (Authentication Server)
* 票據后臺TGS(Ticket-Granting Server)
* 業務后臺App Server
#### 流程
* 密碼校驗、安全通道的建立,獲取大票TGT
* 獲取業務票據,TGT換取相應的業務小票ST
* 使用業務小票ST去訪問業務服務器
### ECDH

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 的網絡請求。

一般來說,我們不會非常關心第三方的網絡請求情況,而對于我們應用自身的網絡請求,最好的監控方法還是統一網絡庫。**不過我們可以通過插樁和 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 性能優化
- 數據跨平臺