KCPベースのリアルタイムゲーム通信の事例研究
リアルタイムオンラインゲームでは、プレイヤーの入力がサーバーに伝送され、その結果が返ってくるまでの遅延時間が重要である。TCPは信頼性と転送順序を保証するが、消失したデータを待っている間に後ろに到着したデータの伝送も遅れることがある。UDPは転送順序と到着を保証しないため、信頼性が必要なメッセージを扱うには、これを補完する仕組みが必要である。
KCPは主にUDP上で再送と順序制御を提供するプロトコルである。この記事では、KCPが遅延時間を短縮する原理を調べ、ゲーム通信の事例をもとに接続・認証のプロセスとメッセージ構造を整理する。認証手順とアプリケーションメッセージの形式は、実装によって異なる場合がある。
KCPはどのように遅延時間を短縮するのか?
KCPは、受信確認と再送を使用するARQ方式で信頼性のある転送を実装する。再送ポリシーと更新周期を調整して消失後の待ち時間を減らすことができ、設定によって帯域幅とCPU使用量が増加する場合がある。
実際のパフォーマンスは、ネットワークの状態と設定によって異なる。
- 選択적 재전송: 受信確認情報をもとに消失したデータを選んで再送する。TCPもSACKを使用すれば選択的再送が可能であるため、これをKCP独自の機能と見なすことはできない。
- 再送待ち時間の調整: 高速モードでは、消失が繰り返されたときに再送待ち時間が増加する幅を縮小し、復旧を早める。
- 高速再送: 送信側は、後ろのパケットのACKが到着するのを見て、前のパケットの消失を推測する。設定された条件を満たすと、タイムアウト前に再送する。
- 短い更新周期: 更新・転送周期を短縮し、ACKや再送データが送信されるまでの待ち時間を減らすことができる。実際の転送タイミングは、アプリケーションの呼び出し周期にも影響を受ける。
KCPも順序通りにデータを伝達するため、前のデータが消失すると後ろのデータが待機する現象は残る。核心は、この待機をなくすことではなく、消失したデータをより迅速に復旧することにある。
接続と認証のプロセス
ゲームサーバーと通信する前には、アカウント認証と接続するサーバーの選択が必要である。以下は、ウェブ認証で受け取ったトークンをゲームサーバーで確認する流れを単純化した例である。
この例で、ウェブサーバーはアカウントを認証し、ゲームサーバーはトークンを検証してゲームセッションを開始する。2つの段階の役割を区別すると、ログイン以降の通信の流れを理解しやすい。
KCP自体には暗号化やユーザー認証機能がない。セキュリティが必要な場合は、個別のセキュリティ層を構築する必要がある。Protobufも同様にデータをシリアライズする形式であるため、Protobufで鍵やトークンを表現するだけでは安全な伝送は保証されない。
KCPヘッダーとアプリケーションメッセージ
KCPセグメントは24バイトのヘッダーとデータ領域で構成される。下の表の conv から len までがKCPヘッダーであり、データ領域内の PackLen、MsgType、CmdId などは、この事例で扱うアプリケーションメッセージの形式である。この内部形式はKCPの共通規格ではない。
表は1マスを1バイトで表す。アプリケーションメッセージが大きい場合、複数のKCPセグメントに分割される可能性があるため、内部の表は再組み立てされたメッセージの構造を示す例として読み取ればよい。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ||||||||||||||||||||||||||||||||
| conv | cmd | frg | wnd | ||||||||||||||||||||||||||||||||||||
| ts | sn | ||||||||||||||||||||||||||||||||||||||
| una | len | ||||||||||||||||||||||||||||||||||||||
Data: アプリケーションメッセージの例
|
|||||||||||||||||||||||||||||||||||||||
KCPヘッダーで、conv は会話識別子、cmd はコマンドの種類、frg は分割情報、wnd は受信ウィンドウサイズである。ts はタイムスタンプ、sn はセグメント順序番号、una は次に受信する順序番号、len はデータ領域の長さを表す。
アプリケーションメッセージのフィールドは次のように解釈できる。長さとチェックサムの計算範囲は、各実装の規則を確認する必要がある。
PackLen: メッセージの長さを表すフィールド。MsgType: メッセージの種類とフラグ情報。seqNo: アプリケーションメッセージの順序番号。rpcId: リモートプロシージャコールの要求と応答を結びつける識別子。CmdId: ペイロードを解釈するメッセージの種類を表す識別子。crc32: エラー検出用のチェックサム。暗号学的認証の代わりにはならない。Payload: シリアライズされたメッセージデータ。暗号化を適用した場合は復号化した後に解釈する。
Protobufによるペイロードの解釈
この事例のペイロードは、Protocol Buffers(Protobuf)でシリアライズされたデータを格納する。Protobufは構造化データをバイナリで表現する形式であり、フィールド名の代わりに番号を使用する。ゲームメッセージを簡潔に表現できるが、サイズと処理性能はデータ構造と実装によって異なる。
まず .proto ファイルにメッセージ構造を定義し、protoc で使用する言語に合ったコードを生成する。送信側はこのコードでデータをシリアライズし、受信側は同じメッセージ定義を使用してデシリアライズする。
例えば、次のように文字列2つを格納するメッセージを定義できる。
アプリケーションで CmdId とメッセージ種類の対応関係を次のように定めたと仮定する。この番号は、Protobufが自動的に付与する値ではなく、アプリケーションが定めた規則である。
message が Hello World!、name が AZA.GG であるメッセージをシリアライズすると、次のバイト列になる。以下のペイロードは暗号化前のデータである。
各文字列の前には、フィールド番号とデータ型を含むタグ、文字列のバイト長が付く。0a と 12 はそれぞれ1番目と2番目のフィールドのタグであり、0c と 06 は文字列の長さである12と6を表す。
受信側は CmdId が 11 であることを確認し、ペイロードを HelloWorld として解釈する。このように、KCPは転送と再送を、アプリケーションヘッダーはメッセージの区別を、Protobufはデータ表現を担当する。