banner
 Sayyiku

Sayyiku

Chaos is a ladder
telegram
twitter

WebSocket ハートビート再接続メカニズム

WebSocket は、クライアントとサーバー間のデータ交換をより簡単にするネットワーク通信プロトコルです。最近のプロジェクトで WebSocket を使用してシンプルなオンラインチャット機能を実装しましたが、ここではハートビート再接続のメカニズムを探求します。

WebSocket#

WebSocket は、サーバーがクライアントにデータをプッシュすることを可能にします。以前は、多くのウェブサイトがプッシュ技術を実現するためにポーリング技術を使用していましたが、効率が悪く、多くの帯域幅などのリソースを浪費していました。HTML5 は WebSocket プロトコルを定義しており、サーバーリソースと帯域幅をより良く節約し、よりリアルタイムでの通信を可能にします。

WebSocket の利点:

  • 少ない制御オーバーヘッド
  • より強いリアルタイム性
  • 接続状態の維持
  • より良いバイナリサポート
  • 拡張をサポート可能
  • より良い圧縮効果

WebSocket の最大の利点は、フロントエンドとバックエンドのメッセージの長い接続を維持できることですが、特定の状況では、長い接続が無効になっても迅速なフィードバックが得られず、フロントエンドは接続が切断されたことを知りません。たとえば、ユーザーのネットワークが切断されても、WebSocket のイベント関数はトリガーされません。この時にメッセージを送信すると、メッセージは送信できず、ブラウザはすぐに、または一定の短時間後(異なるブラウザやブラウザのバージョンによって異なる場合があります)に onclose 関数をトリガーします。

このような状況を避け、接続の安定性を確保するために、フロントエンドは一定の最適化処理を行う必要があります。一般的に採用される方法はハートビート再接続です。フロントエンドは一定の間隔でハートビートパケットを送信し、バックエンドはハートビートパケットを受信した後、接続が正常であることを知らせる応答パケットを返します。一定の時間内にメッセージを受信できない場合、接続が切断されたと見なし、フロントエンドは再接続を行います。

ハートビート再接続#

上記の分析から、ハートビート再接続を実現するための鍵は、タイムリーにハートビートメッセージを送信し、応答メッセージを検出して再接続を判断することです。したがって、まず 4 つの小目標を設定します:

  • 一定の間隔でハートビートパケットを送信できる
  • 接続エラーまたは切断時に自動的に再接続できる
  • 一定の時間間隔内にメッセージを受信できない場合、切断と見なし、自動的に再接続する
  • ハートビートメッセージをカスタマイズし、最大再接続回数を設定できる

0x01 初期化#

再利用を容易にするため、WebSocket 管理をツールクラス WebsocketHB にカプセル化し、設定オブジェクトを渡すことでハートビート再接続メカニズムをカスタマイズすることにします。

class WebsocketHB {
  constructor({
    url, // 接続クライアントアドレス
    pingTimeout = 8000, // ハートビートパケット送信間隔、デフォルト 8000 ミリ秒
    pongTimeout = 15000, // 最長未受信メッセージの間隔、デフォルト 15000 ミリ秒
    reconnectTimeout = 4000, // 各再接続間隔
    reconnectLimit = 15, // 最大再接続回数
    pingMsg // ハートビートパケットのメッセージ内容
  }) {
    // 初期設定
    this.url = url
    this.pingTimeout = pingTimeout
    this.pongTimeout = pongTimeout
    this.reconnectTimeout = reconnectTimeout
    this.reconnectLimit = reconnectLimit
    this.pingMsg = pingMsg

    this.ws = null
    this.createWebSocket()
  }

  // WS を作成
  createWebSocket() {
    try {
      this.ws = new WebSocket(this.url)
      this.ws.onclose = () => {
        this.onclose()
      }
      this.ws.onerror = () => {
        this.onerror()
      }
      this.ws.onopen = () => {
        this.onopen()
      }
      this.ws.onmessage = event => {
        this.onmessage(event)
      }
    } catch (error) {
      console.error('websocket 接続失敗!', error)
      throw error
    }
  }

  // メッセージを送信
  send(msg) {
    this.ws.send(msg)
  }
}

使用方法:

const ws = new WebsocketHB({
  url: 'ws://xxx'
})

ws.onopen = () => {
  console.log('接続成功')
}
ws.onmessage = e => {
  console.log(`onmessage: ${e.data}`)
}
ws.onerror = () => {
  console.log('接続エラー')
}
ws.onclose = () => {
  console.log('接続切断')
}
ws.send('こんにちは、chanshiyu!')

0x02 ハートビートパケットと再接続の送信#

ここでは setTimeout を使用して setInterval を模擬し、ハートビートパケットを定期的に送信し、タイマーキューのブロッキングを避け、最大再接続回数を制限します。再接続を行うたびにロックをかけ、無効な再接続を避けることに注意が必要です。また、メッセージを受信するたびに、最長間隔メッセージ再接続タイマーをクリアします。メッセージを受信できる場合は接続が正常であることを示し、再接続は必要ありません。

class WebsocketHB {
  constructor() {
    // ...
    // インスタンス変数
    this.ws = null
    this.pingTimer = null // ハートビートパケットタイマー
    this.pongTimer = null // メッセージ受信タイマー
    this.reconnectTimer = null // 再接続タイマー
    this.reconnectCount = 0 // 現在の再接続回数
    this.lockReconnect = false // 再接続ロック
    this.lockReconnectTask = false // 再接続タスクキューのロック

    this.createWebSocket()
  }

  createWebSocket() {
    // ...
    this.ws = new WebSocket(this.url)
    this.ws.onclose = () => {
      this.onclose()
      this.readyReconnect()
    }
    this.ws.onerror = () => {
      this.onerror()
      this.readyReconnect()
    }
    this.ws.onopen = () => {
      this.onopen()

      this.clearAllTimer()
      this.heartBeat()
      this.reconnectCount = 0
      // ロック解除、再接続可能
      this.lockReconnect = false
    }
    this.ws.onmessage = event => {
      this.onmessage(event)

      // タイムアウトタイマー
      clearTimeout(this.pongTimer)
      this.pongTimer = setTimeout(() => {
        this.readyReconnect()
      }, this.pongTimeout)
    }
  }

  // ハートビートパケットを送信
  heartBeat() {
    this.pingTimer = setTimeout(() => {
      this.send(this.pingMsg)
      this.heartBeat()
    }, this.pingTimeout)
  }

  // 再接続の準備
  readyReconnect() {
    // 循環再接続を避けるため、再接続タスクが進行中の際は再接続しない
    if (this.lockReconnectTask) return
    this.lockReconnectTask = true
    this.clearAllTimer()
    this.reconnect()
  }

  // 再接続
  reconnect() {
    if (this.lockReconnect) return
    if (this.reconnectCount > this.reconnectLimit) return

    // ロック、再接続禁止
    this.lockReconnect = true
    this.reconnectCount += 1
    this.createWebSocket()
    this.reconnectTimer = setTimeout(() => {
      // ロック解除、再接続可能
      this.lockReconnect = false
      this.reconnect()
    }, this.reconnectTimeout)
  }}

  // すべてのタイマーをクリア
  clearAllTimer() {
    clearTimeout(this.pingTimer)
    clearTimeout(this.pongTimer)
    clearTimeout(this.reconnectTimer)
  }
}

0x03 インスタンスの破棄#

最後に、ツールクラスに破棄メソッドを追加し、インスタンスが破棄される際に再接続禁止ロックを設定し、破棄中に再接続を試みないようにし、すべてのタイマーをクリアし、長い接続を閉じます。

class WebsocketHB {
  // 再接続
  reconnect() {
    if (this.forbidReconnect) return
    //...
  }

  // ws を破棄
  destroyed() {
    // 手動で接続を閉じた場合、再接続しない
    this.forbidReconnect = true
    this.clearAllTimer()
    this.ws && this.ws.close()
  }
}

npm パッケージの封装#

ここまでで、WebSocket ツールクラスのハートビート再接続機能は基本的に封装が完了しました。使用を開始することができます。最終的な完成コードは GitHub にアップロードしました。詳細は websocket-heartbeat をご覧ください。また、今後のプロジェクトで使用できるように npm にも封装してアップロードしました。興味がある方は websockethb を試してみてください。

インストール#

npm install websockethb

インポートと使用#

import WebsocketHB from 'websockethb'

const ws = new WebsocketHB({
  url: 'ws://xxx'
})

ws.onopen = () => {
  console.log('接続成功')
}
ws.onmessage = e => {
  console.log(`onmessage: ${e.data}`)
}
ws.onerror = () => {
  console.log('接続エラー')
}
ws.onclose = () => {
  console.log('接続切断')
}

プロパティ#

プロパティ必須デフォルト値説明
urltruestringnonewebsocket サーバーインターフェースアドレス
pingTimeoutfalsenumber8000ハートビートパケット送信間隔
pongTimeoutfalsenumber1500015 秒内にバックエンドメッセージを受信しない場合、接続が切断されたと見なされます
reconnectTimeoutfalsenumber4000再接続を試みる間隔時間
reconnectLimitfalsenumber15再接続試行回数
pingMsgfalsestring"heartbeat"ハートビートパケットのメッセージ
const opts = {
  url: 'ws://xxx',
  pingTimeout: 8000, // ハートビートパケット送信間隔、デフォルト 8000 ミリ秒
  pongTimeout: 15000, // 最長未受信メッセージの間隔、デフォルト 15000 ミリ秒
  reconnectTimeout: 4000, // 各再接続間隔
  reconnectLimit: 15, // 最大再接続回数
  pingMsg: 'heartbeat' // ハートビートパケットのメッセージ内容
}
const ws = new WebsocketHB(opts)

メッセージを送信#

ws.send('こんにちは、世界')

接続を切断#

ws.destroyed()

Just enjoy it ฅ●ω●ฅ

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。