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); } };