実際の WebSocket クライアントは、しょっちゅう接続が切れます。スマートフォンがロックされる、Wi-Fi がモバイル回線に切り替わる、トンネルが一瞬途切れる、キャプティブポータルが割り込んでくる、といった具合です。接続が戻ってきたとき、クライアントが離れている間に配信されたメッセージは失われています。「スマートフォンをロックしている間にメッセージを取りこぼした」というのは、ユーザーが真っ先に気づく失敗です。そして、よくある回避策 — 再接続のたびに状態全体を取り直す — は無駄が多く、間違えやすいものです。
sgcWebSockets はこれを直接解決するようになりました。サーバーで履歴を有効にすると、再接続したクライアントは取りこぼしたメッセージをオフセット単位で正確に再送してもらえます。オプトイン方式で既定では無効なので、有効にするまで既存のデプロイは従来とまったく同じ動作をします。この記事では、その仕組み、設定、そして Delphi と .NET の例を紹介します。
仕組み
履歴を有効にすると、チャネルに配信されたすべてのメッセージには単調増加するオフセットが刻印され、サーバー上のチャネルごとに上限付きのリングバッファに追加されます。クライアントは各チャネルで最後に受信したオフセットを記憶します。再接続して再購読すると、そのオフセットを送り、サーバーはそれより新しいすべてのメッセージをその 1 つの接続に対して再送します — 順序どおりに、重複なしで。
オフセットはクライアントが持つので、管理すべきサーバー側のセッションはありません。リカバリは通常の再購読そのものです。
サーバーで履歴を有効にする
履歴は sgc プロトコルサーバー上の History オプションオブジェクトに存在します。これを有効にし、メッセージ数や経過時間で上限を設けます:
uses
sgcWebSocket, sgcWebSocket_Protocols;
var
oProtocol: TsgcWSPServer_sgc;
begin
oProtocol := TsgcWSPServer_sgc.Create(nil);
oProtocol.Server := oServer;
oProtocol.History.Enabled := True;
oProtocol.History.Size := 1000; // keep the last 1000 messages per channel
oProtocol.History.TTLSeconds := 3600; // optional: also drop entries older than 1 hour
end;
Size はチャネルごとのメモリに上限を設けます(かつては上限のなかったキューにも上限を設けます)。TTLSeconds は任意の経過時間制限です。履歴が無効(既定)の場合は、通信路上にオフセットは存在せず、動作の変化もまったくありません。
クライアントでの自動リカバリ
クライアントが代わりにオフセットを追跡してくれます。再接続後は、単純な Subscribe がそのチャネルで取りこぼしたものを再送するようサーバーへ自動的に依頼します — 何も追跡したり渡したりする必要はありません:
// On (re)connect, just resubscribe. The client auto-sends the last offset it saw,
// and the server replays the messages published while the client was offline.
sgcWSPClient_sgc1.Subscribe('news');
// Or request the full retained history explicitly (cursor 0 = everything kept):
sgcWSPClient_sgc1.Subscribe('news', 0);
チャネルごとのオフセットマップは切断をまたいで保持されるので、再接続して再購読するだけで済みます。カーソルを表示したり永続化したい場合は、GetLastOffset('news') がそれを公開します。
.NET
マネージド版は API をそのまま反映しています。サーバープロトコルで履歴を有効にすれば、クライアントは再購読時にリカバリします:
var protocol = new TsgcWSPServer_sgc { Server = server };
protocol.History.Enabled = true;
protocol.History.Size = 1000;
protocol.History.TtlSeconds = 3600;
// Client: a plain resubscribe after reconnect recovers the missed messages.
client.Subscribe("news");
long last = client.GetLastOffset("news");
オプトインで既定では安全
履歴は既定で無効です。無効のとき、配信されるフレームにはオフセットが付かず、配信/購読の経路は以前とバイト単位で同一です — ですから明示的に有効にするまで、アップグレードしても何も変わりません。有効にすると、上限付きのリングがメモリを予測可能に保ちます。
マルチノードクラスタでは、このリリースでの履歴はノードローカルです。同じノードに再接続したクライアントは完全な再送を受け取ります。クラスタ全体で共有される履歴はロードマップにあります。すぐに実行できるデモがライブラリに同梱されています(Delphi では 02.WebSocket_Protocols\15.MessageHistory_Recovery、.NET では samples\HistoryRecoveryDemo)。クライアントをオフラインにし、離れている間に配信し、再接続させて、取りこぼしたメッセージを正確にリカバリする様子を示します。
提供状況
メッセージ履歴と再接続リカバリは、Delphi 7 から 13 まで(Win32/Win64、Linux64、macOS、Android、iOS)、およびマネージド .NET 版にわたって sgc プロトコルで利用できます。sgcWebSockets クライアントが既に備えている WatchDog の自動再接続と自然に組み合わせて使えます。
有効なサブスクリプションをお持ちのお客様は、カスタマーエリアから新しいビルドをダウンロードできます。トライアルユーザーの方は、esegece.com/products/websockets/download で更新版のインストーラを入手できます。
ご質問、ご意見、またはリカバリをアプリに組み込む手助けが必要ですか? お問い合わせください — コードを書いた本人から返信が届きます。
