小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

WebSocket 心得分享

 塞北de雪 2024-12-29 發(fā)布于江蘇

一、前言

?

本文將介紹 WebSocket 的封裝,比如:心跳機制,重連和一些問題如何去處理

?

二、背景

之前,錢包相關的查詢,我們是使用的輪詢方案來做的,后來更新了一次需求,需要做一些實時數(shù)據統(tǒng)計的更新,然后順帶給錢包的余額也用長連接來做了,好,那么故事就開始了...

某天,

「老板:」 我錢怎么沒了,但是我這里查賬戶還有。

「我的內心:」 恩?這玩意難道說... 后端沒返?

和后端溝通以后,感覺是返回了的,被擠賬號了?排查了一段時間以后,最終我將問題鎖定在手機息屏的操作上。

因為我們是一個 「H5」 的項目,APP 是嵌套在 webview 中,所以不能操作原生的事件來處理,只能將方案控制在瀏覽器提供的事件來處理。

好了,接下來各位可以看我是如何處理這個問題,如果沒有搞過也是可以有不少收獲,也歡迎大神評論區(qū)交流其他方案。

三、WebSocket

3.1 什么是 WebSocket ?為什么使用他?

以下是百度百科中對 「WebSocket」 的定義:

WebSocket 是一種在單個 TCP 連接上進行 全雙工 通信的協(xié)議。WebSocket  通信協(xié)議于2011年被 IETF 定為標準 RFC 6455,并由 RFC7936 補充規(guī)范。WebSocket API 也被 W3C 定為標準。

WebSocket 使得客戶端和服務器之間的數(shù)據交換變得更加簡單,允許服務端主動向客戶端推送數(shù)據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進行雙向數(shù)據傳輸。

「WebSocket 的關鍵特點」

  1. 「雙向通信(Full Duplex)」

    • 客戶端和服務器都可以主動發(fā)送數(shù)據,而不是像 HTTP 一樣只能由客戶端發(fā)起請求。
  2. 「實時性」

    • 消息可以實時傳遞,延遲更低,適合需要實時更新的場景。
  3. 「持久化連接」

    • 使用單個 TCP 連接完成多次數(shù)據交互,無需為每次通信重新建立連接。
  4. 「輕量級協(xié)議」

    • WebSocket 頭部信息非常小,比傳統(tǒng) HTTP 請求的頭部要輕量。
  5. 「節(jié)約資源」

    • 長連接減少了資源消耗,特別是在頻繁通信的場景中。

上述中,是 AI 給我們總結的 WebSocket 的特點,接下來我們要知道我們?yōu)槭裁词褂盟?code style="padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">HTTP 他能不能做,他的局限性又在哪里?

「傳統(tǒng) HTTP 的局限性:」

  1. HTTP 是基于請求-響應模型的,客戶端必須發(fā)起請求,服務器才能返回數(shù)據。
  2. 如果需要實時更新(如股票價格、在線聊天),通常需要使用輪詢(Polling)或長輪詢(Long Polling),這會導致:
    • 高資源消耗(頻繁的連接建立和斷開)。
    • 高網絡流量(每次請求都包含冗長的 HTTP 頭部信息)。
    • 更高的延遲(數(shù)據可能需要等待較長時間才能返回)。

其實 HTTP 是可以實現(xiàn)的,如果 HTTP 請求頻繁三次握手和四次揮手的操作會占用大量資源,HTTP/1.1 以后開啟了 「Keep-Alive (長連接)」,可以復用連接,但是實時的情況下,響應模型仍然會導致較高的延遲和資源消耗。

相比之下,WebSocket 通過一次握手建立連接以后,就可以保持雙向通信,服務器可以主動推送數(shù)據,無需客戶端輪詢。解決了 HTTP 帶來的一些痛點。

四、封裝 WebSocket

我們將實現(xiàn)以下幾個功能點:

  • 「重連」
  • 「心跳機制」
  • 「事件回調」
  • 「連接狀態(tài)管理」
  • 「銷毀」

4.1 Javascript 版本

class ReSocket {
  constructor(url, options = {}) {
    this.url = url; // WebSocket 服務器地址
    this.options = options; // 可選參數(shù)
    this.socket = null// WebSocket 實例
    this.maxReconnectTimes = options.maxReconnectTimes || 5// 最大重連次數(shù)
    this.reconnectTimes = 0// 當前重連次數(shù)
    this.reconnectInterval = options.reconnectInterval || 3000// 重連間隔時間(毫秒)
    this.isClosed = false// 是否已關閉
    this.isOpen = false// 是否已打開
    this.isConnect = false// 是否已連接
    this.isReconnecting = false// 是否正在重連
    this.isDestroyed = false// 是否已銷毀
    this.reconnectTimer = null// 重連定時器
    this.heartbeatTimer = null// 心跳定時器
    this.heartbeatInterval = options.heartbeatInterval || 30000// 心跳間隔時間(默認30秒)
    this.heartbeatData = options.heartbeatData || "ping"// 心跳數(shù)據
    this.onMessageCallback = null// 消息接收回調
    this.onOpenCallback = null// 連接成功回調
    this.onCloseCallback = null// 連接關閉回調
  }


  
  // 創(chuàng)建WebSocket實例
  createSocket() {
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      this.isOpen = true;
      this.isConnect = true;
      this.reconnectTimes = 0// 重連次數(shù)歸零
      this.startHeartbeat(); // 啟動心跳機制
      if (this.onOpenCallback) this.onOpenCallback(); // 調用連接成功回調
    };

    this.socket.onmessage = event => {
      if (this.onMessageCallback) this.onMessageCallback(event.data); // 調用消息接收回調
    };

    this.socket.onclose = () => {
      this.isOpen = false;
      this.isConnect = false;
      this.stopHeartbeat(); // 停止心跳機制
      if (this.onCloseCallback) this.onCloseCallback(); // 調用連接關閉回調
      if (!this.isClosed && this.reconnectTimes < this.maxReconnectTimes) {
        this.reconnect(); // 嘗試重連
      }
    };

    this.socket.onerror = error => {
      console.error("WebSocket 錯誤: ", error); // 錯誤處理
    };
  }

  // 開始連接
  connect() {
    if (this.isDestroyed) return// 如果已銷毀,則不再連接
    this.createSocket(); // 創(chuàng)建WebSocket實例
  }

  // 重連
  reconnect() {
    if (this.isReconnecting || this.reconnectTimes >= this.maxReconnectTimes)
      return// 防止重復重連

    this.isReconnecting = true;
    this.reconnectTimes++; // 增加重連次數(shù)

    this.reconnectTimer = setTimeout(() => {
      console.log(`正在重連... (${this.reconnectTimes})`); // 打印重連次數(shù)
      this.createSocket(); // 再次創(chuàng)建WebSocket實例
      this.isReconnecting = false// 重連狀態(tài)設置為false
    }, this.reconnectInterval); // 按設定時間重連
  }

  // 發(fā)送消息
  send(data) {
    if (this.isOpen) {
      this.socket.send(data); // 發(fā)送數(shù)據
    } else {
      console.error("WebSocket 未打開,無法發(fā)送消息。"); // 提示錯誤
    }
  }

  // 設置消息接收回調
  onMessage(callback) {
    this.onMessageCallback = callback; // 綁定接收消息的回調
  }

  // 設置連接成功回調
  onOpen(callback) {
    this.onOpenCallback = callback; // 綁定連接成功的回調
  }

  // 設置連接關閉回調
  onClose(callback) {
    this.onCloseCallback = callback; // 綁定連接關閉的回調
  }

  // 啟動心跳機制
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.isOpen) {
        this.send(this.heartbeatData); // 發(fā)送心跳數(shù)據
      }
    }, this.heartbeatInterval); // 按設定的時間間隔發(fā)送
  }

  // 停止心跳機制
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer); // 清除心跳定時器
      this.heartbeatTimer = null;
    }
  }

  // 關閉連接
  close() {
    this.isClosed = true// 設置為已關閉
    this.isOpen = false;
    this.socket.close(); // 關閉WebSocket連接
    this.stopHeartbeat(); // 停止心跳機制
    clearTimeout(this.reconnectTimer); // 清除重連定時器
  }

  // 銷毀實例
  destroy() {
    this.isDestroyed = true// 設置為已銷毀
    this.close(); // 關閉連接
  }
}

4.2 Typescript 版本

type ReSocketOptions = {
  maxReconnectTimes?: number// 最大重連次數(shù)
  reconnectInterval?: number// 重連間隔時間(毫秒)
  heartbeatInterval?: number// 心跳間隔時間(毫秒)
  heartbeatData?: string// 心跳數(shù)據
};

class ReSocket {
  private url: string;
  private socket: WebSocket | null = null;
  private maxReconnectTimes: number;
  private reconnectTimes: number = 0;
  private reconnectInterval: number;
  private isClosed: boolean = false;
  private isOpen: boolean = false;
  private isConnect: boolean = false;
  private isReconnecting: boolean = false;
  private isDestroyed: boolean = false;
  private reconnectTimer: NodeJS.Timeout | null = null;
  private heartbeatTimer: NodeJS.Timeout | null = null;
  private heartbeatInterval: number;
  private heartbeatData: string;
  private onMessageCallback: ((message: string) => void) | null = null;
  private onOpenCallback: (() => void) | null = null;
  private onCloseCallback: (() => void) | null = null;

  constructor(url: string, options: ReSocketOptions = {}) {
    this.url = url;
    this.maxReconnectTimes = options.maxReconnectTimes || 5;
    this.reconnectInterval = options.reconnectInterval || 3000;
    this.heartbeatInterval = options.heartbeatInterval || 30000;
    this.heartbeatData = options.heartbeatData || 'ping';
  }

  private createSocket()void {
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () =>
 {
      this.isOpen = true;
      this.isConnect = true;
      this.reconnectTimes = 0;
      this.startHeartbeat();
      if (this.onOpenCallback) this.onOpenCallback();
    };

    this.socket.onmessage = (event: MessageEvent) => {
      if (this.onMessageCallback) this.onMessageCallback(event.data);
    };

    this.socket.onclose = () => {
      this.isOpen = false;
      this.isConnect = false;
      this.stopHeartbeat();
      if (this.onCloseCallback) this.onCloseCallback();
      if (!this.isClosed && this.reconnectTimes < this.maxReconnectTimes) {
        this.reconnect();
      }
    };

    this.socket.onerror = (error: Event) => {
      console.error("WebSocket 錯誤: ", error);
    };
  }

  public connect(): void {
    if (this.isDestroyed) return;
    this.createSocket();
  }

  private reconnect(): void {
    if (this.isReconnecting || this.reconnectTimes >= this.maxReconnectTimes) return;

    this.isReconnecting = true;
    this.reconnectTimes++;

    this.reconnectTimer = setTimeout(() => {
      console.log(`正在重連... (${this.reconnectTimes})`);
      this.createSocket();
      this.isReconnecting = false;
    }, this.reconnectInterval);
  }

  public send(data: string): void {
    if (this.isOpen && this.socket) {
      this.socket.send(data);
    } else {
      console.error("WebSocket 未打開,無法發(fā)送消息。");
    }
  }

  public onMessage(callback: (message: string) => void): void {
    this.onMessageCallback = callback;
  }

  public onOpen(callback: () => void): void {
    this.onOpenCallback = callback;
  }

  public onClose(callback: () => void): void {
    this.onCloseCallback = callback;
  }

  private startHeartbeat(): void {
    this.heartbeatTimer = setInterval(() => {
      if (this.isOpen && this.socket) {
        this.send(this.heartbeatData);
      }
    }, this.heartbeatInterval);
  }

  private stopHeartbeat(): void {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }

  public close(): void {
    this.isClosed = true;
    this.isOpen = false;
    if (this.socket) {
      this.socket.close();
    }
    this.stopHeartbeat();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
    }
  }

  public destroy(): void {
    this.isDestroyed = true;
    this.close();
  }
}

export { ReSocket };

4.3 如何使用?

首先簡單寫個 ws 的服務,我的 「Node」 環(huán)境是 20.18.0

創(chuàng)建一個 Socket 的文件夾 vscode  打開執(zhí)行:

npm init -y

生成完畢 package.json 之后,我們安裝 ws

npm i ws

創(chuàng)建 app.js 寫一個簡單服務 :

const WebSocket = require("ws");

// 創(chuàng)建 WebSocket 服務器,監(jiān)聽端口 8080
const wss = new WebSocket.Server({ port8080 });

// 監(jiān)聽客戶端連接
wss.on("connection", (ws) => {
  console.log("客戶端已連接");

  // 監(jiān)聽客戶端發(fā)送的消息
  ws.on("message", (message) => {
    console.log("收到客戶端消息:", message);

    // 向客戶端發(fā)送回復
    ws.send(`服務器回復: ${message}`);
  });

  // 發(fā)送一條歡迎消息給客戶端
  ws.send("歡迎連接 WebSocket 服務器");
});

// 打印服務器地址
console.log("WebSocket 服務器已啟動: ws://localhost:8080");

執(zhí)行運行命令 :

node .\app.js

這里可以先用一個 WebSocket 調試工具試試是否創(chuàng)建成功 這里我是用的是 WebSocket在線測試工具 ,效果如下圖:

圖片

看到歡迎連接的時候,說明我們這個服務已經成功啟動了,接下來就是 Javascript 中如何使用了,創(chuàng)建一個 index.html ,然后引入我們封裝好的

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ws 調試</title>
</head>
<script src="./socket.js"></script>
<script>
    var ws = new ReSocket('ws://localhost:8080');

    ws.connect()
    ws.onMessage((res) => {
        console.log('onMessage', res)
    })
</script>

<body>

</body>

</html>

打開瀏覽器之后在控制臺中看日志,如圖:

圖片

在網絡中我們需要在這里看:

圖片

到這里,如果你跟著做了一遍,你已經掌握了,如果感覺現(xiàn)在沒時間,可以收藏,點贊,留個標記,畢竟收藏等于學會了??????

五、我的痛點如何處理

其實我的封裝對于很多瀏覽器都是可以跑的,如果你復制去跑不了,那就人跑,明白我的意思吧?好了,其實這個封裝,沒有一些特殊兼容,比如:

  • 瀏覽器兼容性,某些瀏覽器支持不完整,可能就要降級處理了,具體某些說的是哪個瀏覽器,大家心里都知道
  • 代理和網絡環(huán)境問題,某些企業(yè)網絡或代理服務器會攔截或限制 WebSocket 流量
  • 網絡狀態(tài)檢測,在網絡斷開但沒有觸發(fā) oncloseonerror 時,可能無法及時重新連接
  • 心跳包超時檢測,如果服務器沒有正確響應心跳包,客戶端可能無法及時發(fā)現(xiàn)連接異常
  • 大數(shù)據傳輸和分片處理,發(fā)送大數(shù)據包可能導致超時或失敗
  • 瀏覽器生命周期事件,在瀏覽器后臺或移動設備息屏時,WebSocket 可能被掛起或斷開??? 我的問題就是在這里

等等...

所以,需要各位根據自己使用場景,簡單的需求基本上還是可以用的,如果場景涵蓋比較多,這時候就可以優(yōu)先考慮三方庫使用

「瀏覽器生命周期事件:」 當我在移動設備息屏時,我的 WebSocket 確實不會觸發(fā)心跳,然后后端就給我掛了,那么我們?yōu)g覽器其實提供了一個 visibilitychange 給我們使用,可以這樣寫:

// 頁面可見性監(jiān)聽
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible") {
    console.log("頁面可見,嘗試恢復 WebSocket 連接...");
    if (!socket.isConnect) {
      socket.connect(); // 頁面可見時嘗試恢復連接
    }
  } else {
    console.log("頁面隱藏,關閉 WebSocket 連接...");
    socket.close(); // 頁面隱藏時關閉連接以節(jié)省資源
  }
});

其實,這個我感覺還不太滿足我,所以我又添加了一個定時任務來執(zhí)行檢驗,代碼如下:

// 頁面可見性監(jiān)聽
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible") {
    console.log("頁面可見,嘗試恢復 WebSocket 連接...");
    if (!socket.isConnect) {
      socket.connect();
    }
    lastActiveTime = Date.now(); // 更新最近活動時間
  } else {
    console.log("頁面隱藏,關閉 WebSocket 連接...");
    socket.close();
  }
});

// 定時任務 - 檢測 WebSocket 狀態(tài)及頁面活躍度
const startCheckInterval = () => {
  checkInterval = setInterval(() => {
    const now = Date.now();

    // 檢測 WebSocket 是否斷開,嘗試重連
    if (!socket.isConnect) {
      console.log("WebSocket 未連接,嘗試重連...");
      socket.connect();
    }

    // 檢測最近活動時間,判斷頁面是否處于非活躍狀態(tài)
    if (now - lastActiveTime > 10000) { // 超過10秒未活動
      console.log("檢測到頁面可能處于非活躍狀態(tài)!");
      // 此處可執(zhí)行其他恢復或提醒操作
    }
  }, 5000); // 每5秒檢查一次
};

// 清理定時任務
const clearCheckInterval = () => {
  if (checkInterval) {
    clearInterval(checkInterval);
    checkInterval = null;
  }
};

// 初始化定時任務
startCheckInterval();

// 頁面銷毀的時候調用 clearCheckInterval 清理

結語

很久沒有更新了,狠狠的寫了3000多字,希望這篇文章還是對讀者們有幫助。最近也是經歷了裁員,和找工作一系列的事情,小小吐槽以下,就業(yè)環(huán)境不容樂觀,但是基本上看見這篇文章的讀者,都是熱愛技術的,多學點知識基本上儲備量上去了,面試還是很容易通過的。

「最后,看到此刻的你,祝你工作順利,生活愉快!」

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多