❝歡迎來到 《深刻探索 Android 網絡優化(3、網絡優化篇)下》~css
❞
❝問題:DNS 解析慢/被劫持?html
❞
使用 HTTPDSN,HTTPDNS 不是使用 DNS 協議,向 DNS 服務器傳統的 53 端口發送請求,而是使用 HTTP 協議向 DSN 服務器的 80 端口發送請求。java
在 Awesome-WanAndroid 中已經實現了 HTTPDNS 優化,其優化代碼以下所示:android
// HttpModule-provideClient:httpDns 優化
builder.dns(OkHttpDns.getIns(WanAndroidApp.getAppComponent().getContext())); /** * FileName: OkHttpDNS * Date: 2020/5/8 16:08 * Description: HttpDns 優化 * @author JsonChao */ public class OkHttpDns implements Dns { private HttpDnsService dnsService; private static OkHttpDns instance = null; private OkHttpDns(Context context) { dnsService = HttpDns.getService(context, "161133"); // 一、設置預解析的 IP 使用 Https 請求。 dnsService.setHTTPSRequestEnabled(true); // 二、預先註冊要使用到的域名,以便 SDK 提早解析,減小後續解析域名時請求的時延。 ArrayList<String> hostList = new ArrayList<>(Arrays.asList("www.wanandroid.com")); dnsService.setPreResolveHosts(hostList); } public static OkHttpDns getIns(Context context) { if (instance == null) { synchronized (OkHttpDns.class) { if (instance == null) { instance = new OkHttpDns(context); } } } return instance; } @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { String ip = dnsService.getIpByHostAsync(hostname); LogHelper.i("httpDns: " + ip); if(ip != null){ List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip)); return inetAddresses; } // 三、若是從阿里雲 DNS 服務器獲取不到 ip 地址,則走運營商域名解析的過程。 return Dns.SYSTEM.lookup(hostname); } } 複製代碼
從新安裝 App,經過 HTTPDNS 獲取到 IP 地址 log 以下所示:git
2020-05-11 10:41:55.139 4036-4184/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpDns.java | 52 | lookup] httpDns: 47.104.74.169
2020-05-11 10:41:55.142 4036-4185/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpDns.java | 52 | lookup] httpDns: 47.104.74.169 複製代碼
利用 HTTP 協議的 keep-alive,創建鏈接後,會先將鏈接放入鏈接池中,若是有另外一個請求的域名和端口是同樣的,就直接使用鏈接池中對應的鏈接發送和接收數據。在實現網絡庫的鏈接管理時須要注意如下4點:github
TCP 鏈接不復用,也就是每發起一個網絡請求都要從新創建鏈接,而剛開始鏈接都會經歷一個慢啓動的過程,可謂是慢上加慢,所以 HTTP 1.0 性能很是差。web
引入了持久鏈接,即 TCP 鏈接能夠複用,但數據通訊必須按次序來,也就是後面的請求必須等前面的請求完成才能進行。當全部請求都集中在一條鏈接中時,在網絡擁塞時容易出現 TCP 隊首阻塞問題。算法
Google 2013 實現,2018 基於 QUIC 協議的 HTTP 被確認爲 HTTP3。chrome
QUIC 簡單理解爲 HTTP/2.0 + TLS 1.3 + UDP。弱網環境下表現好與 TCP。數據庫
在 Awesome-WanAndroid 中已經使用 OkHttpEventListener 實現了網絡請求的質量監控,其代碼以下所示:
// 網絡請求質量監控
builder.eventListenerFactory(OkHttpEventListener.FACTORY); /** * FileName: OkHttpEventListener * Date: 2020/5/8 16:28 * Description: OkHttp 網絡請求質量監控 * @author quchao */ public class OkHttpEventListener extends EventListener { public static final Factory FACTORY = new Factory() { @Override public EventListener create(Call call) { return new OkHttpEventListener(); } }; OkHttpEvent okHttpEvent; public OkHttpEventListener() { super(); okHttpEvent = new OkHttpEvent(); } @Override public void callStart(Call call) { super.callStart(call); LogHelper.i("okHttp Call Start"); okHttpEvent.callStartTime = System.currentTimeMillis(); } /** * DNS 解析開始 * * @param call * @param domainName */ @Override public void dnsStart(Call call, String domainName) { super.dnsStart(call, domainName); okHttpEvent.dnsStartTime = System.currentTimeMillis(); } /** * DNS 解析結束 * * @param call * @param domainName * @param inetAddressList */ @Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) { super.dnsEnd(call, domainName, inetAddressList); okHttpEvent.dnsEndTime = System.currentTimeMillis(); } @Override public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) { super.connectStart(call, inetSocketAddress, proxy); okHttpEvent.connectStartTime = System.currentTimeMillis(); } @Override public void secureConnectStart(Call call) { super.secureConnectStart(call); okHttpEvent.secureConnectStart = System.currentTimeMillis(); } @Override public void secureConnectEnd(Call call, @Nullable Handshake handshake) { super.secureConnectEnd(call, handshake); okHttpEvent.secureConnectEnd = System.currentTimeMillis(); } @Override public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol) { super.connectEnd(call, inetSocketAddress, proxy, protocol); okHttpEvent.connectEndTime = System.currentTimeMillis(); } @Override public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol, IOException ioe) { super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe); } @Override public void connectionAcquired(Call call, Connection connection) { super.connectionAcquired(call, connection); } @Override public void connectionReleased(Call call, Connection connection) { super.connectionReleased(call, connection); } @Override public void requestHeadersStart(Call call) { super.requestHeadersStart(call); } @Override public void requestHeadersEnd(Call call, Request request) { super.requestHeadersEnd(call, request); } @Override public void requestBodyStart(Call call) { super.requestBodyStart(call); } @Override public void requestBodyEnd(Call call, long byteCount) { super.requestBodyEnd(call, byteCount); } @Override public void responseHeadersStart(Call call) { super.responseHeadersStart(call); } @Override public void responseHeadersEnd(Call call, Response response) { super.responseHeadersEnd(call, response); } @Override public void responseBodyStart(Call call) { super.responseBodyStart(call); } @Override public void responseBodyEnd(Call call, long byteCount) { super.responseBodyEnd(call, byteCount); // 記錄響應體的大小 okHttpEvent.responseBodySize = byteCount; } @Override public void callEnd(Call call) { super.callEnd(call); okHttpEvent.callEndTime = System.currentTimeMillis(); // 記錄 API 請求成功 okHttpEvent.apiSuccess = true; LogHelper.i(okHttpEvent.toString()); } @Override public void callFailed(Call call, IOException ioe) { LogHelper.i("callFailed "); super.callFailed(call, ioe); // 記錄 API 請求失敗及緣由 okHttpEvent.apiSuccess = false; okHttpEvent.errorReason = Log.getStackTraceString(ioe); LogHelper.i("reason " + okHttpEvent.errorReason); LogHelper.i(okHttpEvent.toString()); } } 複製代碼
成功 log 以下所示:
2020-05-11 11:00:42.678 6682-6847/json.chao.com.wanandroid D/OkHttp: --> GET https://www.wanandroid.com/banner/json
2020-05-11 11:00:42.687 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ Thread: RxCachedThreadScheduler-3 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ OkHttpEventListener.callStart (OkHttpEventListener.java:46) 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ LogHelper.i (LogHelper.java:37) 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpEventListener.java | 46 | callStart] okHttp Call Start 2020-05-11 11:00:42.688 6682-6848/json.chao.com.wanandroid I/WanAndroid-LOG: └──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2020-05-11 11:00:43.485 6682-6847/json.chao.com.wanandroid D/OkHttp: <-- 200 OK https://www.wanandroid.com/banner/json (806ms, unknown-length body) 2020-05-11 11:00:43.496 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ Thread: RxCachedThreadScheduler-2 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ OkHttpEventListener.callEnd (OkHttpEventListener.java:162) 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ LogHelper.i (LogHelper.java:37) 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ [OkHttpEventListener.java | 162 | callEnd] NetData: [ 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ callTime: 817 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ dnsParseTime: 6 2020-05-11 11:00:43.498 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ connectTime: 721 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ secureConnectTime: 269 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ responseBodySize: 975 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ apiSuccess: true 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: │ ] 2020-05-11 11:00:43.499 6682-6847/json.chao.com.wanandroid I/WanAndroid-LOG: └──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 複製代碼
見 深刻探索 Android 網絡優化(2、網絡優化基礎篇)下 - 首部壓縮。
不變參數客戶端只需上傳以此,其它參數均在接入層進行擴展。
使用 Protocol Buffers 替代 JSON 序列化。
HTTPS 一般須要多消耗 2 RTT 的協商時延。
TLS 1.2 引入了 SHA-256 哈希算法,摒棄了 SHA-1,對加強數據完整性有着顯著優點。
IETF(Internet Engineering Task Froce,互聯網工程任務組)制定的 TLS 1.3 是有史以來最安全、複雜的 TLS 協議。它具備以下特色:
相比於 TLS 1.2 及以前的版本,TLS 1.3 的握手再也不支持靜態的 RSA 密鑰交換,使用的是帶有前向安全的 Diffie-Hellman 進行全面握手。所以 TLS 1.3 只需 1-RTT 握手時間。
刪除了以前版本的不安全的加密算法。
此外,咱們能夠在 Google 瀏覽器設置 TLS 1.3。
參考 TLS 1.3 協議,合併請求,優化加密算法,使用 session-ticket 等策略,力求在安全和體驗間找到一個平衡點。
在 TLS 中性能開銷最大的是 TLS 握手階段的 RSA 加解密。在 slight-ssl 中又嘗試以下幾種解決方案:
基於 TLS 1.3 草案標準而實現。
相似於 TLS 協議,mmtls 協議也是位於業務層與網絡鏈接層中間。
TLS 1.3 Handshake 協議有以下幾類:
而 mmtls Handshake 協議有以下幾種:
「1-RTT ECDHE 密鑰協商原理」
ECDH 密鑰交換協議須要使用兩個算法:
可是 1-RTT ECDHE 算法容易被中間人攻擊,中間人能夠截獲雙方的公鑰運行 ECDH_Generate_key 生成本身的公私鑰對,而後將公鑰發送給某一方。
❝如何解決中間人攻擊?
❞
中間人攻擊產生的本質緣由是沒有通過端點認證,須要」帶認證的密鑰協商「。
❝數據認證的方式?
❞
數據認證有對稱與非對稱兩種方式:
ECDH 認證密鑰協商就是 ECDH 密鑰協商 + 數字簽名算法 ECDSA。
雙方密鑰協商會對自身發出的公鑰使用簽名算法,因爲簽名算法中的公鑰 ECDSA_verify_key 是公開的,中間人沒有辦法阻止別人獲取公鑰。
而 mmtls 僅對 Server 作認證,由於通訊一方簽名其協商數據就不會被中間人攻擊。
在 TLS 中,提供了可選的雙方相互認證的能力:
「1-RTT PSK 密鑰協商原理」
在以前的 ECDH 握手下,Server 會下發加密的 PSK{key, ticket{key}},其中:
1)、首先,Client 將 ticket{key}、Client_Random 發送給 Server。
2)、而後,Server 使用 ticket_key 解密獲得 key、Server_Random、Client_Random 計算 MAC 來認證。
3)、最後,Server 將 Server_Random、MAC 發送給 Client,Client 同 Server 使用 ticket_key 解密獲得 key、Server_Random、Client_Random 去計算 MAC 來驗證是否與收到的 MAC 匹配。
「0-RTT ECDH 密鑰協商原理」
要想實現 0-RTT 密鑰協商,就必須在協商一開始就將業務數據安全地傳遞到對端。
預先生成一對公私鑰(static_svr_pub_key, static_svr_pri_key),並將公鑰預置在 Client,私鑰持久保存在 Server。
1)、首先,Client 經過 static_svr_pub_key 與 cli_pri_key 生成一個對稱密鑰SS(Static Secret),用 SS 衍生的密鑰對業務數據加密。
2)、而後,Client cli_pub_key、Client_Random、SS 加密的 AppData 發送給 Server,Sever 經過 cli_pub_key 和 static_svr_pri_key 算出 SS,解密業務數據包。
「1-RTT PSK 密鑰協商原理」
在進行 1-RTT PSK 握手以前,Client 已經有一個對稱加密密鑰 key 了,直接使用此 key 與 ticket{key} 一塊兒傳遞給 Server 便可。
❝TLS 1.3 爲何要廢除 RSA?
❞
所以 TLS 1.3 引入了 PFS(perfect forward secrecy,前向安全性),即徹底向前保密,一個密鑰被破解,並不會影響其它密鑰的安全性。
例如 0-RTT ECDH 密鑰協商加密依賴了靜態 static_svr_pri_key,不符合 PFS,咱們可使用 0-RTT ECDH-ECDHE 密鑰協商,即進行 0-RTT ECDH 協商的過程當中也進行 ECDHE 協商。0-RTT PSK 密鑰協商的靜態 ticket_key 同理也能夠加入 ECDHE 協商。
❝verify_key 如何下發給客戶端?
❞
爲避免證書鏈驗證帶來的時間消耗及傳輸帶來的帶寬消耗,直接將 verify_Key 內置客戶端便可。
❝如何避免簽名密鑰 sign_key 泄露帶來的影響?
❞
由於 mmtls 內置了 verify_key 在客戶端,必要時及時經過強制升級客戶端的方式來撤銷公鑰並更新。
❝爲何要在上述密鑰協商過程當中都要引入 client_random、server_random、svr_pub_key 一塊兒作簽名?
❞
由於 svr_pri_Key 可能會泄露,全部單獨使用 svr_pub_key 時會有隱患,由於須要引入 client_random、server_random 來保證獲得的簽名值惟一對應一次握手。
「一、認證加密」
「二、密鑰擴展」
雙方使用相同的對稱密鑰進行加密通訊容易被某些對稱密鑰算法破解,所以,須要對原始對稱密鑰作擴展變換獲得相應的對稱加密參數。
密鑰變長鬚要使用密鑰延時函數(KDF,Key Derivation Function),而 TLS 1.3 與 mmtls 都使用了 HKDF 作密鑰擴展。
「三、防重放」
爲解決防重放,咱們能夠爲鏈接上的每個業務包都添加一個遞增的序列號,只要 Server 檢查到新收到的數據包的序列號小於等於以前收到的數據包的序列號,就判斷爲重放包,mmtls 將序列號做爲構造 AES-GCM 算參數 nonce 的一部分,這樣就不須要對序列號單獨認證。
在 0-RTT 握手下,第一個業務數據包和握手數據包沒法使用上述方案,此時須要客戶端在業務框架層去協調支持防重放。
mmtls 的 「工做過程」 以下所示:
其優點具備以下4點:
最後,咱們能夠在統一接入層對傳輸數據二次加密,須要注意二次加密會增長客戶端與服務器的處理耗時。
❝若是手機設置了代理,TLS 加密的數據能夠被解開並被利用,如何處理?
❞
能夠在 客戶端鎖定根證書,能夠同時兼容老版本與保證證書替換的靈活性。
在一線互聯網公司,都會有統一的網絡中臺:
一個跨平臺的 Socket 層解決方案,不支持完整的 HTTP 協議。
Mars 的兩個核心模塊以下:
其中 STN 模塊的組成圖以下所示:
根據網絡狀況,調整其它超時的係數或絕對值。
❝Mars 是如何進行 鏈接優化 的?
❞
每間隔幾秒啓動一個新的鏈接,只要有鏈接創建成功,則關閉其它鏈接。=> 有效提高鏈接成功率。
經過感知網絡的狀態切換到更好的網絡環境下。
❝Mars 是如何進行 弱網優化 的?
❞
在弱網下儘可能保證下載完整的圖片輪廓顯示,提升用戶體驗。
將分包轉成流式傳輸。
一個多機房的總體方案,在多個地區同時存在對等的多個機房,以用戶維度劃分,多機房共同承擔全量用戶的流量。
在單個機房發送故障時,故障機房的流量能夠快速地被遷引到可用機房,減小故障的恢復時間。
應用一種有策略的重試機制,將網絡請求以是否發送到 socket 緩衝區做爲分割,將網絡請求生命週期劃分爲」請求開始到發送到 socket 緩衝區「和」已經發送到 socket 緩衝區到請求結束「兩個階段。
這樣當用戶進電梯由於網絡抖動的緣由網絡連接斷了,可是數據其實已經請求到了 socket 緩衝區,使用這種有策略的重試機制,咱們就能夠提高客戶端的網絡抗抖動能力。
同步差量數據,達到節省流量,提升通訊效率與請求成功率。
客戶端用戶不在線時,SYNC 服務端將差量數據保持在數據庫中。當客戶端下次鏈接到服務器時,再同步差量數據給用戶。
核心思想是保障核心業務在體驗可接受範圍內作降級非核心功能和業務。從入口到業務接口總共分爲四個層級,以下所示:
結合 JobScheduler 來根據實際狀況作網絡請求. 比方說 Splash 閃屏廣告圖片, 咱們能夠在鏈接到 Wifi 時下載緩存到本地; 新聞類的 App 能夠在充電, Wifi 狀態下作離線緩存。
app應該對網絡請求劃分優先級儘量快地展現最有用的信息給用戶。(高優先級的服務優先使用長鏈接)
馬上呈現給用戶一些實質的信息是一個比較好的用戶體驗,相對於讓用戶等待那些不那麼必要的信息來講。這能夠減小用戶不得不等待的時間,增長APP在慢速網絡時的實用性。(低優先級使用短鏈接)
將衆多請求放入等待發送隊列中,待長連通道創建完畢後再將等待隊列中的請求放在長連通道上依次送出。
HTTP 的請求頭鍵值對中的的鍵是容許相同和重複的。例如 Set-Cookie/Cookie 字段能夠包含多組相同的鍵名稱數據。在長連通訊中,若是對 header 中的鍵值對用不加處理的字典方式保存和傳輸,就會形成數據的丟失。
儘量將問題在上線前暴露出來。
區分地域、時間段、版本、機型。
業務失敗與請求失敗。
以便進行鍼對性地優化。
RPS/TPS/QPS,每秒的請求次數,服務器最基本的性能指標,RPS 越高就說明服務器的性能越好。
反映服務器的負載能力,即服務器可以同時支持的客戶端數量,越大越好。
反映服務器的處理能力,即快慢程度,響應時間越短越好。
CPU、內存、硬盤和網卡等系統資源。能夠利用 top、vmstat 等工具檢測相關性能。
要實現客戶端監控,首先咱們應該要統一網絡庫,而客戶端須要監控的指標主要有以下三類:
爲了運算簡單咱們能夠拋棄 UV,只計算每一分鐘部分維度的 PV。
關於 ArgusAPM 的網絡監控切面源碼分析能夠參考我以前寫的 深刻探索編譯插樁技術(2、AspectJ) - 使用 AspectJ 打造本身的性能監控框架
監控不全面,由於 App 可能不使用系統/OkHttp 網絡庫,或是直接使用 Native 網絡請求。
須要 Hook 的方法有三類:
不一樣版本 Socket 的實現邏輯會有差別,爲了兼容性考慮,咱們直接 PLT Hook 內存全部的 so,可是須要排除掉 Socket 函數自己所在的 libc.so。其 PLT 的 Hook 代碼以下所示:
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); 複製代碼
下面,咱們使用 PLT Hook 來獲取網絡請求信息。
其成功 log 以下所示:
2020-05-21 15:10:37.328 27507-27507/com.dodola.socket E/HOOOOOOOOK: JNI_OnLoad
2020-05-21 15:10:37.328 27507-27507/com.dodola.socket E/HOOOOOOOOK: enableSocketHook
2020-05-21 15:10:37.415 27507-27507/com.dodola.socket E/HOOOOOOOOK: hook_plt_method
2020-05-21 15:10:58.484 27507-27677/com.dodola.socket E/HOOOOOOOOK: socket_connect_hook sa_family: 10
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: stack:com.dodola.socket.SocketHook.getStack(SocketHook.java:13)
libcore.io.Linux.connect(Native Method)
libcore.io.BlockGuardOs.connect(BlockGuardOs.java:126)
libcore.io.IoBridge.connectErrno(IoBridge.java:152)
libcore.io.IoBridge.connect(IoBridge.java:130)
java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:129)
java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:356)
java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
java.net.SocksSocketImpl.connect(SocksSocketImpl.java:357)
java.net.Socket.connect(Socket.java:616)
com.android.okhttp.internal.Platform.connectSocket(Platform.java:145)
com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:141)
com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:112)
com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:184)
com.android.okhttp.internal.http.Strea
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: AF_INET6 ipv6 IP===>14.215.177.39:443
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: socket_connect_hook sa_family: 1
2020-05-21 15:10:58.495 27507-27677/com.dodola.socket E/HOOOOOOOOK: Ignore local socket connect
2020-05-21 15:10:58.523 27507-27677/com.dodola.socket E/HOOOOOOOOK: socket_connect_hook sa_family: 1
2020-05-21 15:10:58.523 27507-27677/com.dodola.socket E/HOOOOOOOOK: Ignore local socket connect
2020-05-21 15:10:58.806 27507-27677/com.dodola.socket E/HOOOOOOOOK: respond:<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新聞</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地圖</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>視頻</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>貼吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登陸</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登陸</a>');
</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多產品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>關於百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必讀</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意見反饋</a> 京ICP證030173號 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
複製代碼
此外,咱們也可使用愛奇藝提供的 android_plt_hook 來實現 PLT Hook。
接管了系統的 Local Socket,須要在代碼中增長過濾條件。
❝爲何要作接入層監控?
❞
服務的入口和出口流量、服務端的處理時延、錯誤率等。
❝監控的同時如何實現準確的自動化報警呢?
❞
一般是兩種結合使用。
超限拒絕訪問。
若是用戶反饋 App 消耗的流量過多,或後臺消耗流量較多,咱們均可以具體地分析網絡請求日誌、以及下發命令查看具體時間段的流量、客戶端線上監控 + 體系化方案建設 來實現單點問題的追查。
❝注意:體現演進的過程。
❞
網絡優化及監控咱們剛開始並無去作,所以咱們在 APP 的初期並無注意到網絡的問題,而且咱們一般是在 WIFI 場景下進行開發,因此並無注意到網絡方面的問題。
當 APP 增大後,用戶增多,逐漸由用戶反饋 界面打不開或界面顯示慢,也有用戶反饋咱們 APP 消耗的流量比較多。在咱們接受到這些反饋的時候,咱們沒有數據支撐,沒法判斷用戶反饋是否是正確的。同時,咱們也不知道線上用戶真實的體驗是怎樣的。因此,咱們就 「創建了線上的網絡監控,主要分爲 質量監控與流量監控」。
首先,最重要的是接口的請求成功率與每步的耗時,好比 DNS 的解析時間、創建鏈接的時間、接口失敗的緣由,而後在合適的時間點上報給服務器。
首先,咱們獲取到了精準的流量消耗狀況,而且在 APM 後臺,能夠下發指令獲取用戶在具體時間段的流量消耗狀況。 => 引出亮點 => 先後臺流量獲取方案。 關於指標 => 網絡監控。
❝注意:結合實際案例
❞
首先,咱們處理了項目當中展現數據相關的接口,同時,對時效性沒那麼強的接口作了數據的緩存,也就是一段時間內的重複請求直接走緩存,而不走網絡請求,從而避免流量浪費。對於一些數據的更新,例如省市區域、配置信息、離線包等信息,咱們 「加上版本號的概念,以實現每次更新只傳遞變化的數據,即實現了增量更新」 => 亮點:離線包增量更新實現原理與關鍵細節。
而後,咱們在上傳流量這方面也作了處理,好比針對 POST 請求,咱們對 Body 作了 GZip 壓縮,而對於圖片的發送,必需要通過壓縮,它可以在保證清晰度的前提下極大地減小其體積。
對於圖片展現,咱們採用了不一樣場景展現不一樣圖片的策略,好比在列表展現界面,咱們只展現了縮略圖,而到用戶顯示大圖的時候,咱們纔去展現原圖。 => 引出 webp 的使用策略。
首先,部分用戶遇到流量消耗多的狀況是確定會存在的,由於線上用戶很是多,每一個人遇到的狀況確定是不同的,好比有些用戶他的操做路徑比較詭異,可能會引起一些異常狀況,所以有些用戶可能會消耗比較多的流量。
咱們在客戶端能夠精確q地獲取到流量的消耗,這樣就給咱們排查用戶的流量消耗提供了依據,咱們就知道用戶的流量消耗是否是不少。
此外,經過網絡請求質量的監控,咱們知道了用戶全部網絡請求的次數與大小,經過大小和次數排查,咱們就能知道用戶在使用過程當中遇到了哪些 bug 或者是執行了一些異常的邏輯致使重複下載,處於不斷重試的過程之中。
在客戶端,咱們發現了相似的問題以後,咱們還須要配備主動預警的能力,及時地通知開發同窗進行排除驗證,經過以上手段,咱們對待用戶的反饋就能更加高效的解決,由於咱們有了用戶全部的網絡請求數據。
若是一個 WiFi 發送過數據包,可是沒有收到任何的 ACK 回包,這個時候就能夠初步判斷當前的 WiFi 是有問題的。
網絡優化能夠說是移動端性能優化領域中水最深的領域之一,要想作好網絡優化必須具有很是紮實的技術功底與全鏈路思惟。總所周知,對於一個工程師的技術評級每每是以他最深刻的那一兩個領域爲基準,而不是計算其技術棧的平均值。所以,建議你們能找準一兩個點,例如 網絡、內存、NDK、Flutter,對其進行深刻挖掘,以打造自身的技術壁壘。而筆者後續也會利用晚上的時間繼續深刻 「網絡協議與安全」 的領域,開始持續不斷地深刻挖掘。
個人公衆號 JsonChao 開通啦,歡迎關注~
❝歡迎關注個人微信:
❞bcce5360
❝「因爲微信羣人數過多,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。」
❞
❝2千人QQ羣,「Awesome-Android學習交流羣,QQ羣號:959936182」, 歡迎你們加入~
❞
本文使用 mdnice 排版