就這樣把 Websocket 玩出了多種花樣!

一、首先我們要了解 Websocket 握手的原理

請求頭特徵

響應頭特徵

二、短連接輪詢、長連接、Websocket 橫向對比

  1. 短連接輪詢

  1. 長連接

題外話:

HTTP keep-alive 的作用是,告知服務端持久化當前的 TCP 連接,不要立即斷開,以便後續的 HTTP 請求複用它,也就是我們所說的「長連接」

HTTP 的 keep-alive 是爲了讓 TCP 活久一點,而 TCP 本身也有一個 keepalive(注意沒有橫槓哦)機制。這是 TCP 的一種檢測連接狀況的保活機制,keepalive 是 TCP 保活定時器:TCP 建立後,如果閒置沒用,服務器不可能白等下去,閒置一段時間 [可設置] 後,服務器就會嘗試向客戶端發送偵測包,來判斷 TCP 連接狀況,如果沒有收到對方的回答(ACK 包),就會過一會 [可設置] 再偵測一次,如果多次 [可設置] 都沒回答,就會丟棄這個 TCP 連接

(TCP keepalive 保活示意圖)

  1. Websocket

三、我在微信小程序中利用 WebSocket 都搗鼓了什麼?

1 驗籤鑑權及對應的容錯策略(登錄態要求、峯值訪問、服務端宕機異常)

背景與目的:

僞碼示意:

SocketTask.onOpen(function () {
  SocketTask.sendSocketMessage({
     msg_type: '驗籤',
     token: 'xxx'
  }(response) ={
      console.log(response.user_id, response.access_token)

      // 通道可用,打個標記
      global.isSocketAvaliable = true;
  })
})

2 心跳保活(減少 TCP 佔用)

背景與目的:爲了減少 TCP 連接的無效佔用,客戶端定時發送一個空包到服務端,告知服務端不要銷燬這條 socket,如果服務端超過一定時間都沒收到心跳包,則將關閉並銷燬該 socket

僞碼示意:

SocketTask.onOpen(function () {
  SocketTask.sendSocketMessage({
     msg_type: '驗籤',
     token: 'xxx'
  }(response) ={
      console.log(response.user_id, response.access_token)

      // 通道可用,打個標記
      global.isSocketAvaliable = true;
      
      // 驗籤成功,開始定時發送心跳包
      setInterval(() ={
          SocketTask.sendSocketMessage({
            msg_type: '心跳'
          });
      });
   });
})

3 模擬 RTT(用於弱網體驗優化)

背景與目的:在發送心跳包時,可得知一個心跳包的 RTT,以此模擬當前用戶網絡環境的 TCP RTT,並據此計算出平滑 RTO,用於弱網體驗優化

僞碼示意:

SocketTask.onOpen(function () {
  SocketTask.sendSocketMessage({
     msg_type: '驗籤',
     token: 'xxx'
  }(response) ={
      console.log(response.user_id, response.access_token)

      // 通道可用,打個標記
      global.isSocketAvaliable = true;
      
      // 驗籤成功,開始定時發送心跳包
      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 傳輸的字節數

僞碼示意:

  import Snappy from 'snappy';

  SocketTask.sendSocketMessage = function (msg) {
     const encryptedMsg = Snappy.encode(msg);
     
     wx.send(encryptedMsg);
  }

5 重連(階梯式錯位重連,避免擁擠)

背景與目的:用戶的網絡環境不穩定,可能會存在主動 / 被動斷開 socket 的情況,需要進行自動重連

僞碼示意:

SocketTask.onClose(function () {
  // 限定最大重連次數
  if (retryCount > maxCount) {
    return;
  }
  
  retryCount++;

  setTimeout(() ={
    SocketTask.connectSocket();
  }, retryCount * 1000 + Math.random() * 1000);
});

6 埋點中間層緩存(重複的用戶信息可以不用每次都上報,支持刷新緩存)

背景與目的:爲減少網絡傳輸的包體積,通過 websocket 上報埋點日誌時,可以把部分重複字段值在第一次上報時緩存在服務端,從第二次上報開始只上報值不重複的字段,然後由服務端做日誌合併

僞碼示意:

SocketTask.sendSocketMessage({
     msg_type: '埋點日誌',
     logs: {
       country: 'China', // 可緩存字段
       city: '北京', // 可緩存字段
       platform: '安卓', // 可緩存字段
       click_some_btn: true // 動態變化的埋點字段
     },
     cacheFields: ['country''city''platform'] // 只在第一次上報時攜帶
 });

7 啓用 TCP_NODELAY

TCP_NODELAY 是用來禁用 Nagle 算法的。Nagle 算法設計的目的是提高網絡帶寬利用率,其核心思路是「合併小的 TCP 包爲一個大的 TCP 包」,避免過多的小包的 TCP 頭部浪費網絡帶寬

參考資料:https://www.zhihu.com/question/42308970

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Q_QlgMQYnz7BZg3lSvb2dQ