概要
前回の記事で 「TCP コネクションの切断をまたいで到達保証・順序保証をつける」 という機能があることを書きましたが今回はその話をしたいと思います.
背景
前回書いた通り通信には主に TCP を使用しているため, TCP コネクションが確立されている限り通信の到達と順序の保証はされています.
しかし, 今回のサーバーのターゲットはモバイル端末のため, 電波状況やOS/アプリの挙動により短時間のTCPコネクション断は十分に考えられます.
ゲームの通信を設計する上でこれらを加味した上で処理を継続するように書くのは非常に面倒なため, 低レイヤーで吸収する必要があります.
到達を保証する
返事とリトライ
通信は基本的に失敗します.
原因はハード・ソフトに限らずあらゆる事情により送った通信が届かないことは常に起こります.
手紙と一緒ですね. メッセージを送っただけでは届いたのかわからないのです.
ではどうするかといえば, 送った相手から返事をもらえば届いた事がわかります.
返事がこなかったら届いてないはずなので, 返事が来るまで送り続けます. (リトライ)
(送信側が諦めた時が送信失敗です)
問題
この方法は一見問題ないように見えますがいくつか問題があります
受信側に複数の同じメッセージが届く可能性がある
通信は常に失敗する可能性があるので, 当然行きが成功しても返事が届かないという事がありえます.
返信がないと送信側は成功を確認できないため, 自分の送信が届かなかったのか, 返信が届かなかったのかを区別する事ができません.
なので受信側にはメッセージが複数回到達する事を前提としたロジックが求められます.
順序を保証すると速度が出ない
1通のメッセージを送るたびに返事を待っていると送信速度は極めて劣化します(送信側は返信が来るまで待つしかできません)
そこで返事を待たずに送り続け, 返事が返ってこなかったメッセージだけ再送するようにすると今度はメッセージの順序が一定になりません.
この例では Message A, Message B の順序で到達する事が期待されているのに実際に到達する順序は Message B, Message A となって到達しています.
到達順序を保証
当初は前述の方法(のアレンジ)を採用して到達保証は行っていましたが順序保証をしていませんでした.
しかし順序保証が無いとゲームの通信処理で色々問題が出たため, 順序保証を行う事にしました.
カウンター
順序を保証するという事は, メッセージの順番を管理する必要があります.
そのため順序保証をつけるメッセージ全てに1ずつくりあがる数字を付与しました.
送信者は必ず順番にメッセージを送ってくるはずなので, 受信者は受信したメッセージ番号が飛んだら最後に正しく受信できた番号を送信側に伝えます.
それを受け取った送信者は受信が確認できたメッセージの次の番号から再送信を開始します.
この場合, 1つ1つのメッセージに対して返信をするのは無駄なので問題があった時だけ返信するようになります.
全てのメッセージを受信できない状態でエラーを検知できない
しかし個別のメッセージの受信応答をなくすと今度は全てのメッセージの到達が失敗している場合にどちらも失敗している事に気づけません.
そのため受信側は何もメッセージの受信がなくても定期的に最後に受信成功した番号を送信します.
これで途中が抜けても, 全ての送信が到達に失敗しても検知する事が出来るようになりました.
また, 定期的に必ず最新の受信番号が来るはずなので一定以上受信番号の通知がこなかったらこれもエラーとして扱います.
複数回受信
ちなみに当然, 送信側が再送信を行っている以上, 受信側は同じメッセージを複数回受け取りますが受信側が最後に受信したメッセージ番号の管理を正しく行っていれば2重に受け取ったメッセージなのかを判別する事ができるのでこのプロトコルの内部で2重に処理する事を防止する事が可能になります.
まとめ
通信において到達保証・順序保証を実現する手段を紹介してみました.
TCP は内部で近い事をもっと高度に行ってくれているため TCP の上で通信する限りこれらは意識する必要がありません.
UDP を使用したり, TCP コネクション切断をまたいで保証をつけようとするとこう言った事を考慮する必要が出てきます.
TCP の実装などを見るとすごく参考になるのでこう言った事に興味が有る場合はオススメです.
(MQTTのQoSなども面白いです)