8. ダイレクトストリーム

ダイレクトストリームは、単純な送受信の機能を提供します。

8.1. 有効化および無効化

ダイレクトストリームによる送受信を行う前には、 DirectStream::Enable() を呼び出す必要があります。 DirectStream::Enableを呼び出すことで、ダイレクトストリームを使用しての送信および受信を行うことができるようになります。 有効化するまでに受信したパケットはライブラリ内で破棄されます。

ダイレクトストリームが不要となった場合は、 DirectStream::Disable() を呼び出すことで送信および受信を無効にすることができます。 ただし、無効化しても、すでに送信用キューに蓄積された送信データがあれば引き続き送信され続けます。 また、受信においては後述のReliable通信に対する到達確認の応答がライブラリ内部で行われます。

8.2. 通信タイプ

ダイレクトストリームでは、2つの通信タイプをサポートします。各通信タイプの特徴を以下に示します。

いずれもUDP上に実装されたPRUDPプロトコルを利用しており、信頼性のある・なしを選択することができます。

8.2.1. Unreliable通信

到達保証がされない通信です。

到達保証が必要ない場合に使用することで、効率的に通信することができます。

標準設定では、重複パケットや、順序が入れ替わったパケットも到着します。

パケットが届く順番がおかしくなってしまう、同じパケットが何度か届く問題を解消し、 パケットの順番を保証する場合には、以下の設定を行ってください。 この設定では、順番がおかしくなったパケットは、古い方のパケットが破棄されます。

RootTransport::EnableDropDuplicateReorderingUnreliablePackets(true);

8.2.2. Reliable通信

到達保証がされる通信です。

パケットが送信された順序で受信されることも保証されます。 また、欠損したパケットは再送されて、重複パケットは破棄されます。

パケット転送性能やロス発生状況によっては、詰まって送信エラーに可能性があるので、 高頻度の通信を行う際には、エラーハンドリングの上、リトライ処理が必要となります。

8.3. 送信処理

8.3.1. 特定ステーション宛て送信(呼び出しコンテキスト指定なし)

セッション参加中の特定のステーションにパケットを送信する場合は、 DirectStream::Send()を使用します。 送信用キューに送信データが追加されれば成功を返し、追加できなかった場合はエラー要因を返します。 送信用キューに追加された送信データは、 Scheduler::Dispatch()または Scheduler::DispatchAll()呼び出し時に送信されます。

DirectStream::Send()では、すべての通信タイプを指定できます。

呼び出しコンテキスト指定なしのUnreliable用送信関数として、 DirectStream::SendUnreliable()があります。

8.3.2. 特定ステーション宛て送信(呼び出しコンテキスト指定あり)

DirectStream::Send()には、呼び出しコンテキストを指定する同名メソッドが用意されています。 送信用キューに送信データが追加されればtrue, 追加できなかった場合はfalseを返します。

呼び出しコンテキストを使用することで、Reliable通信におけるパケットの到達確認を非同期処理で受け取ることができます。 到達確認を明示的にゲームアプリケーション側で行いたい場合に使用します。 到達確認がされると呼び出しコンテキストはCallSuccessの状態になります。 なお、 DirectStream::Disable()を呼び出した場合は、到達確認の非同期処理が停止するため、ゲームアプリケーション側で呼び出しコンテキストをキャンセルしてください。

Reliable通信以外では、到達確認が不要のため、送信用キューへの送信データの追加の結果が呼び出しコンテキストに返されます。

8.3.3. 全ステーション宛て送信

セッション参加中の全ステーションにUnreliableパケットを送信する場合は、 DirectStream::SendUnreliableAll()を使用します。 全ステーションに対して送信用キューに送信データが追加されれば成功を返し、追加できなかった場合はエラー要因を返します。

全ステーション宛て送信には、呼び出しコンテキスト指定はありません。

8.3.4. 複数本のReliable通信

標準設定ですと、Reliableキューが単一キューであるため、ネットワーク環境が悪いと、 Head-of-line blocking(行頭ブロッキング)により、後続のデータがいつまでも到着しないという 問題が生じます。

複数本の個別Reliable(個々のReliableをサブストリームと呼ぶ)を利用することにより、 独立した再送制御が行われます。サブストリーム間では到着順序性を保証しないかわりに、 重要なデータを再送によるつまりを回避して送信することが可能となります。 NetZの初期化時に StreamSettings::SetMaxUserReliableSubStreams()で利用するユーザー用サブストリーム数を指定することにより、 初期にあるシステム共有サブストリーム(IDは、 SubStreamIDDefine::SYSTEM)と合わせた最大256本の個別Reliable通信ができます。

SubStreamIDを指定したReliable通信は、 DirectStream::SendReliable()によって行います。

システムと共用サブストリームを詰まらせると、フォルトリカバリ処理に遅延が生じるので、ユーザー用サブストリームを最低 1本用いて、そのサブストリーム経由でデータのやり取りをすることを推奨します。

以下は、SubStreamIDが0から2のサブストリームを利用可能とする例です。SubStreamID 0(= SubStreamIDDefine::SYSTEM)は、システムと共有で、 DirectStream::Send()で、Reliable通信を行った場合と同じです。

Code 8.1 複数サブストリーム利用時の初期化例
NetZ                *poNetZ = qNew NetZ;
Stream::GetSettings().SetMaxUserReliableSubStreams(2);

....
//JoinSession()やCreateSession()の処理

通信相手と StreamSettings::SetMaxUserReliableSubStreams()の値が異なった場合には、ネゴシエーションにより小さい方に最大サブストリームIDが決定されます。 送信時に、最大サブストリームIDを超えたIDを指定した場合には、QERROR(Transport, InvalidSubStreamID)が発生します。

サンプルコードについては AutoMatchサンプル を参照してください。

注意

通信終了直前にデータを送りたいという場合には、システムと共有の SubStreamIDDefine::SYSTEM (=0)を利用するようにしてください。 場合によっては、通信終了までにデータが到着しない可能性があります。

過大なサブストリーム数は、メモリを消費しますので、適正な本数を利用するようにしてください。

また、NEXのReliable通信は再送制御は行いますが、輻輳制御を行いません。そのため、複数のサブストリームに並列してデータを詰めるすぎると、再送が頻発してしまい、ロス率が高くなってしまいます。大量のデータを送信するときには、一本のサブストリームを使うようにしてください。

8.3.5. 最大送信サイズについて

Unreliable通信での送信可能サイズは、MTUによって制限されます。 MTUにはNEXのヘッダが含まれるため、送信サイズはNetZのネットワークトポロジーが P2P ネットワークの場合は1300Bytes以下、 それ以外の場合は、1250Byte以下となるようにしてください。

Reliable通信では、送信時に指定するバッファサイズを最大32768Byteに指定できますが、MTUを超過した分は分割して送信されます。

8.3.6. Reliable通信の最大送信バッファ数

Reliable通信の送信データが最大送信バッファ数にまで達していた場合、送信処理はReliableSendBufferFullのエラーを返します。

ReliableSendBufferFullは一時的に送信バッファの上限に達したことを示すエラーであるため、 ReliableSendBufferFullが発生した場合は、ディスパッチを行い時間をおいてから再度送信を行ってください。 Reliable通信の送信バッファには、ダイレクトストリーム経由でのパケットの他にNEX内部で使用されるReliable通信のパケットも含まれます。

DirectStream::GetPendingReliableBufferNum() を使用することで、Reliable通信における送信バッファに滞留しているパケット数を取得できます。 DirectStream::GetPendingReliableBufferNum() で取得した値には、ダイレクトストリームを使用したReliable通信以外に、NEX内部で使用されるReliable通信のパケットも含まれます。

DirectStream::SetMaxReliableSendBufferNum() によって、Reliable通信の最大送信バッファ数を変えることができます。 NEX内部で使用されるReliable通信のパケット量は少ないため、通常デフォルト値で十分です。また、96より大きい値を設定しないでください。 現在の設定値は DirectStream::GetMaxReliableSendBufferNum() で取得できます。

P2P 通信の一部を除くパケットのバッファは、パケットバッファマネージャーによって固定長のメモリプールで管理されます。 パケットバッファの空きが十分にある間は、Reliable通信の最大送信バッファ数に達するまでエラーとなりません。 パケットバッファの空きが不足した場合は、 QERROR(Transport, PacketBufferFull) が発生します。 PacketBufferFullが発生した場合は、ディスパッチを行い時間をおいてから再度送信を行ってください。 PacketBufferFullが高頻度で発生する場合はパケットバッファを増やす対策も行ってください。 パケットバッファについては、 「メモリ管理」のパケットバッファのメモリ管理 を参照してください。

8.3.7. 送信処理におけるエラー要因

次の場合、送信に失敗し、エラーを返します。

  • NetZオブジェクトが生成完了する前に、または破壊中に呼び出したとき。
  • 送信データがNULLのとき。
  • 送信サイズが送信可能サイズを超えるとき。( 8.3.5. を参照)
  • Reliable通信時、Reliable送信バッファが一杯のとき。( 8.3.6. を参照)
  • 自ステーション宛のとき。
  • 自ステーションが P2P セッションに参加していないとき。
  • 送信先のステーションが P2P セッションに参加していないとき。
  • DirectStream::SendAll()でReliable通信を指定したとき。
  • DirectStream::SendReliable()で、最大サブストリームIDを超えたSubStreamIDを指定したとき。
  • パケットバッファが枯渇した場合(PacketBufferFullエラーを返します)

また、呼び出しコンテキスト指定で送信をしている場合は、宛先ステーションとの通信ができなくなった場合に呼び出しコンテキストにエラーが返ります。

Reliable通信を指定した送信では、送信バッファに空きがない場合にReliableSendBufferFullエラーを返します。 これは一時的に送信バッファの上限に達したことを示すエラーであるため、 ReliableSendBufferFullが発生した場合は、ディスパッチを行い時間をおいてから再度送信を行ってください。

PacketBufferFullはパケットバッファが不足している場合に発生します。 これは一時的にパケットバッファが不足していることを示すエラーであるため、 PacketBufferFullが発生した場合は、ディスパッチを行い時間をおいてから必要に応じて再度送信を行ってください。 PacketBufferFullが高頻度で発生する場合はパケットバッファを増やす対策も行ってください。 パケットバッファについては、 「メモリ管理」のパケットバッファのメモリ管理 を参照してください。

8.4. 受信処理

Scheduler::Dispatch()または Scheduler::DispatchAll()呼び出し時に、受信済みパケットがダイレクトストリームの受信バッファに蓄積されます。 受信バッファに蓄積された受信データは、 DirectStream::GetReceivedData()を呼び出すことで取得します。 受信データから、DirectStream::Data 構造体により送信者のStationID、通信タイプ、パケットバッファ、SubStreamIDを取得することができます。

DirectStream::GetReceivedData()を呼び出すまで、ダイレクトストリームの受信バッファはクリアされません。 そのため、 Scheduler::Dispatch()または Scheduler::DispatchAll()の呼び出しによって、ダイレクトストリームの受信バッファに受信データが蓄積され過ぎないように、 DirectStream::GetReceivedData()は、 Scheduler::Dispatch()または Scheduler::DispatchAll()の呼び出しとともに定期的に呼び出してください。 受信バッファの上限を超えた場合、QERROR(Transport, NoBuffer)のフェイタルエラーが発生します。

フェイタルエラー発生時のエラー処理については、 5. を参照してください。

受信あふれが起こる場合は、他のStationからの送信を減らすように設計するか、 Scheduler::Dispatch() または Scheduler::DispatchAll() とともに DirectStream::GetReceivedData() の呼び出し頻度を上げてください。