SilentCloseに対する対応について

概要

WebSocketを利用した場合、長時間接続していると知らない内に接続がきれていることがあります。
詳細はこのあたりが詳しいです。
僕の環境でもSilentCloseに多分なったので事象と対応をまとめておこうと思います。

SilentCloseがおこるとどうなるか

SilentCloseが発生した場合、上記のリンクにもありますが以下のようなことが発生します。

  • JavaScriptのonerror, oncloseは発生しない
  • サーバ側でのonerror, oncloseも発生しない
  • サーバ側でnetstatすると接続はESTABLISHEDになっている
SilentCloseの問題点

これが発生すると実際にメッセージの送受信ができないこと以外はすべて正常だと認識されます。
僕の環境だとサーバからメッセージを送ってもなにもエラーが発生しませんでした。
なので実際にメッセージが送れなくなってからメッセージはおくれない、システムは気づけないという大きな問題が発生してしまいます。

どう解決するか

上記のリンクを参考にすると以下のような解決策があるようです。

  • 一定時間で接続をリセットする
  • 定期的にPing-Pongリクエストのやり取りを行う

接続をリセットする場合ある一定時間で接続がきれてから再接続するまでメッセージの送受信ができないのが難点です。どのNATやFireWallがすべて同一の時間できってくれるならいいですが一概にもそうはいえないのかと思います。(確証はないですが。。。)

ということで僕の環境では定期的にPing-Pongで実装するようにしました。

ただ、小松さんの発言の通りkeep-aliveで解消されてJavaScriptレベルで意識しなくてよくなるのが一番ですね。

どう実装したか

まずは、Client側でPing-Pong管理の配列を用意します。

var heartbeats = [];

次にOnOpenの際に定期的にメッセージ送受信するsetIntervalを定義します。

var socket = new WebSocket(url);
socket.onopen = function() {
  setInterval(function() {
    if (heartbeats.length() >= 2) {
      reconnect();                  // 前回のPingリクエストがかえってきていない再接続処理を実装する
    }
    current_time = new Date().toString();
    heartbeats.push(current_time); // Queueに追加
    socket.send(current_time);        // Pingリクエスト
  }, 10000)
  // 実際にやりたいOnOpen処理
};

最後にOnMessageでPingのレスポンスを処理する。

socket.onmessage = function(event) {
  var parse_data = event.data;
  ping_data = __indexOf.call(self.heartbeats, parse_data) >= 0; // Pingレスポンスか判断
  if (ping_data) {
    // Queueから取り除く
    return self.heartbeats = self.heartbeats.filter(function(e) {
    return e !== parse_data;
    });
   } else {
     // Pingレスポンスでなければ本来の処理へ
     return self.onmessage(event);
   }
};