Websocket 可以玩出些什么花兒?
大廠技術??堅持周更??精選好文
一、首先我們要了解 Websocket 握手的原理

請求頭特征
HTTP 必須是 1.1 GET 請求
HTTP Header 中 Connection 字段的值必須為 Upgrade
HTTP Header 中 Upgrade 字段必須為 websocket
Sec-WebSocket-Key 字段的值是采用 base64 編碼的隨機 16 字節(jié)字符串
Sec-WebSocket-Protocol 字段的值記錄使用的子協(xié)議,比如 binary base64
Origin 表示請求來源
響應頭特征
狀態(tài)碼是 101 表示 Switching Protocols
Upgrade / Connection / Sec-WebSocket-Protocol 和請求頭一致
Sec-WebSocket-Accept 是通過請求頭的 Sec-WebSocket-Key 生成
二、短連接輪詢、長連接、Websocket 橫向對比
1. 短連接輪詢
很耗費 TCP 連接
而且 Header 重復發(fā)送
且通過宏任務發(fā)起,受限于 Event Loop,無法保證及時性
同時無效請求會很多
2. 長連接
HTTP keep-alive 開啟后雖然 TCP 可以復用,但是 Header 重復的問題并沒有解決
同時 HTTP keep-alive 還有一個有效期,有效期結束后服務端會發(fā)偵查幀探查 TCP 是否有效
題外話:
HTTP keep-alive 的作用是,告知服務端持久化當前的?TCP?連接,不要立即斷開,以便后續(xù)的 HTTP 請求復用它,也就是我們所說的「長連接」
HTTP 的 keep-alive 是為了讓 TCP 活久一點,而 TCP 本身也有一個 keepalive(注意沒有橫杠哦)機制。這是 TCP 的一種檢測連接狀況的保活機制,keepalive 是 TCP 保活定時器:TCP 建立后,如果閑置沒用,服務器不可能白等下去,閑置一段時間[可設置]后,服務器就會嘗試向客戶端發(fā)送偵測包,來判斷 TCP 連接狀況,如果沒有收到對方的回答(ACK包),就會過一會[可設置]再偵測一次,如果多次[可設置]都沒回答,就會丟棄這個 TCP 連接

(TCP keepalive 保活示意圖)
3. Websocket
和 HTTP 一樣都是建立在 TCP 協(xié)議之上,但只需一次 HTTP 握手,就能建立持久性連接,后續(xù)就不走 HTTP 了,而是 WebSocket 特有的數(shù)據(jù)幀
全雙工通信,雙向數(shù)據(jù)傳輸
數(shù)據(jù)格式輕量,且支持發(fā)送二進制數(shù)據(jù),支持 ws 和加密的 wss
三、我在微信小程序中利用 WebSocket 都搗鼓了什么?
1 驗簽鑒權及對應的容錯策略(登錄態(tài)要求、峰值訪問、服務端宕機異常)
背景與目的:
websocket 握手后,接口請求即可以放棄 HTTP 改走 weboskcet,但大部分業(yè)務接口都要求登錄態(tài),因此握手成功后必須先走一次簽名鑒權,獲取登錄態(tài)
當出現(xiàn)大流量訪問的場景(如大促、熱點活動等)或服務端出 bug 而導致服務端宕機,前端會做 對應容錯,將位于內存的等待隊列中的待發(fā)送請求立即降級成 HTTP 發(fā)送出去
偽碼示意:
SocketTask.onOpen(function?()?{
??SocketTask.sendSocketMessage({
?????msg_type:?'驗簽',
?????token:?'xxx'
??},?(response)?=>?{
??????console.log(response.user_id,?response.access_token)
??????//?通道可用,打個標記
??????global.isSocketAvaliable?=?true;
??})
})
2 心跳保活(減少 TCP 占用)
背景與目的:為了減少 TCP 連接的無效占用,客戶端定時發(fā)送一個空包到服務端,告知服務端不要銷毀這條 socket,如果服務端超過一定時間都沒收到心跳包,則將關閉并銷毀該 socket
偽碼示意:
SocketTask.onOpen(function?()?{
??SocketTask.sendSocketMessage({
?????msg_type:?'驗簽',
?????token:?'xxx'
??},?(response)?=>?{
??????console.log(response.user_id,?response.access_token)
??????//?通道可用,打個標記
??????global.isSocketAvaliable?=?true;
??????
??????//?驗簽成功,開始定時發(fā)送心跳包
??????setInterval(()?=>?{
??????????SocketTask.sendSocketMessage({
????????????msg_type:?'心跳'
??????????});
??????});
???});
})
3 模擬 RTT(用于弱網(wǎng)體驗優(yōu)化)
背景與目的:在發(fā)送心跳包時,可得知一個心跳包的 RTT,以此模擬當前用戶網(wǎng)絡環(huán)境的 TCP RTT,并據(jù)此計算出平滑 RTO,用于弱網(wǎng)體驗優(yōu)化
偽碼示意:
SocketTask.onOpen(function?()?{
??SocketTask.sendSocketMessage({
?????msg_type:?'驗簽',
?????token:?'xxx'
??},?(response)?=>?{
??????console.log(response.user_id,?response.access_token)
??????//?通道可用,打個標記
??????global.isSocketAvaliable?=?true;
??????
??????//?驗簽成功,開始定時發(fā)送心跳包
??????setInterval(()?=>?{
??????????//?計算?RTT
??????????const?begin?=?Date.now();
??????????SocketTask.sendSocketMessage({
????????????msg_type:?'心跳'
??????????},?()?=>?{
????????????const?end?=?Date.now();
????????????
????????????const?RTT?=?begin?-?end;
????????????
????????????const?smoothedRTO?=?cal(RTT);
????????????
????????????global.smoothedRTO?=?smoothedRTO;
??????????});
??????});
???});
});
4 Snappy 壓縮(橫向對比了 gzip / zip / 7z)
背景與目的:在小程序中引入第三方壓縮包(犧牲小程序包體積),減少 websocket 傳輸?shù)淖止?jié)數(shù)
偽碼示意:
??import?Snappy?from?'snappy';
??SocketTask.sendSocketMessage?=?function?(msg)?{
?????const?encryptedMsg?=?Snappy.encode(msg);
?????
?????wx.send(encryptedMsg);
??}
5 重連(階梯式錯位重連,避免擁擠)
背景與目的:用戶的網(wǎng)絡環(huán)境不穩(wěn)定,可能會存在主動 / 被動斷開 socket 的情況,需要進行自動重連
偽碼示意:
SocketTask.onClose(function?()?{
??//?限定最大重連次數(shù)
??if?(retryCount?>?maxCount)?{
????return;
??}
??
??retryCount++;
??setTimeout(()?=>?{
????SocketTask.connectSocket();
??},?retryCount?*?1000?+?Math.random()?*?1000);
});
6 埋點中間層緩存(重復的用戶信息可以不用每次都上報,支持刷新緩存)
背景與目的:為減少網(wǎng)絡傳輸?shù)陌w積,通過 websocket 上報埋點日志時,可以把部分重復字段值在第一次上報時緩存在服務端,從第二次上報開始只上報值不重復的字段,然后由服務端做日志合并
偽碼示意:
SocketTask.sendSocketMessage({
?????msg_type:?'埋點日志',
?????logs:?{
???????country:?'China',?//?可緩存字段
???????city:?'北京',?//?可緩存字段
???????platform:?'安卓',?//?可緩存字段
???????click_some_btn:?true?//?動態(tài)變化的埋點字段
?????},
?????cacheFields:?['country',?'city',?'platform']?//?只在第一次上報時攜帶
?});
7 啟用 TCP_NODELAY
TCP_NODELAY 是用來禁用 Nagle 算法的。Nagle 算法設計的目的是提高網(wǎng)絡帶寬利用率,其核心思路是「合并小的 TCP 包為一個大的 TCP 包」,避免過多的小包的 TCP 頭部浪費網(wǎng)絡帶寬
參考資料:https://www.zhihu.com/question/42308970
H5-Dooring, 讓H5制作, 更簡單
???謝謝支持
以上便是本次分享的全部內容,希望對你有所幫助^_^
喜歡的話別忘了?分享、點贊、收藏?三連哦~。
