配信保証
メッセージの順序
Socket.IO は、どの低レベルトランスポートが使用されていても(HTTP ロングポーリングから WebSocket へのアップグレード中でも)、メッセージの順序を保証します。
これは以下のことによって実現されます。
- 基盤となる TCP 接続によって提供される保証
- アップグレードメカニズムの注意深い設計
例
socket.emit("event1");
socket.emit("event2");
socket.emit("event3");
上記の例では、イベントは常に相手側で同じ順序で受信されます(実際に到着する場合、下記参照)。
メッセージの到着
最大1回
デフォルトでは、Socket.IO は 最大1回 の配信保証を提供します。
- イベントの送信中に接続が切断された場合、相手側がそれを受信した保証はなく、再接続時に再試行は行われません。
- 切断されたクライアントは、再接続までイベントをバッファリングします(ただし、前の点は依然として適用されます)。
- サーバーにはそのようなバッファがないため、切断されたクライアントが見逃したイベントは、再接続時にそのクライアントに送信されません。
情報
現時点では、追加の配信保証はアプリケーションで実装する必要があります。
少なくとも1回
クライアントからサーバーへ
クライアント側からは、確認応答とタイムアウトを使用して、少なくとも1回の保証を実現できます。
function emit(socket, event, arg) {
  socket.timeout(2000).emit(event, arg, (err) => {
    if (err) {
      // no ack from the server, let's retry
      emit(socket, event, arg);
    }
  });
}
emit(socket, "foo", "bar");
上記の例では、クライアントは指定された遅延の後、イベントの送信を再試行するため、サーバーは同じイベントを複数回受信する可能性があります。
注意
その場合でも、ユーザーがタブを更新すると、保留中のイベントは失われます。
サーバーからクライアントへ
サーバーから送信されたイベントの場合、追加の配信保証は、次のように実装できます。
- 各イベントに一意のIDを割り当てる
- イベントをデータベースに永続化する
- クライアント側で最後に受信したイベントのオフセットを保存し、再接続時に送信する
例
クライアント
const socket = io({
  auth: {
    offset: undefined
  }
});
socket.on("my-event", ({ id, data }) => {
  // do something with the data, and then update the offset
  socket.auth.offset = id;
});
サーバー
io.on("connection", async (socket) => {
  const offset = socket.handshake.auth.offset;
  if (offset) {
    // this is a reconnection
    for (const event of await fetchMissedEventsFromDatabase(offset)) {
      socket.emit("my-event", event);
    }
  } else {
    // this is a first connection
  }
});
setInterval(async () => {
  const event = {
    id: generateUniqueId(),
    data: new Date().toISOString()
  }
  await persistEventToDatabase(event);
  io.emit("my-event", event);
}, 1000);
不足しているメソッド(fetchMissedEventsFromDatabase()、generateUniqueId()、persistEventToDatabase())の実装はデータベース固有であり、読者の課題として残されます。
参考資料
- socket.auth(クライアント)
- socket.handshake(サーバー)