3. フォアグラウンド通信

アプリケーションが意図的に無線通信モジュールを利用した無線通信を行うために、以下のライブラリが用意されています。

  • ローカル通信(UDS 通信)
  • ニンテンドー3DSダウンロードプレイ
  • 自動接続
  • 信頼性のあるローカル通信(RDT 通信)

この章では、それぞれのライブラリを使用したアプリケーションの開発に必要な情報とプログラミング手順について説明します。

3.1. ローカル通信(UDS 通信)

3DS ではローカル通信として、ゲームでの利用を想定した任天堂独自の通信方式である「UDS 通信」を使用します。

UDS 通信は以下の特徴を持っています。

  • 1 つのネットワークに対して、最大 16 台(現時点では 13 台以上は動作保証外です)が同時接続可能
  • パケットのブロードキャストをサポート
  • ピクチャーフレームに同期しない
  • 送信データの到達は保証されない
  • 伝送経路は暗号化されている
  • データの送受信を定期的に行わなくとも接続が維持される

 

注意:

開発用の本体(デバッガ、開発実機)と製品版の本体との間では UDS 通信を行うことができません。

UDS 通信には、CTR-SDK で用意されている UDS ライブラリを使用します。

UDS ライブラリで使用する用語は以下のとおりです。

ノード / Node

UDS 通信のネットワーク上にある、通信(データの送受信)を行う端末です。

後述する、Master と Client を総称したものです。

ノード ID

UDS 通信のネットワークへの接続時に割り当てられる ID です。

詳細については「3.1.8. ノード ID の割り当て」を参照してください。

Master

新規に UDS 通信のネットワークを開設し、必要に応じてネットワークの属性を変更する権限を持つノードです。

DS / DSi の MP 通信における MP Parent に相当します。

Client

Master が開設した既存のネットワークに接続し、データの送受信を行うノードです。

DS / DSi の MP 通信における MP Client に相当します。

Spectator

Master が開設した既存のネットワークに接続し、データの受信のみを行う端末ですがノードではありません。

Client と異なり、ネットワークの同時接続台数としてカウントされません。Spectator の同時接続可能台数にはライブラリによる上限はありません。また、アプリケーションからその数を制限することもできません。

Unicast 通信

Unicast 通信は、あるノードから別の 1 つのノードに対してパケットを送信する方式で、基本的に Master と Client 間でのみ通信が可能です。アプリケーションからは Client 同士での通信も同じように扱うことができますが、Master を経由する必要がありますので、ライブラリ内で 2 回の送信が行われます。この方式では、すべて Master との通信で処理されるため Client からの送信レイテンシは高くなりますが、送信先のノードが通信可能範囲外になることはありません。

Broadcast 通信

Broadcast 通信は、あるノードから自分以外のすべてのノードに対してパケットを送信する方式で、1 回の送信で複数のノードにパケットを送信することができます。この方式では、ネットワーク内のノード数に関係なく 1 回の送信で処理されるため Client からの送信レイテンシも低くなりますが、Client から通信可能範囲外にあるノードに送信されるパケットが到達しなくなります。

3.1.1. UDS 通信の内部ステート

UDS ライブラリは、図 3-1 のように遷移する内部ステートを持っています。

図 3-1. 状態遷移図

Disconnected Creating Network Master Destroying Network Create Network Success Destroy Network Connecting Network Client Disconnecting Network Connect Network Success(Client) Disconnect Network Spectator Success(Spectator) Disconnect Network Initializing Finalizing Initialize Success Finalize None 中間ステート ステート UDS状態遷移図 被切断 被切断 Failed Failed API実行による遷移 自動的な遷移 Failed Scan

内部ステートには状態を表すステートと、現在のステートから目的のステートへ遷移する際に経由する中間ステートがあります。中間ステートはライブラリ内部の処理用として存在しているステートのため、基本的にアプリ開発者が意識する必要はありませんが、もし現在のステートが中間ステートだった場合は、通常のステートに遷移するまで待つようにしてください。

UDS ライブラリはステートによって呼び出すことのできる関数に制限があり、そのステートで使用できない関数を呼び出した場合は即座にエラーが返されます。

表 3-1. ステート

ステート名

説明

None

UDS ライブラリ初期化前の状態です。

Disconnected

UDS ライブラリの初期化が完了していて、ネットワークに属していない状態です。

Master

ネットワークを新規に構築し、Master として動作している状態です。

Client

既存のネットワークに Client として接続している状態です。

Spectator

既存のネットワークに Spectator として接続している状態です。

表 3-2. 中間ステート

中間ステート名

説明

Initializing

UDS ライブラリの初期化中です。成功した場合に Disconnected に遷移します。

Finalizing

UDS ライブラリを終了中です。終了後 None に遷移します。

Creating Network

設定に基づきネットワークを構築中です。終了後 Master に遷移します。

Connecting Network

既存のネットワークに Client/Spectator として接続中です。

成功した場合に Client/Spectator に遷移します。

Destroying Network

すべての Client/Spectator をネットワークから切断し、ネットワークを破棄しています。

終了後 Disconnected に遷移します。

Disconnecting Network

現在接続中のネットワークから離脱中です。終了後 Disconnected に遷移します。

外的要因によりネットワークから切断された場合にもこのステートになります。

これらのステートは nn::uds::State で定義されており、ステート名と定義名の対応は以下のようになっています。

表 3-3. ステート名と定義名の対応

ステート名

定義名

None

STATE_NONE

Disconnected

STATE_DISCONNECTED

Master

STATE_MASTER

Client

STATE_CLIENT

Spectator

STATE_SPECTATOR

Initializing

STATE_PROCESSING_INITIALIZE

(このステートをアプリケーションで取得することはありません)

Finalizing

STATE_PROCESSING_FINALIZE

(このステートをアプリケーションで取得することはありません)

Creating Network

STATE_CREATING_NETWORK

Connecting Network

STATE_CONNECTING_NETWORK

Destroying Network

STATE_DESTROYING_NETWORK

Disconnecting Network

STATE_DISCONNECTING_NETWORK

3.1.2. 初期化

UDS ライブラリの初期化は、nn::uds::Initialize() を呼び出して行います。

コード 3-1. UDS ライブラリの初期化
nn::Result nn::uds::Initialize(
        nn::os::Event* pStatusUpdateEvent,
        void* receiveBuffer, const size_t bufferSize);
nn::Result nn::uds::Initialize(
        nn::os::Event* pStatusUpdateEvent,
void* receiveBuffer, const size_t bufferSize, 
        nn::cfg::UserName* pUserName);

pStatusUpdateEvent には nn::os::Event クラス型のインスタンスへのポインタを指定します。インスタンスは自動リセットイベントとして初期化されます。

Event インスタンスは nn::uds::Initialize() 内で初期化され、以下のタイミングでアプリケーションに対する通知が行われます。

  • 自身がネットワークを構築した、もしくはネットワークに接続したとき
  • 自身がネットワークから離脱した、もしくは切断されたとき
  • 自身以外のノードがネットワークに接続したとき
  • 自身以外のノードがネットワークから離脱した、もしくは切断されたとき

receiveBuffer bufferSize には UDS ライブラリ内で使用する受信データバッファとそのサイズを指定します。バッファはアプリケーションでデバイスメモリ以外から確保し、先頭アドレスが nn::uds::BUFFER_ALIGNMENT ( 4096 Byte ) アライメントでサイズが nn::uds::BUFFER_UNITSIZE ( 4096 ) の倍数でなければなりません。受信データバッファに指定したメモリ領域は、nn::uds::Finalize() による終了処理が完了するまで、アプリケーションからアクセスしないでください。

pUserName は Mii につけられた名前など、本体設定と異なるユーザー名を使用する場合に指定します。ユーザー名の取り扱いは UGC ガイドラインに従ってください。NULL を指定すると、pUserName を引数に持たない関数で初期化した場合と同様に、本体設定のユーザー名が使用されます。

なお、返り値に nn::uds::ResultAlreadyOccupiedWirelessDevice が返されたときは、無線通信モジュールがほかの通信で利用されているために UDS 通信を開始することができません。また、無線オフモードになっているときには、nn::uds::ResultWirelessOff が返されます。

3.1.3. 接続

UDS ライブラリの初期化後、UDS 通信のネットワークの構築を行います。ここでは、Master によるネットワークの構築および Client/Spectator によるネットワークへの接続の手順を説明します。

3.1.3.1. ローカル通信 ID の生成

UDS 通信のネットワークの構築や検索を行う関数の呼び出しには、ネットワークを識別するためにローカル通信 ID(32 bit)を指定しなければなりません。ローカル通信 ID は、以下の関数でユニーク ID(20 bit)から変換して生成します。

コード 3-2. ローカル通信 ID の生成
bit32 nn::uds::CreateLocalCommunicationId(bit32 uniqueId, 
                                          bool isDemo = false);

uniqueId には、弊社業務部がタイトルごとに割り当てたユニーク ID を指定します。異なるタイトル間で通信を行う際は、どちらか片方の ID を指定してください。ID 0 はライブラリで予約されていますので指定しないでください。

isDemo には、ユニーク ID が共通となる製品版とダウンロードアプリ型体験版の間で UDS 通信を行いたくない場合に、体験版側のみ true を指定してください。体験版との通信を行うかどうかに関わらず、製品版では必ず false を指定してください。

補足:

弊社からユニーク ID を割り当てられていないタイトルや実験プログラムで UDS 通信を使用する場合は、uniqueId にゲームソフト試作用コード(0xFF000~0xFF3FF)を指定してください。ただし、製品版では必ず弊社より割り当てられたユニーク ID を指定してください。

3.1.3.2. ネットワークの新規構築

UDS 通信のネットワークの構築は、Master が nn::uds::CreateNetwork() を呼び出すことで行われます。

コード 3-3. ネットワークの構築
nn::Result nn::uds::CreateNetwork(
        u8 subId,
        u8 maxEntry,
        bit32 localId,
        const char passphrase[], size_t passphraseLength,
        u8 channel);
nn::Result nn::uds::CreateNetwork(
        u8 subId,
        u8 maxEntry,
        bit32 localId,
        const char passphrase[], size_t passphraseLength,
        u8 channel,
        const void* pData, size_t dataSize);

subId は通信モード識別用 ID です。アプリケーション側で自由に設定することができます。たとえば、"通信対戦"や"データ交換" など、通信モードの区別に使用することを想定しています。subId に 0xFF は指定しないでください。0xFF は Client がすべての subId を対象にネットワークを探索する際に使用されます。

maxEntry にはネットワークに接続できるノードの最大数を指定します。Master、Client を含めて最大 16 まで指定可能ですが、13 台以上での同時接続は十分な動作確認が行われていません。そのため、現時点では 13 以上を指定した場合の動作は保証の範囲外とします。Master 自身が台数に含まれていることと、Spectator が台数に含まれないことに注意してください。

localId にはユニーク ID から生成したローカル通信 ID を指定します。

passphrase には無線レイヤの暗号化に使用する暗号鍵の種となる文字列(パスフレーズ)を指定します。localIdpassphrase の組み合わせが、Master と Client/Spectator で一致する場合に通信が成立します。極端に短いものや連想されやすいもの、ほかのタイトルでも使っているものなどを指定して第三者に特定されたり、文字列そのものが漏洩したりしないように注意してください。

passphraseLength には passphrase の長さを指定します。指定可能な値の範囲は 8 以上、255 以下です。

channel には UDS 通信で使用するチャンネルを 0, 1, 6, 11 のいずれかから指定します。0 を指定した場合はチャンネルを自動で選択します。チャンネルの指定はデバッグ用途でのみ使用します。

pData dataSize には、構築と同時にビーコンに独自データをセットしたい場合に、データとそのバイトサイズ指定します。dataSize に 0 を指定したときに限り、pDataNULL を指定してもエラーにはなりません。ビーコンにセットするデータについては「3.1.3.5. 独自データのビーコンへのセット」を参照してください。

注意:

製品版の本体での実行時には、チャンネルが常に自動で選択されます。

チャンネルを指定したネットワークの構築を行うには、デバッグモードに設定(Config ツールで「Debug Setting」の「Debug Mode」を「enable」に設定)する必要があります。

UDS 通信ではネットワークを再構築したことを識別するために、ネットワークを構築するたびに TemporaryID を乱数生成しています。ただし再構築までの時間が短い場合、かつ Client からの接続がなく、機器間の通信が成立していない場合は TemporaryID を更新しません。

3.1.3.3. 周囲にあるネットワークの探索

Client/Spectator は、同じローカル通信 ID で構築されたネットワークが周囲にあるかどうかを nn::uds::Scan() を呼び出して探索する必要があります。

コード 3-4. 既存ネットワークの探索
nn::Result nn::uds::Scan(
        void * pBuffer, size_t bufferSize,
        u8 subId,
        bit32 localId);

pBuffer には 探索で発見したネットワークの情報を格納するバッファのアドレスを指定します。バッファに必要なサイズはネットワーク 1 つあたり 1 KByte 程度です。バッファのサイズを超える分のネットワークの情報は格納されません。

subId は探索の対象とする通信モード識別用 ID です。この引数で指定した値と同じ subId で構築されたネットワークが探索の対象となります。subId に 0xFF を指定したときは、すべてのネットワークが探索の対象となります。

localId にはユニーク ID から生成したローカル通信 ID を指定します。

注意:

開発用の本体(デバッガ、開発実機)と製品版の本体とでは、双方とも相手をスキャンで発見することができません。

次に、nn::uds::Scan() を実行して得られたバッファの内容を nn::uds::ScanResultReader クラスのコンストラクタに渡して、探索結果を解析するインスタンスを生成します。

生成したインスタンスからは、メンバ関数の GetFirstDescription() で最初の探索結果を受け取ることができます。GetNextDescription() では、その次の探索結果を受け取ることができます。なお、探索結果は受信信号強度(RSSI 値)の大きなものから格納されています。

探索結果は nn::uds::NetworkDescriptionReader クラス型のインスタンスです。このインスタンスのメンバ関数でネットワーク情報の解析を行うことができ、GetNetworkDescription() でネットワーク情報を、GetNodeInformationList() でノードの一覧を、GetRadioStrength() で電波強度(4 段階)を取得することができます。また、CompareWith() ではネットワーク情報の比較を行い、比較結果を「完全に一致」、「同じネットワークだがデータが異なる」、「違うネットワーク」の 3 種類で取得することができます。

3.1.3.4. ネットワークへの接続

Client/Spectator は、解析された探索結果から得られる情報のうち、GetNetworkDescription() で取得したネットワーク情報を元に nn::uds::ConnectNetwork() を呼び出して既存のネットワークに接続することができます。

コード 3-5. ネットワークへの接続
nn::Result nn::uds::ConnectNetwork(
        const NetworkDescription & networkDescription,
        ConnectType type,
        const char passphrase[], size_t passphraseLength);

networkDescription にはネットワークの探索で得られたネットワーク情報を指定します。

type には接続する端末の種別を指定します。端末の種別は以下の 2 種類から選択することができます。

表 3-4. ネットワークへの接続モード

設定値

説明

CONNECT_AS_CLIENT

Client(送受信が行え、接続時にノード ID が付与されるノード)としてネットワークに接続します。

CONNECT_AS_SPECTATOR

Spectator(受信のみが可能な、接続時にノード ID が付与されないノード)としてネットワークに接続します。

Spectator としてネットワークに接続できるかどうかは、ネットワーク情報(nn::uds::NetworkDescription クラス)のメンバ関数 CanConnectAsSpectator() の返り値が true であるかどうかで判断することができます。

passphrase には無線レイヤの暗号化に使用する暗号鍵の種となる文字列(パスフレーズ)を指定します。Master が構築したネットワークのローカル通信 ID とパスフレーズの組み合わせと一致する場合に通信が成立します。

passphraseLength には passphrase の長さを指定します。指定可能な値の範囲は 8 以上、255 以下です。

補足:

満員のネットワークに対して接続したときに返されるエラーは ResultAlreadyNetworkIsFull、解散済みのネットワークに対して接続したときに返されるエラーは ResultNotFoundNetwork です。

3.1.3.5. 独自データのビーコンへのセット

ネットワークを構築した Master は、最大 NET_DESC_APPDATA_SIZE_MAX バイトの独自データを、ネットワークのビーコンに設定することができます。

コード 3-6. 独自データのビーコンへのセット
nn::Result nn::uds::SetApplicationDataToBeacon(
                        const void* pData, size_t dataSize);

pData dataSize には、独自データの先頭アドレスとそのサイズを指定します。

ビーコンに設定する独自データは、セッションの状態(対戦者待ちや対戦中など)やコメントなどを設定し、ネットワークを探索してきた Client や Spectator にネットワークの状態を知らせるといった、アプリケーション独自の接続状況の管理に使用することを想定しています。ただし、独自データをセットし直しても、すでにネットワークに接続されている Client や Spectator には変更が通知されないことに注意してください。

独自データの内容は暗号化されません。ビーコンは PC などで容易に傍受できることに注意してください。ビーコンの改竄は検出できますが、傍受したビーコンをそのまま利用される可能性があります。特に、ビーコンで配信した特別なデータが不正に拡散されないようにする場合は、傍受したビーコンが別の場所で配信されても、ビーコンを受信するだけでは受け取れないようにする対策(Spectator としてネットワークに接続するなど)が必要になります。

3.1.3.6. ビーコンにセットされた独自データの取得

Master がビーコンにセットした独自データを Client/Spectator が取得できるタイミングは、ネットワークを探索した際に取得したネットワーク情報のメンバ関数を呼び出したときと、ネットワーク接続中に取得用の関数を呼び出したときです。

コード 3-7. ビーコンにセットされた独自データの取得
size_t nn::uds::NetworkDescription::GetApplicationData(
                        bit8* buffer, const size_t bufferSize) const;
nn::Result nn::uds::GetApplicationDataFromBeacon(
                        void* pBuffer, size_t* pDataSize, size_t bufferSize);

buffer bufferSizepBufferbufferSize にはそれぞれ、独自データを格納するバッファの先頭アドレスとそのサイズを指定します。バッファのサイズには独自データの最大サイズ(NET_DESC_APPDATA_SIZE_MAX)を指定し、バッファは最大サイズで確保してください。

取得した独自データのサイズは、nn::uds::NetworkDescription::GetApplicationData() は返り値で、nn::uds::GetApplicationDataFromBeacon()pDataSize で、それぞれ確認することができます。

3.1.4. 通信

これまでの処理で UDS 通信でデータの送受信を開始する準備が整いました。続いてデータの送受信について説明します。

3.1.4.1. 端点の生成

データの送受信を行う前に、ネットワークの端点(Endpoint)を生成する必要があります。端点はデータの送受信を行う "入り口" のようなもので、nn::uds::CreateEndpoint() で生成することができます。

補足:

現時点では、同時に生成可能な端点は 16 個までです。

送信用と受信用に別々の端点を生成することを推奨します。同じ端点を送信と受信で使うことは可能ですが、送受信を行っている最中の端点を別の送受信に使用しないように注意してください。

受信用の端点は、nn::uds::Attach() を呼び出し、ポート番号とノード ID を関連付けることでデータを受信できるようになります。この時、ライブラリは nn::uds::Initialize() で確保したメモリブロック上に受信用バッファを作成します。

コード 3-8. 端点とポート番号、ノード ID の関連付け
nn::Result nn::uds::Attach(
    EndpointDescriptor * pEndpointDesc,
    u16 srcNodeId,
    u8 port,
    size_t receiveBufferSize = ATTACH_BUFFER_SIZE_DEFAULT);

pEndpointDesc には nn::uds::CreateEndpoint() で生成した端点を指定します。

srcNodeId には送信元となるノード ID を指定します。指定されたノード以外から送られたデータは受信用バッファに格納されません。BROADCAST_NODE_ID を指定すると、すべてのノードから送信されたデータを受信用バッファに格納します。

port には受信するポート番号を指定します。指定したポート番号以外からのデータは受信用バッファに格納されません。ポート番号 0 はライブラリで予約されていますので指定しないでください。

receiveBufferSize には、確保する受信用バッファのサイズを ATTACH_BUFFER_SIZE_MIN 以上で指定しなければなりません。nn::uds::Initialize() で確保したメモリブロックから確保されますので、受信バッファの合計が確保したメモリブロックのサイズよりも大きくならないよう注意してください。また、32 Byte ごとにメモリを確保しますので、32 の倍数にすることでメモリの使用効率がよくなります。デフォルトでは ATTACH_BUFFER_SIZE_DEFAULTUDS_PACKET_PAYLOAD_MAX_SIZE の 8 倍)の領域を受信用バッファとして確保します。

データの受信時に送信元のノード ID を取得することもできますので、複数のノードが同じポートに送信するデータを 1 つの端点で受け取っていても送信元を特定することができます。ただし、受信用バッファはリングバッファのため、サイズが十分でなければ先に受信したパケットから消失する可能性があります。

ネットワークからの切断時など、端点を介しての通信が不要になったときは、nn::uds::DestroyEndpoint() で端点を破棄してください。受信用の端点を破棄したときは、nn::uds::Attach() を呼び出したときに作成した受信用バッファはライブラリによって解放されます。

3.1.4.2. 送信

生成した端点を通して、ノードに nn::uds::SendTo() でデータを送信します。送信処理が完了するまでブロックされることに注意してください。

コード 3-9. データの送信
nn::Result nn::uds::SendTo(
        const EndpointDescriptor & endpointDesc,
        const void * data, size_t dataSize,
        u16 destNodeId, u8 port,
        bit8 option = 0x00);

endpointDesc には生成した端点を指定します。

data には送信するデータへのポインタを、dataSize にはそのサイズを指定します。1 回の呼び出しで送信できるデータの最大サイズは UDS_PACKET_PAYLOAD_MAX_SIZE で定義されています。dataSize に最大サイズを超える値を指定した場合、ResultTooLarge のエラーが返されます。送信データの先頭アドレスが 4 Byte アライメントでない場合、ResultMisalignedAddress のエラーが返されます。

destNodeId には送信先のノード ID を指定します。BROADCAST_NODE_ID(= 0xFFFF)を指定すると送信データはブロードキャストされます。Spectator が受信することのできるデータは、この方法でブロードキャストされたデータのみです。特定の Spectator に向けての送信はできません。また、Spectator はデータの送信を行うことができません。

port にはポート番号を指定します。ポート番号 0 はライブラリで予約されていますので指定しないでください。

option には、以下のフラグの論理和を指定します。これらのオプション設定を利用しない場合は、この引数の指定を省略する(0x00 を指定する)ことができます。オプション設定を使用しない場合は、Client から Client への Unicast 通信は Master を経由します。また、経由のために Master へ送信されるパケットを除き、送信バッファによるパケットのバッファリングが有効になります。

  • NO_WAIT
    このフラグを指定した場合、UDS 内部の送信バッファにデータをためず、即座に送信を行いますが、送信が完了するまで処理がブロックされます。
    このフラグを指定しない場合は、なるべく送信遅延が起こらないように、送信バッファを無線送信の一回分(UDS_PACKET_PAYLOAD_MAX_SIZE とほぼ同等)という小さなサイズで確保します。これが一杯になった、もしくはバッファにある最も古いデータがためられてから一定時間(最大送信遅延時間)経った場合に、ライブラリがバッファ内のデータを送信します。そのため、このフラグを指定せずに送信を繰り返し実行した場合の処理には、バッファにデータをためるだけのケースとバッファが一杯になり送信処理も行うケースが存在し、後者は前者に比べてブロック時間が長くなります。
    なお、最大送信遅延時間のデフォルトは 10 ms に設定されていますが、nn::uds::SetMaxSendDelay() により、5~100 ms の範囲で指定可能です。
補足:

NO_WAIT を指定するかどうかは、通信に求めるリアルタイム性に依存します。

送信先にデータが到達するまでの時間は最小限であることが望ましく、データ交換にかかる時間をなるべく短くしたい場合は NO_WAIT を指定してください。

インターネット通信と同程度のレイテンシが許容できる場合は、NO_WAIT を指定しないことで、より多くのパケットが扱えるようになります。複数のパケットをまとめて送信するようになるため、サイズの小さいパケットを大量に送信したときは、見た目の通信帯域が向上します。スループットは向上しませんが、1 秒間に送信できるパケットは増えます。

  • FORCE_DIRECT_BC
    どの送信先に対してでも、Broadcast 通信による送信を行います。Client 間の Unicast 通信も Master を経由しません。送信レイテンシは低くなりますが、通信可能範囲外のノードを考慮する必要があります。 
UDS 通信による送信の流れ

SendTo() で処理されたパケットは、NO_WAIT オプションが設定されていなければ送信バッファに可能な限りためられてから、NO_WAIT オプションが設定されていればすぐに、ライブラリによって無線フレーム(無線規格の通信単位)に詰められて送信されます。

各ノードが受信した無線フレームはライブラリによってパケットに分解され、それぞれの受信バッファに振り分けられます。このとき、Master 以外は自分宛ではないパケットを捨てますが、スター型ネットワークである UDS 通信で Client 間通信を行うために、Master はほかのノード宛のパケットを自身の送信バッファにためて再送するように設計されています。

送信パケットの順序の保証について

同じ送信先に同じオプション設定(FORCE_DIRECT_BC)で送信する場合には、パケットの順序は保証されています。

オプションを指定しない場合やオプションを使い分けた場合、経由するパスの違いから Unicast 通信と Broadcast 通信の間でパケットの順序が入れ替わる可能性がありますが、Unicast 通信で送信するパケットと Broadcast 通信で送信するパケットの順序が入れ替わること自体が問題となるケースはないと思われますので、基本的にはパケットの順序が保証されると考えてください。

送信の遅延について

ライブラリは無線通信の物理レイヤでの効率を良くするために、サイズの小さなパケットをまとめて送信するように設計されています。そのため、nn::uds::SendTo() により送信されたデータは設定された送信遅延時間の間、送信を待つ可能性があります。この送信遅延を回避し、回線効率よりもレイテンシを重視したい場合は nn::uds::SendTo() の引数 optionNO_WAIT を含む指定を行ってください。

nn::uds::SetMaxSendDelay() では、送信遅延時間の最大値を設定することができます。

コード 3-10. 最大送信遅延時間の設定
nn::Result nn::uds::SetMaxSendDelay(nn::fnd::TimeSpan maxDelay);

maxDelay には、最大送信遅延時間を 5~100 ms の範囲で指定してください。デフォルトは 10 ms です。

この関数は、ネットワークに接続していない状態でのみ実行可能です。

3.1.4.3. 受信

Attach() によってポート番号とノード ID との関連付けられた端点を介してデータの受信を行います。送信元のノード ID を取得する必要がない場合は nn::uds::Receive() を、取得する必要がある場合は nn::uds::ReceiveFrom() を呼び出してください。ここでは、nn::uds::ReceiveFrom() について説明します。

コード 3-11. データの受信
nn::Result nn::uds::ReceiveFrom(
        const EndpointDescriptor & endpointDesc,
        void * pBuffer,
        size_t * pReceivedSize,
        u16 * pSrcNodeId,
        size_t bufferSize,
        bit8 option = 0x00);

endpointDesc には nn::uds::CreateEndpoint() で生成し、nn::uds::Attach() で関連付けた端点を指定します。

pBuffer には受信データ格納先のポインタを指定します。バッファの先頭アドレスとサイズは 4 Byte のアライメントである必要があります。バッファの先頭アドレスとサイズが 4 Byte アライメントでない場合、それぞれ ResultMisalignedAddressResultMisalignedSize のエラーが返されます。

pReceivedSize には受信データサイズの格納先のポインタを指定します。1 回の呼び出しで受信できるデータの最大サイズは UDS_PACKET_PAYLOAD_MAX_SIZE で定義されています。

pSrcNodeId には送信元のノード ID の格納先のポインタを指定します。

bufferSize には受信データ格納先のバッファのサイズを指定します。

option NO_WAIT を指定すると、データを受信していない場合でも即座に終了します。option を指定しない場合は、データを受信もしくはエラーが発生するまで受信を終了しません。

受信パケットが壊れている可能性について

UDS 通信では、データ破壊のチェックはハードウェアでは行われていますが、速度と適応性を重視しているため、ソフトウェアレベルでパケット単位の完全性チェックは行っていません。

壊れたり改竄されたりすると困るようなデータ(アイテム交換など)の場合は、アプリケーションで、ファイル単位などの適切な粒度でチェックを行ってください。改竄されても特に困らない場合やリアルタイム性が要求される場合は、パケット単位でのチェックをする必要はありません。

3.1.4.4. パケット消失の要因

ここでは、UDS 通信でパケットが消失する要因を説明します。

環境の悪化

通信する本体間の距離による電波強度の低下、第三者が送信する電波による干渉、ノイズなどの外的要因によって環境が悪化するとパケットの消失率が高くなります。

パケット消失率の実測値(ライブラリで保証するものではありません)は、良好な環境では 1% 程度、接続が維持できる程度に劣悪な環境では最大 10% 程度です。経験的な情報ですが、システムやデバイスでの処理が追いつかなくなると、それまでは低かった消失率が急激に高くなる傾向があります。

回線速度

UDS 通信の最大通信量の目安は、ネットワーク全体で秒間 500~600 パケット程度で、これを大きく超えた場合や周囲に別のネットワークが存在することで通信帯域が圧迫された場合にパケットが消失することがあります。

このケースでは、消失率は送信されるパケット量に左右されますので、接続台数を減らすとパケットの消失率が低くなります。

現在、com_demo1 デモのように単純な通信プログラムで単一のネットワークのみが通信を行った場合、秒間 800 パケットを超えたあたりで消失率が高くなる現象が確認されています。

受信バッファのバッファあふれ

アプリケーションが受信関数を呼び出す頻度よりもパケットを受信する頻度が高い場合、受信バッファがあふれて、もっとも古いパケットが消失することがあります。このケースでは、通信負荷の上昇に伴って徐々に消失率が高くなります。受信バッファのサイズを大きくすることで一時的な負荷の増大には対応できますが、恒常的なバッファあふれには対応できないことに注意してください。

注意:

SNAKE と CTR のプロセッサの処理速度の違いで通信に問題が生じます。そのため、プロセッサの処理速度に大きく依存した通信処理にしないでください。

たとえば、送信過多にならないようにパケットの送信後にタイマーを用いた遅延処理を行う、送信側は受信側の受信状況を必要に応じて確認するなどの対応を行ってください。

3.1.4.5. 通信の効率を高めるには

UDS 通信のベースとなっている規格(IEEE 802.11b/g)では、同一チャンネルのネットワーク全体で無線帯域を共有しています。また、無線フレームの送信には干渉回避のための無通信時間(バックオフ)があるため、同じサイズのデータを送信する場合でも、無線フレームで送信可能な最大のデータサイズまでパケットをまとめるなどして、無線フレームの送信回数を減らすことで効率がよくなります。

UDS 通信のネットワークトポロジは、Master にすべての Client が接続するスター型です。通常の Unicast 通信では、Master から Client または Client から Master への送信が 1 パスで行われますが、Client から Client への送信は Master を経由するため 2 パスで行われます。同じデータを複数のノードに送信する場合は、Broadcast 通信を利用することによりネットワーク全体で送信されるパケットの数を減らすことができます。ネットワークに接続されているすべてのノードに一斉に送信する Broadcast 通信は 1 パスで行われ、しかもノードの個数に関わらず 1 回で送信することができます。ただし、Master からの Broadcast 通信ではすべてのノードが通信可能範囲に存在することが保証されていますが、Client からの Broadcast 通信では通信可能範囲外にノードが存在する可能性を考慮する必要があります。

メッシュ型のネットワークを UDS 通信で実装する

メッシュ型のネットワークではすべてのノードが同じ処理を行うため、通信処理の実装が比較的容易になります。しかし、すべてのノードがほかのすべてのノードにパケットを送信するため、通信台数が増加するとネットワーク全体で単位時間に送信するパケットの数が指数関数的に増加してしまいます。

UDS 通信では、Broadcast 通信を利用することで送信するパケットの数をノード数に比例する値にまで減らすことができますが、Client からの Broadcast 通信ではパケットが届かないノードが存在する可能性に注意しなければなりません。そのため、パケットの不達を検知した Client がパケットの再送を要求する処理が必要となります。再送処理は Unicast 通信で行うことになりますので、往復かつ Master を経由することによるレイテンシを考慮しなければなりません。

通常、Broadcast 通信ではすべてのノードに同じパケットを送信します。すべてのノードに同じデータを送信する場合は問題ありませんが、ノードごとに送信するデータが異なる場合はパケットの数を減らすことができません。送信オプションに FORCE_DIRECT_BC を指定した場合は Unicast 通信も Broadcast 通信のように 1 パスで送信され、受信したノードで自分宛ではないパケットが破棄されます。この機能とパケットをまとめて送信する機能を利用することで、ネットワーク全体で単位時間に送信するパケットの数を減らすことができます。ただし、この方法では通信可能範囲外にノードが存在する可能性を考慮する必要があります。

3.1.5. 切断

ここでは Master による Client/Spectator の切断および、Client/Spectator によるネットワークからの切断の手順を説明します。

3.1.5.1. Master による Client/Spectator の切断

Master は次の方法で Client/Spectator をネットワークから切断することができます。

  • 特定の Client をネットワークから切断
  • すべての Client をネットワークから切断
  • すべての Spectator をネットワークから切断

Client をネットワークから切断するには、nn::uds::EjectClient() を呼び出してください。

コード 3-12. Client をネットワークから切断
nn::Result nn::uds::EjectClient(u16 nodeId);

特定の Client をネットワークから切断するには、nodeId に切断したい Client のノード ID を指定してください。

すべての Client を一括でネットワークから切断する場合は、nodeIdBROADCAST_NODE_ID(= 0xFFFF)を指定してください。

Spectator をネットワークから切断するには、nn::uds::EjectSpectator() を呼び出してください。

コード 3-13. Spectator をネットワークから切断
nn::Result nn::uds::EjectSpectator(void);
nn::Result nn::uds::AllowToSpectate(void);

nn::uds::EjectSpectator() は、すべての Spectator をネットワークから切断し、以降は Spectator の接続を拒否するようになります。ネットワークを構築したときの初期設定では、Spectator の接続が許可されています。再度 Spectator の接続を許可するには、nn::uds::AllowToSpectate() を呼び出してください。

3.1.5.2. Master によるネットワークの破棄

Master はすべての Client/Spectator を切断し、ネットワークを破棄することができます。ネットワークを破棄するには、nn::uds::DestroyNetwork() を呼び出してください。

コード 3-14. ネットワークの破棄
nn::Result nn::uds::DestroyNetwork(void);
補足:

Master がネットワークを破棄した場合、Client が受け取る切断要因(nn::uds::ConnectionStatus 構造体の disconnectReason)は DISCARDED_FROM_NETWORK です。ただし、通信環境によっては CONNECTION_LOST となる可能性があります。

3.1.5.3. Client/Spectator による切断

Client/Spectator が現在接続中のネットワークから離脱するには、nn::uds::DisconnectNetwork() を呼び出してください。

コード 3-15. ネットワークからの離脱
nn::Result nn::uds::DisconnectNetwork(void);

3.1.5.4. Master による Client からの接続の制御

Master は Client のネットワークへの接続の拒否や許可を制御することができます。

コード 3-16. Client からの接続の制御
nn::Result nn::uds::DisallowToConnect(bool isDisallowToReconnect = false);
nn::Result nn::uds::AllowToConnect();

nn::uds::DisallowToConnect() は、現在の接続を維持したまま、以降の Client からの接続を拒否します。ただし、isDisallowToReconnectfalse を指定した場合は、この関数を呼び出したあとに切断された Client であれば再接続を許可します。ネットワークを構築したときの初期設定では、Client の接続が許可されています。

nn::uds::AllowToConnect() は、Client からの接続の拒否を解除します。nn::uds::EjectSpectator() による Spectator の接続拒否は解除されません。

3.1.5.5. スリープ状態への遷移、無線オフモードへの切り替えによる切断

事前に UDS 通信を終了させずにスリープ状態へ遷移した場合や、無線スイッチにより無線オフモードに切り替わった場合、UDS 通信の内部ステートはエラー(nn::uds::STATE_ERROR)へと遷移します。この状態で nn::uds::Finalize() 以外の関数を呼び出すと、スリープ状態へ遷移していた場合は nn::uds::ResultInvalidState を、無線オフモードに切り替えていた場合は nn::uds::ResultWirelessOff を返すようになります。そのため、内部ステートがエラーから復帰する場合は nn::uds::Finalize() で UDS 通信を終了させ、再び初期化から行わなければなりません。無線オフモードへの切り替えで切断された場合は、無線オンモードに切り替わってから初期化する必要があります。

補足:

UDS 通信中にスリープ状態へと遷移する場合、アプリケーションは事前に UDS 通信を終了してからスリープ状態に遷移することを推奨します。

3.1.6. 終了処理

nn::uds::Finalize() を呼び出して、UDS 通信を終了します。

コード 3-17. UDS ライブラリの終了処理
nn::Result nn::uds::Finalize(void);

このとき、ライブラリは nn::uds::Initialize() で確保したメモリブロック領域と nn::os::Event インスタンスを解放します。UDS 通信を使用するアプリケーションは、その終了までに UDS 通信を終了させていなければなりません。「3DS プログラミングマニュアル - システム編」の「終了処理の注意事項」を参照してください。

3.1.7. ネットワーク状態の同期

UDS ライブラリは、ネットワークに接続しているノードの以下の情報について、自動的に同期を取っています。

  • 現在の接続数
  • 各ノードのノード ID
  • 各ノードのローカルフレンドコード(プライバシー保護のためスクランブルがかけられています)
  • 各ノードのユーザー名

アプリケーションでノードの情報の更新を明示的に通知する必要はありません。

また、これらの情報は Master/Client のすべてのノードで同期を取りますので、Client 側でも各ノードの情報を取得したり、新規接続・切断を検知したりすることができます。

3.1.8. ノード ID の割り当て

UDS 通信では、ネットワークに接続しているノードをノード ID という 16 bit の番号で管理しています。Master と Client にはノード ID が割り当てられますが、Spectator にはノード ID が割り当てられません。

同じネットワーク内では、MP 通信の AID と異なり、一度使用されたノード ID がその後別のノードに割り当てられることはありません。また、Client が同じネットワークに再接続すると、前回の接続時と同じノード ID が割り当てられます。

再接続時に前回と同じノード ID を割り当てるため、Master は内部で 64 台分の切断済みノード情報を保持しています。これを超える台数の切断が発生した場合は、もっとも昔に切断した Client のノード情報が破棄されます。切断済みノード情報から破棄されたノードがネットワークに再接続されると、前回とは異なるノード ID が割り当てられます。 なお、以下のノード ID は予約されているため、Client に割り当てられることはありません。

表 3-5. 予約済みノード ID

予約済みノードID

説明

0

存在しないノードです。ライブラリの内部処理で使用します。

1

Master に固定で割り当てられます。

0xFFFF(BROADCAST_NODE_ID

ブロードキャストする場合に指定するノード ID です。

3.1.9. 各種情報の取得

自身の接続状態、リンクレベルなどの情報を取得することができます。

3.1.9.1. 接続状態の取得

nn::uds::GetConnectionStatus() を呼び出して、現在の接続状態を取得することができます。

コード 3-18. 接続状態の取得
nn::Result nn::uds::GetConnectionStatus(nn::uds::ConnectionStatus* pStatus);

pStatus には接続状態を受け取る nn::uds::ConnectionStatus 構造体へのポインタを渡してください。構造体は以下のように定義されています。

コード 3-19. nn::uds::ConnectionStatus 構造体の定義
struct nn::uds::ConnectionStatus
{
    State               nowState;
    DisconnectReason    disconnectReason;
    u16                 myNodeId;
    bit16               updateNodeBitmap;
    u16                 nodeIdList[NODE_MAX];
    u8                  nowEntry;
    u8                  maxEntry;
    bit16               slotBitmap;
};

nowState には現在のステートが格納されます。ステートについては「3.1.1. UDS 通信の内部ステート」を参照してください。

disconnectReason には切断理由が格納されます。格納される値と切断理由の対応は下表のとおりです。

表 3-6. 切断理由

切断理由

BEFORE_COMMUNICATION

まだ通信をしていない状態です。

NETWORK_IS_AVAILABLE

切断されていません。接続は維持されています。

REQUEST_FROM_MYSELF

自身の操作により接続を切断しました。

REQUEST_FROM_SYSTEM

スリープ状態や無線オフモードへの遷移など、3DS 本体からの要求で接続が切断されました。

DISCARDED_FROM_NETWORK

Master の操作により接続が切断されました。

CONNECTION_LOST

通信状況の悪化により接続が維持できなくなりました。

UNKNOWN_DISCONNECT_REASON

切断の原因は不明です。

myNodeId には自身のノード ID が格納されます。Spectator の場合は 0xFFFF が格納されています。

updateNodeBitmap には、前回の呼び出しから変更のあったノードを示すビットマップ情報が格納されます。1 になっているビットに対応する要素が変更のあったノードのノード ID です。ビットマップの 1 ビットが nodeIdList の 1 要素に対応し、下位ビットから上位ビットの順に先頭から最後の要素へと対応しています。

nowEntrymaxEntry には、ネットワークに接続しているノードの数と同時接続の最大数が格納されます。nowEntry には現在の状況が反映されますが、maxEntry は通信中に変化することはありません。

slotBitmap には、ノード情報が格納されているスロットを示すビットマップ情報が格納されます。前回の差分ではなく、現在の状況が反映されています。

3.1.9.2. リンクレベルの取得

nn::uds::GetLinkLevel() を呼び出して、現在のリンクレベル(通信品質)を取得することができます。

コード 3-20. リンクレベルの取得
nn::Result nn::uds::GetLinkLevel(nn::uds::LinkLevel* pLinkLevel);
nn::uds::LinkLevel nn::uds::GetLinkLevel();

pLinkLevel にはリンクレベルを受け取る変数へのポインタを渡してください。リンクレベルは下表のように定義されています。

表 3-7. リンクレベル

説明

LINK_LEVEL_0

通信品質が非常に悪い状態、もしくは通信が成立していません。

LINK_LEVEL_1

通信品質が悪い状態です。

LINK_LEVEL_2

通信品質はあまり良くありません。

LINK_LEVEL_3

通信品質は良好です。

電波強度はアプリケーションで常に表示しなければならない情報ではなく、この関数は処理をブロックするため、高頻度(ゲームフレームごとなど)で呼び出すことは推奨しません。

Client/Spectator がネットワークに接続する前に、指標として電波強度を表示する場合は、nn::uds::Scan() の結果から取得した nn::uds::NetworkDescriptionReader クラスの GetRadioStrength() を利用してください。

3.1.9.3. ノード情報の取得

nn::uds::GetNodeInformation() を呼び出して、指定したノードのユーザー情報を取得することができます。

コード 3-21. ノード情報の取得
nn::Result nn::uds::GetNodeInformation(nn::uds::NodeInformation* pNodeInfo, 
                                       u16 nodeId);

pNodeInfo には、ノード情報を格納する nn::uds::NodeInformation 構造体へのポインタを指定します。

nodeId には、ノード情報を取得したいノードのノード ID を指定してください。

3.1.9.4. チャンネルの取得

nn::uds::GetChannel() を呼び出して、現在のチャンネルを取得することができます。

コード 3-22. チャンネルの取得
nn::Result nn::uds::GetChannel(u8* pChannel);

pChannel にはチャンネルを受け取る変数へのポインタを渡してください。

3.2. ニンテンドー3DSダウンロードプレイ

ニンテンドー3DSダウンロードプレイ(以降、3DSダウンロードプレイと呼びます)は、DSダウンロードプレイと同じように、無線通信で親機から子機プログラムを配信する機能です。 3DSダウンロードプレイは以下のような特徴を持っています。

  • 配信可能なプログラム(子機プログラム)のサイズは 32 MByte(33,554,432 Byte)まで
  • 配信可能なプログラム(子機プログラム)は標準モードで動作するアプリのみ
  • 必要であれば子機プログラムの配信前に、無線通信でシステム更新を行う
  • 子機プログラムでローカル通信をするときに、親機を識別するための情報を取得可能
  • 子機プログラムは専用領域に保存され、ほかの子機プログラムがダウンロードされたときに上書きされる
  • 子機プログラムは HOME メニューには表示されない(HOME メニューからは起動できない)

 

注意:

開発用の本体(デバッガ、開発実機)と製品版の本体との間では 3DSダウンロードプレイを行うことができません。

補足:

子機プログラムのサイズはインポート後のサイズです。DevMenu の SDMC タブまたは HIO タブで子機プログラムを選択したときに表示される Required Size で確認することができます。

アプリケーションで 3DSダウンロードプレイに対応するには、CTR-SDK で用意されている DLP ライブラリを使用します。

クライアント側のプログラムはシステムアプリで用意されていますので、配信のためにアプリケーションが準備しなければならないのは、配信する子機プログラムと配信を制御するためのサーバーコードです。また、擬似ダウンロードプレイ子機の機能をサポートするためのクラスも用意されています。通信処理などはライブラリ内部で行われますので、アプリケーションは状態を確認して必要な関数を呼び出すだけです。

以下の図は、3DSダウンロードプレイで行うサーバー側での処理の流れを、親機(サーバー)と子機(クライアント)の状態の遷移を軸に図示したものです。

図 3-2. 3DSダウンロードプレイで行う処理の流れ(サーバー側)

CLIENT_SERVER_STATE_INVALID STATE_INVALID CLIENT_STATE_DISCONNECTED_NETWORK CLIENT_STATE_SCANNING CLIENT_STATE_WAITING_CONNECT CLIENT_STATE_WAITING_ACCEPT CLIENT_STATE_DOWNLOAD_COMPLETE CLIENT_STATE_REBOOTING SERVER_STATE_OPENED_SESSIONS SERVER_STATE_DISTRIBUTING SERVER_STATE_COMPLETE_DISTRIBUTION SERVER_STATE_REBOOTING_CLIENTS SERVER_STATE_INITIALIZED Initialize() OpenSessions() AcceptClient() StartDistribute() Finalize() RebootAllClients() 初期化 リブート スキャン開始 サーバーに接続 サーバー側クライアント側 呼び出す関数 クライアントの処理 クライアントの状態 クライアントの状態(サーバーから見えない) 処理の流れ DisconnectClient() CLIENT_STATE_WAITING_INVITE サーバーの状態 クライアントのシステム更新が必要な場合、この状態のままシステムアップデート(ダウンロード、リブート、再接続)が行われます。 接続を拒否(クライアントは切断されます) 接続を許可 CLIENT_STATE_DOWNLOADING CLIENT_STATE_JOINED_SESSION

 

注意:

3DSダウンロードプレイを行うと、本体バージョンの低い開発実機上のクライアントはダウンロード完了後に FatalError になることがありますので、サーバーとクライアントの本体バージョンをそろえておく必要があります。実際の製品では、カードアプリならば無線通信によるクライアントのシステムアップデートが行われます。ダウンロードアプリならば本体更新開始直後に通信が中断します。

3.2.1. 初期化

サーバーコードで必要な関数は nn::dlp::Server クラスにまとめられています。DLP ライブラリ自体の初期化は必要なく、nn::dlp::Server クラスの Initialize() を呼び出してサーバーの初期化を行います。

コード 3-23. 初期化
static nn::Result nn::dlp::Server::Initialize(
                bool* pNotice, nn::Handle eventHandle, u8 maxClientNum,
                u8 childIndex, void* pBuffer, size_t bufferSize,
                size_t blockBufferSize = MIN_NETWORK_BLOCK_BUFFER_SIZE * 2,
                size_t blockBufferNum = MIN_NETWORK_BLOCK_BUFFER_NUM);
static size_t nn::dlp::Server::GetBufferSize(u8 maxClientNum,
                size_t blockBufferSize = MIN_NETWORK_BLOCK_BUFFER_SIZE * 2,
                size_t blockBufferNum = MIN_NETWORK_BLOCK_BUFFER_NUM);

pNotice には、ダウンロードプレイの警告メッセージを表示しなければならないかどうかが格納されます。true が返されたときは、ガイドラインに従ってメッセージを表示してください。メッセージは、ダウンロードアプリがサーバーになったときに、システムバージョンの低いクライアントからの接続が警告なく切断されることへの警告メッセージです。なお、今後の更新でシステムがダウンロードアプリによるシステムアップデートに対応した場合、メッセージの表示は必要なくなり pNoticetrue が返されなくなります。システムアップデートの対応状況に応じてメッセージ表示の有無を切り替えられるよう、必ず pNotice の結果に応じてメッセージを表示してください。また、カードアプリでは pNoticetrue が返されることはありません。しかし、pNotice に応じたメッセージ表示を組み込んでおくと、 カードアプリをダウンロードアプリとしてリリースする際にソースコードの変更が不要になります。

eventHandle には、3DSダウンロードプレイからの通知を待つ nn::os::Event クラスのハンドルを渡します。ハンドルを渡すイベントはアプリケーションで初期化する必要があります。

maxClientNum には、サーバーに接続可能なクライアントの最大数を 1 ~ MAX_CLIENT_NUM の範囲で指定します。

childIndex には、配信する子機プログラムのインデックス(rsf ファイルの ChildIndex)を指定してください。クライアントに表示されるアイコンやタイトルなどは、この引数で指定された子機プログラムのヘッダ情報から取得されます。

pBuffer bufferSize には作業バッファとそのサイズ、blockBufferSizeblockBufferNum にはブロックバッファ(子機プログラムの読み出しバッファ)のサイズと個数を渡します。作業バッファはアプリケーションでデバイスメモリ以外から確保し、先頭アドレスが nn::dlp::Server::BUFFER_ALIGNMENT ( 4096 Byte ) アライメントでサイズが GetBufferSize()maxClientNumblockBufferSizeblockBufferNum を渡して取得したサイズ以上の nn::dlp::Server::BUFFER_UNITSIZE ( 4096 ) の倍数でなければなりません。引数に範囲外の値を指定した場合、GetBufferSize() は不定値を返します。

ServerWithName クラスは Server クラスの Initialize() にユーザー名の指定機能を追加したものです。このクラスを利用してユーザー名を設定する場合は、必ず UGC のガイドラインに従って NG ワードのチェックを行ってください。チェックの結果、NG ワードが含まれている場合は Initialize() に渡す構造体の isNgUserNametrue にし、userName には伏せ文字などの加工を行っていないユーザー名をそのまま設定します。ServerWithName クラスの Initialize() 以外の動作は Server クラスと同じです。

Initialize() で返される可能性のある返り値とその原因を以下に示します。

表 3-8. Initialize() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultAlreadyOccupiedWirelessDevice

無線通信モジュールがほかの無線処理で占有されています。インフラストラクチャ通信などを終了させてください。

ResultOutOfRange

範囲外の値を引数に指定しています。

ResultInvalidPointer

不正なポインタを引数に指定しています。

ResultInvalidHandle

不正なハンドルを引数に指定しています。

ResultInternalError

ライブラリ内で回復不能なエラーが発生しました。

ResultFailedToAccessMedia

指定されたインデックスの子機プログラムが存在しないか、カードが抜けている可能性があります。

ResultChildTooLarge

子機プログラムが配信可能なサイズを超えています。

ResultInvalidRegion

親機と子機プログラムのリージョンが異なっています。

ResultWirelessOff

無線通信ができない状態(スリープ中または無線オフモード)です。

3.2.2. 状態の確認

サーバー側の状態を確認する方法には、初期化で渡したイベントがシグナル状態になったときに GetEventDesc() でイベントの詳細を取得する方法と、定期的に GetState() で状態を取得する方法があります。

コード 3-24. 状態の確認
static nn::Result nn::dlp::Server::GetEventDesc(
                                nn::dlp::EventDesc* pEventDesc);
static nn::Result nn::dlp::Server::GetState(nn::dlp::ServerState* pState);

GetEventDesc() で取得することのできるイベントの詳細は 32 個までキューに保持され、いっぱいになると古いものから捨てられます。イベントがない場合はすぐに処理を戻し、返り値に nn::dlp::ResultNoData が返されます。

イベントのシグナル状態を監視しなければサーバーとして機能できないわけではありません。定期的に GetState() で状態を確認し、適宜処理を行う方がサーバーコードの実装は簡単になるでしょう。

補足:

サーバーの実装には、GetState() による状態のポーリングを強く推奨します。セッション参加の受付中やダウンロード中に、スリープに移行したり無線オフモードに切り替えられたりして、通信状態を維持できなくなったときは必ずエラー状態になるため、エラーへの対処が容易になります。

GetState() で取得することのできるサーバーの状態は下表のとおりです。

表 3-9. サーバーの状態

定義

説明

SERVER_STATE_INVALID

まだ初期化を行っていない状態です。

SERVER_STATE_INITIALIZED

初期化完了後の状態です。

SERVER_STATE_OPENED_SESSIONS

セッションへの参加を受け付けている状態です。クライアントの募集はこの状態でのみ行えます。

SERVER_STATE_DISTRIBUTING

配信を行っている状態です。

SERVER_STATE_COMPLETE_DISTRIBUTION

配信が完了した状態です。

SERVER_STATE_REBOOTING_CLIENTS

配信後にサーバーがクライアントをリブートしている状態です。すべてのクライアントが切断されたことを確認してから 3DSダウンロードプレイを終了してください。

SERVER_STATE_ERROR

エラーが発生し、再初期化が必要な状態です。この状態に遷移するのは、Initialize() から Finalize() までにスリープ状態への移行や無線オフモードへの切り替えが発生したときや、配信後のリブート以外で配信中にクライアントが切断したとき、カードが途中で抜けてしまったときです。

GetEventDesc()GetState() で返される可能性のある返り値とその原因を以下に示します。

表 3-10. GetEventDesc()、GetState() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultNoData

取得できるデータがありません。

ResultInvalidPointer

不正なポインタを引数に指定しています。

3.2.3. セッションの開始と停止

3DSダウンロードプレイの処理で、クライアントからの配信要求を受け付け可能になる状態から、配信を完了してクライアントをリブートするまでをセッションと呼びます。

セッションの開始と停止は OpenSessions()CloseSessions() で行うことができます。

コード 3-25. セッションの開始と停止
static nn::Result nn::dlp::Server::OpenSessions(
                                bool isManualAccept = false, u8 channel = 0);
static nn::Result nn::dlp::Server::CloseSessions();

isManualAccept true を渡してセッションを開始した場合は、セッションに参加しようとしているクライアントへの接続の許可と拒否をアプリケーションで行うことができます。引数を省略した場合や false を渡して開始した場合は、自動的に接続が許可されます。

channel には、通信で使用するチャンネルを 0, 1, 6, 11 のいずれかから指定します。0 を指定した場合はチャンネルを自動で選択します。通常は何も指定する必要はありません。

注意:

製品版の本体での実行時には、チャンネルが常に自動で選択されます。

セッションの途中でクライアントが切断されるなど、サーバーの状態遷移が止まってしまったときは、CloseSessions() を呼び出してセッションを停止してください。また、配信後にリブートされたクライアントが必ずしもサーバーに再接続するとは限りませんので、サーバーコードはユーザーが任意のタイミングでセッションを停止できるように実装しなければなりません。

OpenSessions()CloseSessions() で返される可能性のある返り値とその原因を以下に示します。

表 3-11. OpenSessions()、CloseSessions() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultInvalidState

不適切な状態で関数を呼び出しました。

ResultOutOfRange

範囲外の値を引数に指定しています。

ResultWirelessOff

無線通信ができない状態(スリープ中または無線オフモード)です。

3.2.4. 接続状況の確認

セッションを開始したあとは、エラーが発生していない状態ならば、GetConnectingClients() で接続されているクライアントの数とそのノード ID を確認することができます。また、取得したノード ID からクライアントの情報や状態を GetClientoInfo()GetClientState() で確認することができます。

コード 3-26. 接続状況の確認
static nn::Result nn::dlp::Server::GetConnectingClients(
                        u16* pNum, u16 clients[], u16 size);
static nn::Result nn::dlp::Server::GetClientInfo(
                        nn::dlp::NodeInfo* pClientInfo, u16 nodeId);
static nn::Result nn::dlp::Server::GetClientState(
                        nn::dlp::ClientState* pClientState, u16 nodeId);
static nn::Result nn::dlp::Server::GetClientState(
                        nn::dlp::ClientState* pClientState, size_t* pTotalNum,
                        size_t* pDownloadedNum, u16 nodeId);

GetConnectingClients() の引数 pNum にはセッションに接続されているクライアントの数が、pClients にはクライアントのノード ID の一覧が、それぞれ格納されます。pNum に渡すのが u16 型の変数へのポインタ、clients に渡すのが u16 型の配列であることに注意してください。size には clients に指定した配列のサイズを渡します。配列のサイズは初期化時に指定した maxClientNum 以上でなければなりません。

GetClientInfo() または GetClientState() の引数 nodeId には、情報を取得したいクライアントのノード ID を指定してください。存在しないクライアントやサーバーのノード ID を指定した場合は nn::dlp::ResultNoData が返されます。

GetClientInfo() の引数 pClientoInfo で指定された nn::dlp::NodeInfo 構造体に格納される情報は、「3.1.9.3. ノード情報の取得」で取得するノード情報と同じです。ユーザーの名前の表示などに利用することができます。

GetClientState() の引数 pClientState で指定された nn::dlp::ClientState 型のポインタには、クライアントの状態が格納されます。pTotalNum には子機プログラムの総パケット数が、pDownloadNum にはダウンロード済みのパケット数がそれぞれ格納されます。pTotalNum には 0 が格納されることがありますので、進捗の表示で除数に使用する場合は注意が必要です。

pClientState にはクライアントの状態が格納されています。

表 3-12. クライアントの状態(サーバーから確認できる状態のみ)

定義

説明

CLIENT_STATE_WAITING_INVITE

サーバーからセッション参加を許可され、セッションに招待されるのを待っている状態です。

CLIENT_STATE_WAITING_ACCEPT

ネットワークに接続されたクライアントが、サーバーからのセッション参加許可を待っている状態です。

CLIENT_STATE_JOINED_SESSION

セッションに参加している状態です。

CLIENT_STATE_DOWNLOADING

配信が開始され、ダウンロードを行っている状態です。配信開始直後などで、サーバーからデータを受け取っていない場合も含みます。

CLIENT_STATE_DOWNLOAD_COMPLETE

ダウンロードが完了した状態です。

GetConnectingClients()GetClientInfo()GetClientState() で返される可能性のある返り値とその原因を以下に示します。

表 3-13. GetConnectingClients()、GetClientInfo()、GetClientState() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultInvalidState

不適切な状態で関数を呼び出しました。

ResultInvalidPointer

不正なポインタを引数に指定しています。

ResultNoData

指定されたノード ID を持つクライアントは接続されていません。

3.2.5. 接続の許可と拒否

セッションを開始してから配信を開始するまでの間、サーバーはクライアントからのセッションへの参加要求を受け付けます。引数 isManualAccepttrue を渡してセッションを開始していた場合、アプリケーションでクライアントに対するセッションへの接続許可を制御することができます。

コード 3-27. 接続の許可と拒否
static nn::Result nn::dlp::Server::AcceptClient(u16 nodeId);
static nn::Result nn::dlp::Server::DisconnectClient(u16 nodeId);

AcceptClient() で接続を許可し、DisconnectClient() で接続を拒否することができます。 クライアントの状態が CLIENT_STATE_WAITING_ACCEPT でなければ、これらの関数を呼び出して接続の許可や拒否を行うことができません。

AcceptClient()DisconnectClient() で返される可能性のある返り値とその原因を以下に示します。

表 3-14. AcceptClient()、DisconnectClient() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultInvalidState

不適切な状態で関数を呼び出しました。

ResultNoData

指定されたノード ID を持つクライアントは接続されていません。

ResultWirelessOff

無線通信ができない状態(スリープ中または無線オフモード)です。

3.2.6. 配信の開始

配信先のクライアントを確定したあとは、StartDistribute() を呼び出してセッションへの参加を締め切り、クライアントへの配信を開始することができます。

コード 3-28. 配信の開始
static nn::Result nn::dlp::Server::StartDistribute();

配信を開始し、サーバーの状態が SERVER_STATE_PREPARING_FOR_TITLE_DISTRIBUTION に遷移します。

サーバーの状態が SERVER_STATE_COMPLETE_DISTRIBUTION に遷移して配信が完了するまで、クライアントの接続状況を確認して進捗などを表示しながら待機するだけです。

注意:

配信が開始されても、状態が CLIENT_STATE_JOINED_SESSION のままになっているクライアントはシステム更新が必要なクライアントです。その状態のままシステムアップデート(ダウンロード、リブート、再接続)され、クライアントの状態が CLIENT_STATE_DOWNLOADING に遷移するまで、サーバーはクライアントの再接続を待ち受けている状態になっています。しかし、ダウンロードのキャンセルや電源の切断、無線オフモードへの切り替え、通信品質の悪化など、サーバーとクライアントが通信できない状況になる可能性があり、すべてのクライアントが必ずしも再接続されるとは限りません。そのため、ユーザーの操作でセッションをいつでも停止(CloseSessions())できるように実装してください。

StartDistribute() で返される可能性のある返り値とその原因を以下に示します。

表 3-15. StartDistribute() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultInvalidState

不適切な状態で関数を呼び出したか、配信すべき子機が存在しません。

ResultWirelessOff

無線通信ができない状態(スリープ中または無線オフモード)です。

ResultFailedToAccessMedia

メディアにアクセスできませんでした。カードが抜けている可能性があります。

補足:

配信中にサーバーのカード抜けなどによって接続が切断されても、クライアントが中断画面に遷移しないのは仕様です。また、アプリケーションでこの事象に対応する必要はありません。

3.2.7. クライアントのリブート

配信が完了し、サーバーの状態が SERVER_STATE_COMPLETE_DISTRIBUTION に遷移したときは、セッションに参加していたすべてのクライアントをリブートするために RebootAllClients() を呼び出してください。

コード 3-29. クライアントのリブート
static nn::Result nn::dlp::Server::RebootAllClients(
                        const char passpharase[] = NULL);

passphrase には、リブート後の子機とローカル通信を行うために必要となる、ネットワーク構築時のパスフレーズを指定します。指定可能な文字列の長さは NULL を含めて、最大 MAX_CHILD_UDS_PASSPHRASE_LENGTH 文字です。パスフレーズはダウンロードセッションごとに異なる値を指定し、容易に推測できるような文字列を指定しないでください。

配信完了後にこの関数を呼び出した場合は、すべてのクライアントのリブートを行い、サーバーの状態が SERVER_STATE_REBOOTING_CLIENTS に遷移します。以降はクライアントの接続状況を確認し、すべてのクライアントの切断を確認してから終了処理を行ってください。

RebootAllClients() で返される可能性のある返り値とその原因を以下に示します。

表 3-16. RebootAllClients() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultInvalidState

不適切な状態で関数を呼び出しました。

3.2.8. 終了処理

すべてのクライアントのリブートとセッションからの切断を確認したあとは、Finalize() でサーバーの終了処理を行うことができます。また、蓋が閉じられたことによるスリープ状態への遷移時には Finalize() を呼び出さなければなりません。

コード 3-30. 終了処理
static nn::Result nn::dlp::Server::Finalize();

終了処理の完了で 3DSダウンロードプレイは終了します。

配信した子機プログラムとローカル通信を行う場合は、終了処理以降に UDS ライブラリでネットワークを構築してクライアントの接続を待ちます。しかし、必ずしもクライアントからの再接続が行われるとは限らないことには注意が必要です。

Finalize() で返される可能性のある返り値とその原因を以下に示します。

表 3-17. Finalize() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

3.2.9. 子機プログラムについて

3DSダウンロードプレイで子機プログラムをダウンロードしたあと、クライアントはサーバーの指示でリブートします。子機プログラムでサーバーだった本体を親機とのローカル通信を行う場合の補助として、親機への再接続のための情報を nn::dlp::GetRebootInfo() で取得することができます。

コード 3-31. 再接続情報の取得
nn::Result nn::dlp::GetRebootInfo(nn::dlp::RebootInfo* pRebootInfo);

この関数は DLP ライブラリの初期化を行わずに呼び出すことができます。 pRebootInfo には、再接続のための情報を格納する nn::dlp::RebootInfo 構造体へのポインタを渡してください。

コード 3-32. nn::dlp::RebootInfo 構造体の定義
typedef struct
{
    u8      bssid[6];
    char    passphrase[MAX_CHILD_UDS_PASSPHRASE_LENGTH];
    NN_PADDING1;
} nn::dlp::RebootInfo;

bssidpassphrase には親機が構築するネットワークの BSSID とパスフレーズが格納されています。

これらの情報をもとに、どのネットワークに接続するのかをアプリケーションが自動的に判断することもできます。

3.2.9.1. 組み込み方法

rsf ファイルで指定する、子機プログラムとサーバー側のアプリケーションのユニーク ID は同じでなければなりません。また、子機プログラムの Category は DlpChild でなければなりません。なお、作成する子機プログラムに CTR アイコンは必要ですが、CTR タイトルバナーは必要ありません。

アプリケーションのビルドに OMake を利用している場合は、サーバー側のアプリケーションの OMakefile のビルド変数 "CHILD_APPS[]" に子機プログラムの cia ファイルを指定してください。

OMake を利用していない場合は、コマンドラインツールの ctr_makeciaarchive で cfa ファイルを作成し、ctr_makerom の引数 "-content" で cci ファイルに含めてください。

3.2.9.2. 強制ダウンロード

通常、クライアントは自身の持っていない子機プログラムか、自身にインポートされているものより新しい子機プログラムでなければダウンロードしませんが、Config ツールの強制インポート設定を有効にすることでクライアントに子機プログラムのバージョンを無視して強制的にダウンロードさせることができます。

3.2.9.3. デバッグ方法

子機プログラムのデバッグは、ダウンロードプレイで子機プログラムの実行が始まったあとに、以下の手順で行うことができます。

  • デバッガの ATTACHA コマンドで子機プログラムにアタッチする
  • 実行が一時停止するので、LS コマンドで axf ファイルからデバッグ情報を読み込む
  • 実行を再開する

3.2.9.4. 子機プログラムの判定

nn::dlp::IsChild() を呼び出して、実行中のアプリケーションが子機プログラムであるかどうかを判定することができます。

コード 3-33. 子機プログラムの判定
bool nn::dlp::IsChild();

この関数は nn::dlp::Servernn::dlp::FakeClient を使用しているとき(Initialize() を呼び出してから Finalize() が完了するまで)は呼び出さないでください。

3.2.10. 擬似クライアント

擬似クライアント(nn::dlp::FakeClient クラス)は、ダウンロードとリブートを行わないクライアントとして、アプリケーションからダウンロードプレイのセッションに参加するために用意されています。

擬似クライアントを利用すると、ダウンロードプレイで参加する 3DS と同じセッションに参加することができるため、アプリケーションと子機プログラムが混在するネットワークでの無線通信へと円滑に移行することができます。また、ソフトを持っていないユーザーとソフトを持っているユーザーをネットワークに混在させることができるという利点以外にも、セッションの管理などを DLP ライブラリにまかせることができるという利点があります。

注意:

擬似クライアントを利用するタイトルをパッチや改訂版で修正する場合は、リマスターバージョンの異なる擬似クライアントが DLP サーバーに接続する場合について配慮する必要があります。

詳しくは「パッチマニュアル」の「疑似クライアント機能」を参照してください。

以下の図は、3DSダウンロードプレイで行う擬似クライアント側での処理の流れを、親機(サーバー)と子機(擬似クライアント)の状態の遷移を軸に図示したものです。

図 3-3. 3DSダウンロードプレイで行う処理の流れ(擬似クライアント側)

CLIENT_SERVER_STATE_INVALID STATE_INVALID CLIENT_STATE_DISCONNECTED_NETWORK CLIENT_STATE_SCANNING CLIENT_STATE_WAITING_CONNECT CLIENT_STATE_WAITING_ACCEPT CLIENT_STATE_JOINED_SESSION CLIENT_STATE_REBOOTING SERVER_STATE_OPENED_SESSIONS SERVER_STATE_DISTRIBUTING SERVER_STATE_COMPLETE_DISTRIBUTION SERVER_STATE_REBOOTING_CLIENTS SERVER_STATE_INITIALIZED 初期化 セッション開始 接続許可 終了 クライアントのリブート Initialize() Finalize() StartScan() StartFakeSession() サーバー側(擬似)クライアント側 サーバーの処理 呼び出す関数 クライアントの状態 クライアントの状態(サーバーから見えない) 処理の流れ CLIENT_STATE_WAITING_INVITE CLIENT_STATE_DOWNLOAD_COMPLETE CLIENT_STATE_DOWNLOADING サーバーの状態 配信開始 サーバーの状態(クライアントから見えない)

3.2.10.1. 初期化

擬似クライアントのコードで必要な関数は nn::dlp::FakeClient クラスにまとめられています。DLP ライブラリ自体の初期化は必要なく、nn::dlp::FakeClient クラスの Initialize() を呼び出して擬似クライアントの初期化を行います。

コード 3-34. 擬似クライアントの初期化
static nn::Result nn::dlp::FakeClient::Initialize(
u8 scanNum, nn::Handle eventHandle, 
void* pBuffer, const size_t bufferSize);
static size_t nn::dlp::FakeClient::GetBufferSize(u8 scanNum);

scanNum には、1 回のスキャン要求で取得可能なタイトルの最大数を 1 ~ MAX_SCAN_NUM の範囲で指定します。

eventHandle には、3DSダウンロードプレイからの通知を待つ nn::os::Event クラスのハンドルを渡します。ハンドルを渡すイベントはアプリケーションで初期化する必要があります。

pBuffer bufferSize には作業バッファとそのサイズを渡します。作業バッファはアプリケーションでデバイスメモリ以外から確保し、先頭アドレスが nn::dlp::FakeClient::BUFFER_ALIGNMENT ( 4096 Byte ) アライメントでサイズが GetBufferSize()scanNum を渡して取得したサイズ以上の nn::dlp::FakeClient::BUFFER_UNITSIZE ( 4096 ) の倍数でなければなりません。引数に範囲外の値を指定した場合、GetBufferSize() は不定値を返します。

FakeClientWithName クラスは FakeClient クラスの Initialize() にユーザー名の指定機能を追加したものです。このクラスを利用してユーザー名を設定する場合は、必ず UGC のガイドラインに従って NG ワードのチェックを行ってください。チェックの結果、NG ワードが含まれている場合は Initialize() に渡す構造体の isNgUserNametrue にし、userName には伏せ文字などの加工を行っていないユーザー名をそのまま設定します。FakeClientWithName クラスの Initialize() 以外の動作は FakeClient クラスと同じです。

Initialize() で返される可能性のある返り値とその原因を以下に示します。

表 3-18. Initialize() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultAlreadyOccupiedWirelessDevice

無線通信モジュールがほかの無線処理で占有されています。インフラストラクチャ通信などを終了させてください。

ResultOutOfRange

範囲外の値を引数に指定しています。

ResultInvalidPointer

不正なポインタを引数に指定しています。

ResultInvalidHandle

不正なハンドルを引数に指定しています。

ResultInternalError

ライブラリ内で回復不能なエラーが発生しました。

ResultWirelessOff

無線通信ができない状態(スリープ中または無線オフモード)です。

3.2.10.2. 状態の確認

擬似クライアント側の状態を確認する方法には、初期化で渡したイベントがシグナル状態になったときに GetEventDesc() でイベントの詳細を取得する方法と、定期的に GetMyStatus() で状態を取得する方法があります。

コード 3-35. 擬似クライアントの状態の確認
static nn::Result nn::dlp::FakeClient::GetEventDesc(
                        nn::dlp::EventDesc* pEventDesc);
static nn::Result nn::dlp::FakeClient::GetMyStatus(
                        nn::dlp::ClientStatus* pStatus);

GetEventDesc() で取得することのできるイベントの詳細は 32 個までキューに保持され、いっぱいになると古いものから捨てられます。イベントがない場合はすぐに処理を戻し、返り値には nn::dlp::ResultNoData が返されます。

イベントのシグナル状態を監視しなければ擬似クライアントとして機能できないわけではありません。定期的(ゲームフレームごとなど)に GetMyStatus() で状態を確認し、適宜処理を行う方がコードの実装は簡単になるでしょう。

補足:

擬似クライアントの実装には、GetMyStatus() による状態のポーリングを強く推奨します。サーバーのスキャン中やダウンロード中に、スリープに移行したり無線オフモードに切り替えられたりして、通信状態を維持できなくなったときは必ずエラー状態になるため、エラーへの対処が容易になります。

GetMyStatus() で取得することのできる nn::dlp::ClientStatus は以下のように定義されています。

コード 3-36. nn::dlp::ClientStatus の定義
typedef struct
{
    u16             nodeId;
    SessionType     sessionType;
    ClientState     state;
    size_t          totalNum;
    size_t          downloadedNum;
} nn::dlp::ClientStatus;

state メンバに擬似クライアントの状態が格納されます。格納される値の定義は下表のとおりです。

表 3-19. 擬似クライアントの状態(擬似クライアントで確認できる状態のみ)

定義

説明

CLIENT_STATE_INVALID

まだ初期化を行っていない状態です。

CLIENT_STATE_DISCONNECTED_NETWORK

初期化完了後、またはサーバーから切断された状態です。

CLIENT_STATE_SCANNING

サーバーのスキャン中です。

CLIENT_STATE_WAITING_CONNECT

ネットワークへの接続を待っている状態です。

CLIENT_STATE_WAITING_INVITE

セッションへの招待を待っている状態です。

CLIENT_STATE_JOINED_SESSION

セッションに参加している状態です。

CLIENT_STATE_DOWNLOADING

配信が開始され、ダウンロードを行っている状態です。擬似クライアントでは、サーバーからデータを受け取りません。

CLIENT_STATE_DOWNLOAD_COMPLETE

ダウンロードが完了した状態です。

CLIENT_STATE_RECONNECTING_NETWORK

擬似クライアントでは、この状態に遷移することはありません。

CLIENT_STATE_REBOOTING

サーバーからのリブート要求を受信した状態です。

CLIENT_STATE_ERROR

エラーが発生し、再初期化が必要な状態です。

Initialize() から Finalize() までにスリープ状態への移行や無線オフモードへの切り替えが発生したときや、配信中にサーバーから切断されたり、配信中にほかのクライアントが切断したときにも、この状態に遷移します。

state メンバ以外のメンバに格納される値は、擬似クライアントの実装には必要ありません。

3.2.10.3. サーバーのスキャン

サーバー(タイトル)の一覧を取得するために、ダウンロードプレイのセッションを開始しているサーバーをスキャンします。

コード 3-37. サーバーのスキャン
static nn::Result nn::dlp::FakeClient::StartScan(
                u32 uniqueId, u8 childIndex, const u8* pMac = NULL);
static nn::Result nn::dlp::FakeClient::StopScan();

StartScan() を呼び出して、サーバーのスキャンを開始します。uniqueIdchildIndexpMac を指定することで、スキャン対象のタイトルやサーバーを絞り込むことができます。

注意:

異なるアプリケーションのサーバーを選択すると、そのサーバーの動作に悪影響を及ぼす可能性があります。擬似クライアントは uniqueIdchildIndex を必ず指定し、特定のタイトルを配信しているサーバーだけをスキャンの対象にしてください。

サーバーのスキャンは StopScan() で停止するまで続けられ、スキャンの結果は再度 StartScan() を呼び出すまで、最大で Initialize() の 引数 scanNum に指定した数が保持されます。

StartScan() で返される可能性のある返り値とその原因を以下に示します。

表 3-20. StartScan() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultInvalidState

不適切な状態(CLIENT_STATE_DISCONNECTED_NETWORK 以外)で関数を呼び出しました。

3.2.10.4. スキャン結果の取得

スキャン結果は GetTitleInfo() を呼び出して、タイトル情報(nn::dlp::TitleInfo)の一覧として取得します。また、取得したタイトル情報をもとに、GetServerInfo() でサーバー情報(nn::dlp::ServerInfo)を取得することができます。これらの関数は擬似クライアントの状態が CLIENT_STATE_INVALIDCLIENT_STATE_ERROR で呼び出すと nn::dlp::ResultInvalidState を返します。

コード 3-38. スキャン結果の取得
static nn::Result nn::dlp::FakeClient::GetTitleInfo(
        nn::dlp::TitleInfo* pTitleInfo, bool isReset = false);
static nn::Result nn::dlp::FakeClient::GetTitleInfo(
        nn::dlp::TitleInfo* pTitleInfo, 
        const u8* pMac, u32 uniqueId, u8 childIndex);
static nn::Result nn::dlp::FakeClient::GetServerInfo(
        nn::dlp::ServerInfo* pServerInfo, const u8* pMac);
static nn::Result nn::dlp::FakeClient::DeleteScanInfo(const u8* pMac);

GetTitleInfo() のオーバーロードのうち、引数に isReset を持つものはタイトル情報一覧の取得に使用するためのものです。通常は、まだ GetTitleInfo() で取得していないタイトル情報を取得するように、isReset には false を指定します。返り値のインスタンスで IsSuccess()true を返したときは、新たなタイトル情報を取得しています。未取得のタイトル情報がない場合や、これ以上タイトル情報を保持できない場合は、nn::dlp::ResultNoData が返されます。isResettrue を指定すれば、取得済みのタイトル情報を含め、タイトル情報の一覧を先頭から再取得することができます。StartScane() でスキャンを開始してから StopScan() でスキャンを中止するまでサーバーのスキャンは続けられ、最大で Initialize() で指定した scanNum 個のタイトル情報を保持することができます。タイトル情報の一覧は StartScan() を再度呼び出したときにクリアされます。

引数に pMacuniqueIdchildIndex を持つオーバーロードは、サーバーの MAC アドレス、ユニーク ID、子機プログラムのインデックスが分かっている場合に使用するためのものです。

nn::dlp::TitleInfo は、以下のように定義されています。

コード 3-39. nn::dlp::TitleInfo の定義
typedef struct
{
    u32                         uniqueId;
    u8                          childIndex;
    NN_PADDING3;
    u8                          mac[6];
    u16                         programVersion;
    bit8                        ratingInfo[RATING_INFO_SIZE];
    wchar_t                     shortTitleName[SHORT_TITLE_NAME_LENGTH];
    wchar_t                     longTitleName[LONG_TITLE_NAME_LENGTH];
    nn::dlp::IconInfo           icon;
    u32                         importSize;
    nn::cfg::CfgRegionCode      region;
    bool                        ulcd;
    NN_PADDING2;
} nn::dlp::TitleInfo;

uniqueIdchildIndexmac の 3 つはタイトル情報を特定するために利用します。

GetServerInfo() は、サーバーの情報の取得に使用します。pMac には、サーバー情報を取得したいサーバーの MAC アドレスを指定します。指定された MAC アドレスの機器がない、もしくはダウンロードプレイのサーバーとして動作していない場合は nn::dlp::ResultNoData が返されます。

サーバー情報にはリンクレベルや接続されているクライアントの一覧などの情報が含まれており、以下のように定義されています。

コード 3-40. nn::dlp::ServerInfo の定義
typedef struct
{
    u8                      mac[6];
    u8                      channel;
    nn::uds::LinkLevel      linkLevel;
    u8                      maxNodeNum;
    u8                      nodeNum;
    bit16                   dlpVersion;
    NN_PADDING4;
    NodeInfo                nodeInfo[MAX_NODE_NUM];
    nn::os::Tick            lastUpdateTick;
} nn::dlp::ServerInfo;

状態がスキャン中(CLIENT_STATE_SCANNING)であれば、リンクレベル(linkLevel)、サーバーを含んだ接続ノード数(nodeNum)、ノードの詳細情報(nodeInfo) には、その時点での情報が反映されています。それ以外の状態では変化しませんので、「3.2.10.6. 副次的な情報の取得」で紹介する関数を利用してください。

ダウンロードプレイのサーバーのノード ID には、ローカル通信での Master と同じように、必ず 1 が割り当てられています。サーバー情報のノード情報(nodeInfo)のうち、ノード ID(nodeId)が 1 であるノード情報のユーザー名(userName.userName)を表示するなど、スキャン対象を絞った状態でも複数のサーバーが一覧に表示される可能性を考慮してください。ただし、ユーザー名の情報(userName)の isNgUserNametrue の場合は、ユーザー名に NG ワードが含まれていますので、ガイドラインに従って処理しなければなりません。

スキャン結果から特定のタイトル情報を削除したいときは DeleteScanInfo() を呼び出します。pMac に指定された MAC アドレスのサーバーのスキャン結果が削除されます。一定時間サーバーの情報が更新されなかった場合などに使用してください。指定された MAC アドレスのサーバーが存在しなかった場合は nn::dlp::ResultNoData を返します。また、状態が CLIENT_STATE_DISCONNECTED_NETWORK または CLIENT_STATE_SCANNING 以外のときに呼び出された場合は、nn::dlp::ResultInvalidState を返します。

3.2.10.5. セッションへの参加

サーバー(タイトル情報)の一覧から接続するサーバーを選択し、StartFakeSession() でセッションに参加します。擬似クライアントは、システムアプリのダウンロードプレイでセッションに参加してきたクライアントと同じようにサーバーからは見えますが、子機プログラムのダウンロードは行われません。

補足:

セッションに参加する前に、必ず StopScan() でサーバーのスキャンを停止しておいてください。

コード 3-41. セッションへの参加
static nn::Result nn::dlp::FakeClient::StartFakeSession(
                        const u8* pMac, u32 uniqueId, u8 childIndex);
static nn::Result nn::dlp::FakeClient::StopFakeSession();

引数 pMacuniqueIdchildIndex に指定する値は、GetTitleInfo() で取得したタイトル情報から得ることができます。

StartFakeSession() で返される可能性のある返り値とその原因を以下に示します。

表 3-21. StartFakeSession() で返される可能性のある返り値

返り値

原因

IsSuccess()true を返す

処理に成功しました。

ResultInvalidState

不適切な状態(CLIENT_STATE_DISCONNECTED_NETWORK 以外)で関数を呼び出しました。

ResultNoData

pMac uniqueIdchildIndex で指定されたタイトルはありません。

ResultWirelessOff

無線通信ができない状態(スリープ中または無線オフモード)です。

ResultNotFoundServer

pMac で指定されたサーバーはありません。

ResultServerIsFull

すでに接続クライアント数が最大に達していたため、サーバーに接続できませんでした。接続クライアント数が減少しない限り、接続できません。

ResultDeniedFromServer

サーバーが子機プログラムの配信中のため、接続を拒否されました。

ResultConnectionTimeout

一定時間以内に接続が成立しませんでした。電波状況が悪い場合や、通信負荷が高い場合に返されます。

ResultInternalError

ライブラリ内で不整合によるエラーが発生しました。

セッションの途中で状態の遷移が止まってしまったときは、StopFakeSession() を呼び出してセッションを停止してください。この関数で返されるインスタンスは必ず IsSuccess()true を返します。擬似クライアントもサーバーと同様に、ユーザーが任意のタイミングでセッションを停止できるように実装しなければなりません。

3.2.10.6. 副次的な情報の取得

擬似クライアントの実装には必要ではない、副次的な情報を取得するための関数が用意されています。

コード 3-42. 副次的な情報の取得
static nn::Result nn::dlp::FakeClient::GetConnectingNodes(
                        u8* pNum, u16* pNodeIds, u16 size);
static nn::Result nn::dlp::FakeClient::GetNodeInfo(
                        nn::dlp::NodeInfo* pNodeInfo, u16 nodeId);
static nn::Result nn::dlp::FakeClient::GetLinkLevel(
                        nn::uds::LinkLevel* pLinkLevel);

GetConnectingNodes() は、セッションに接続中のノードの一覧を取得することができます。

GetNodeInfo() は、セッションに接続中のノードの詳細情報を取得することができます。pNodeInfo に返される nn::dlp::NodeInfo はローカル通信の nn::uds::NodeInfomation と同じです。ノードの詳細情報については、「3.1.9.3. ノード情報の取得」を参照してください。

GetLinkLevel() は、サーバーとの間のリンクレベル(通信品質)を取得することができます。pLinkLevel に返される値については、「3.1.9.2. リンクレベルの取得」を参照してください。

3.2.10.7. ダウンロードの終了

擬似クライアントの状態が CLIENT_STATE_DOWNLOAD_COMPLETE に遷移したことは、このクライアントのダウンロードが完了していることを示しているだけで、ほかのクライアントはダウンロード中である可能性があります。ダウンロードプレイのセッション全体としてはまだ完了していませんので、サーバーに接続しているすべてのクライアントがダウンロードを完了するまで待機してください。

サーバーに接続していたすべてのクライアントがダウンロードを完了すると、サーバーはすべてのクライアントに対してリブート要求を送信します。リブート要求を受信すると、擬似クライアントの状態は CLIENT_STATE_REBOOTING へと遷移します。サーバーから指定されたローカル通信のパスフレーズは、この段階でのみ GetPassphrase() で取得することができます。

パスフレーズを取得し終えたら、Finalize() を呼び出して擬似クライアントの終了処理を行ってください。

コード 3-43. パスフレーズの取得と終了処理
static nn::Result nn::dlp::FakeClient::GetPassphrase(char* pPassphrase);
static nn::Result nn::dlp::FakeClient::Finalize();

GetPassphrase()pPassphrase には、MAX_CHILD_UDS_PASSPHRASE_LENGTH バイト以上のバッファを指定してください。CLIENT_STATE_REBOOTING 以外の状態では ResultInvalidState が返されます。

Finalize() は必ず成功しますので、返されるインスタンスの IsSuccess() は常に true を返します。

そのまま親機(サーバー)とのローカル通信を行う場合は、セッションの開始時に選択したサーバーの MAC アドレスと GetPassphrase() で取得したパスフレーズを利用してください。

スリープ状態への移行時や HOMEメニューを起動しなければならないときは、必ず Finalize() を呼び出して、終了処理を行ってください。

3.3. 自動接続

AC(自動接続)ライブラリは、本体設定のネットワーク設定による無線 LAN のアクセスポイントやニンテンドー Wi-Fi USB コネクタ、ニンテンドーゾーン、Wi-Fi ステーション、公共無線 LAN アクセスポイントを経由してのインターネット接続を自動的に行うライブラリです。3DS の無線通信モジュールに直接アクセスするライブラリは用意されていませんので、3DS から外部のサーバーにアクセスする際には AC ライブラリを利用してインターネット接続(もしくは LAN 接続)を確立しなければなりません。

AC ライブラリは本体設定のネットワーク設定に従って無線 LAN のアクセスポイントなどにアクセスします。そのため、事前に本体設定で有効な無線 LAN アクセスポイントへの接続設定を記録しておかなければなりません。

補足:

本体設定を行うメニューの代わりにネットワーク設定の変更を行う、NetworkSetting ツールが用意されています。また、AC ライブラリでは、デバッグ用途でのみ利用可能な関数として、ネットワーク設定 1 を変更する nn::ac::DebugSetNetworkSetting1()、自動接続で確立する接続がインターネットへの接続まで必要なのか、それともアクセスポイントへの接続まででよいのかを指定する nn::ac::DebugSetNetworkArea()、接続対象とするアクセスポイントの種類を ApType 列挙子(表 3-22. nn::ac::ApType 列挙子)の論理和で指定する nn::ac::DebugSetApType() が用意されています。

ただし、これらの関数はデバッグモードに設定(Config ツールで「Debug Setting」の「Debug Mode」を「enable」に設定)されているときにのみ動作します。

AC ライブラリを利用してインターネット接続を確立させると、デーモンマネージャがインフラストラクチャ通信モードで無線通信モジュールを独占し、すれちがい通信などのバックグラウンド通信が行われるのを阻害する可能性があります。

AC ライブラリの関数は処理結果を nn::Result クラスのインスタンスで返します。処理が正常に行われたときは、IsSuccess()true が返されます。

3.3.1. 初期化

AC ライブラリの初期化は nn::ac::Initialize() の呼び出しで行われます。

コード 3-44. AC ライブラリの初期化
nn::Result nn::ac::Initialize();
bool nn::ac::IsInitialized();

nn::ac::ResultAlreadyInitialized が返されたときは、すでにアプリケーションで初期化を行っていることを示します。エラーではありませんが、ライブラリは初期化回数の参照カウンタを保持していますので、Initialize() を呼び出した回数と同じ回数 Finalize() を呼び出さなければなりません。nn::ac::IsInitialized() を呼び出すと、すでに初期化が行われているかどうかを判断することができます。

3.3.2. 自動接続

自動接続処理は nn::ac::CreateDefaultConfig() で作成された接続条件に従って行われます。

コード 3-45. 接続条件の作成
nn::Result nn::ac::CreateDefaultConfig(nn::ac::Config* config);

config に渡された nn::ac::Config 構造体に接続条件が格納されます。構造体はアプリケーションで確保しなければなりません。この引数に NULL を指定した場合は nn::ac::ResultInvalidData が返されます。

作成された接続条件を nn::ac::Connect() または nn::ac::ConnectAsync() の引数 config に渡して自動接続処理を開始します。

コード 3-46. 自動接続
nn::Result nn::ac::Connect(nn::ac::Config& config);
nn::Result nn::ac::ConnectAsync(nn::ac::Config& config, nn::os::Event* event);
nn::Result nn::ac::CancelConnectAsync();
nn::Result nn::ac::GetConnectResult();
bool nn::ac::IsConnected();
注意:

自動接続処理中に EULA 同意のチェックが行われるため、事前に FS ライブラリの初期化を行う必要があります。

nn::ac::ConnectAsync()nn::ac::Connect() の非同期版です。非同期版では、処理が成功したかどうかに関わらず、自動接続処理が終了した時点で event に渡したイベントがシグナル状態になります。イベントがシグナル状態になったあとは、必ず nn::ac::GetConnectResult() で処理結果を取得してください。処理結果を取得するまでローカル通信の開始に失敗し、すれちがい通信が行われない状態になります。自動接続処理を途中でキャンセルする場合は nn::ac::CancelConnectAsync() を呼び出してください。処理がキャンセルされるかキャンセルされる前に処理が成功したときは、nn::ac::ConnectAsync()event に渡されたイベントがシグナル状態になります。処理がキャンセルされたときは、処理結果として nn::ac::ResultCanceled が返されます。

注意:

バックグラウンド通信の接続要求が処理されているときに nn::ac::CancelConnectAsync() を呼び出すと、アプリケーションの接続要求を取り下げて nn::ac::ResultSuccess を返します。

補足:

自動接続の接続先は、ユーザー設定がニンテンドーゾーンやホットスポットよりも優先されます。ユーザー設定内の接続先優先順位は不定ですので、接続先を確実に指定するにはユーザー設定を一つにしてください。デバッグモードでは nn::ac::DebugSetApType() で接続先を指定できます。

自動接続処理が成功すると、インターネット接続が確立したときは nn::ac::ResultWanConnected が、無線 LAN 接続が確立したときは nn::ac::ResultLanConnected が処理結果として返されます。以降、ソケット通信などの無線通信モジュールを利用したネットワーク経由の通信を行うことができるようになります。接続可能なアクセスポイントを発見できなかったときは nn::ac::ResultNotFoundAccessPoint が返されます。

補足:

すでにバックグラウンド通信によって接続が確立していた場合、すぐに成功を返すことがあります。逆に接続が確立していない場合は処理に時間がかかることがあり、インターネット接続を確立できないアクセスポイントに接続した場合は通常よりも処理に時間がかかることがあります。

処理結果に nn::ac::ResultWifiOff が返されたときは、無線オフモードになっているために無線通信モジュールを利用することができない状態です。処理結果に nn::ac::ResultNotAgreeEula が返されたときは、EULA 同意チェックに失敗した場合とアプリケーションにアイコンファイルが設定されていない場合とがあります。そのほかの処理結果については関数リファレンスを参照してください。

nn::ac::IsConnected() を呼び出して、アプリケーションが行った自動接続処理によって確立した接続で、アクセスポイントに接続中かどうかを確認することができます。すでにアクセスポイントなどとの接続がバックグラウンド通信で確立していることが判明していても、必ずアプリケーションで自動接続処理を行ってください。アプリケーションで接続処理を行っていない場合、バックグラウンドで接続していたプロセスが終了したときに、意図せず接続が切断される可能性があります。

3.3.3. 接続状態の取得

自動接続処理で接続を確立したアクセスポイントの種類や、接続中のアクセスポイントの電波強度を、以下の関数で取得することができます。

コード 3-47. 接続状態の取得
nn::Result nn::ac::GetConnectingApType(nn::ac::ApType* apType);
nn::Result nn::ac::GetConnectingNintendoZoneBeaconSubset(
                                nn::ac::NintendoZoneBeaconSubset* beacon);
nn::Result nn::ac::GetConnectingHotspotSubset(nn::ac::HotspotSubset* hotspot);
nn::Result nn::ac::GetLinkLevel(nn::ac::LinkLevel* linkLevel);
nn::ac::LinkLevel nn::ac::GetLinkLevel();

nn::ac::GetConnectingApType() は接続しているアクセスポイントの種類を apType に返します。接続されていない状態で呼び出した場合は、返り値に nn::ac::ResultNotConnecting が返されます。アクセスポイントの種類は nn::ac::ApType 列挙子に、以下のように定義されています。アクセスポイントの種類は、ファームウェアのアップデートが行われた際に追加される可能性がありますので、記載されているもの以外を取得した場合についてもアプリケーションの実装で考慮する必要があります。

表 3-22. nn::ac::ApType 列挙子

列挙子

アクセスポイント

AP_TYPE_NONE

なし

AP_TYPE_USER_SETTING_1

ネットワーク設定 1

AP_TYPE_USER_SETTING_2

ネットワーク設定 2

AP_TYPE_USER_SETTING_3

ネットワーク設定 3

AP_TYPE_NINTENDO_WIFI_USB_CONNECTOR

ニンテンドー Wi-Fi USB コネクタ

AP_TYPE_NINTENDO_ZONE

ニンテンドーゾーン

AP_TYPE_WIFI_STATION

Wi-Fi ステーション

AP_TYPE_FREESPOT

フリースポット(AP_TYPE_HOTSPOT に統合されました)

AP_TYPE_HOTSPOT

ホットスポット

AP_TYPE_ALL

上記すべて(nn::ac::DebugSetApType() のみ)

nn::ac::GetConnectingNintendoZoneBeaconSubset() は接続中のニンテンドーゾーンのゾーンビーコンを beacon に格納します。nn::ac::GetConnectingHotspotSubset() は接続中のホットスポットの情報を hotspot に格納します。どちらの関数も、対応するアクセスポイント以外に接続しているときに呼び出した場合には nn::ac::ResultInvalidLocation を返します。

接続中のアクセスポイントの電波強度は、nn::ac::GetLinkLevel() で取得することができます。返り値または引数 pLinkLevel で取得できる電波強度は nn::ac::LinkLevel 列挙子に、以下のように定義されています。電波強度を取得する処理でエラーが発生したときは LINK_LEVEL_0 を返すため、エラー発生時に pLinkLevel の値を書き換える必要はありません。

表 3-23. nn::ac::LinkLevel 列挙子

列挙子

電波強度

LINK_LEVEL_0

通信品質が非常に悪い、もしくは通信が成立していない

LINK_LEVEL_1

通信品質が悪い

LINK_LEVEL_2

通信品質があまりよくない

LINK_LEVEL_3

通信品質がよい

ニンテンドー3DSステーションへの接続中は、機材間の距離によって AP_TYPE_WIFI_STATION が返る場合と AP_TYPE_NINTENDO_ZONE が返る場合があります。

いつの間に通信拠点へ接続中は、nn::ac::AP_TYPE_WIFI_STATION、nn::ac::AP_TYPE_NINTENDO_ZONEnn::ac::AP_TYPE_HOTSPOT のいずれかが返ります。いつの間に通信拠点への接続中であることを検出したい場合はこれらの論理和で判断 してください。

3.3.4. 切断

自動接続処理で確立した接続は、アプリケーションで明示的に切断する場合と、電波状況の悪化などの外因によって切断される場合とがあります。

コード 3-48. 接続の切断
nn::Result nn::ac::Close();
nn::Result nn::ac::CloseAsync(nn::os::Event* event);
nn::Result nn::ac::GetCloseResult();
nn::Result nn::ac::RegisterDisconnectEvent(nn::os::Event* event);

nn::ac::Close() または nn::ac::CloseAsync() を呼び出すことで、確立していた接続をアプリケーションで明示的に切断することができます。nn::ac::CloseAsync()nn::ac::Close() の非同期版です。処理が成功したかどうかに関わらず、切断処理が終了した時点で event に渡したイベントがシグナル状態になります。イベントがシグナル状態になったあと、nn::ac::GetCloseResult() で処理結果を取得してください。

切断されたかどうかについては、処理結果で返されるインスタンスの IsSuccess()true を返すかどうかでのみ判断してください。バックグラウンドで動作しているデーモンが自動接続で接続を確立していた場合、切断後の状態が未接続(nn::ac::STATUS_IDLE)に遷移するとは限りません。

電波状況の悪化やアクセスポイントの圏外になるなどの理由で、確立していた接続が切断される場合があります。接続が切断されたことは、nn::ac::RegisterDisconnectEvent()event に渡したイベントがシグナル状態になることによってアプリケーションに通知されます。自動接続処理で接続を確立したあとは、この関数で渡したイベントからの通知を待ち受けるスレッドを作成して外因による切断を検知してください。なお、イベントがシグナル状態になった時点でアクセスポイントとの接続が切断されていることは保証されていますので、ライブラリの終了処理前にアプリケーションで特別な処理を行う必要はありません。

3.3.5. 終了

ネットワーク経由の接続が不要になるなど、AC ライブラリの使用を終了するときは nn::ac::Finalize() を呼び出して終了処理を行ってください。

コード 3-49. AC ライブラリの終了
nn::Result nn::ac::Finalize();

初期化が行われていない状態で呼び出した場合は、nn::ac::ResultNotInitialized(エラーではありません)が返されます。

3.4. 信頼性のあるローカル通信(RDT 通信)

CTR-SDK では、信頼性のあるローカル通信を実現する RDT ライブラリを用意しています。

RDT ライブラリは、UDS ライブラリの上位ライブラリとして位置づけられており、以下の特徴があります。

  • 1 対 1 で接続した相手にデータを欠落させることなく届ける
  • 送信したバイト列は、その順序を保った形で受信できることが保証される
  • 送信側から受信側への一方向通信(通信路を 2 つ用意すれば、双方向通信の実現は可能ですが、メモリなどのリソース消費は 2 倍になります)

RDT ライブラリの設計は、サイズの小さなデータを定期的に送信する用途には向いていませんが、ある程度サイズの大きなデータを送信する用途に向いています。

RDT ライブラリで使用する用語は以下のとおりです。

表 3-24. RDT ライブラリで使用する用語

用語

説明

Sender

RDT 通信で、データを送信する側のクラス

Receiver

RDT 通信で、データを受信する側のクラス

3.4.1. 初期化処理

RDT ライブラリは、UDS 通信を利用して信頼性のある通信を実現します。そのため、RDT ライブラリを使用するためには、事前に UDS 通信によるネットワーク接続が確立している必要があります。つまり、Master 側は nn::uds::CreateNetwork() の、Client 側は nn::uds::ConnectNetwork() の実行が完了していなければなりません。

ネットワーク接続を確立させた後に、Sender と Receiver の初期化を行います。それぞれ、nn::rdt::Sender クラスと nn::rdt::Receiver クラスのインスタンスを生成し、メンバ関数の Initialize() を呼び出してください。Master/Client に関係なく、データの送信方向だけを考慮して、生成するインスタンスが Sender なのか Receiver なのかを決めてください。

通信路を 2 つ用意して双方向通信を行う場合は、Sender と Receiver で異なるポート番号を使用してください。

図 3-4. 双方向通信

CTR A CTR B port=0 Sender Receiver port=1 Receiver Sender

3.4.1.1. Sender の初期化

Initialize() を呼び出して、Sender の初期化を行います。初期化が成功すると、Sender は内部で暗黙的に nn::uds::EndpointDescriptor を 2 つ生成します。関数内で nn::uds::Attach() を呼び出し、暗黙的に受信バッファを確保しますので、UDS ライブラリの初期化時に指定した受信バッファのサイズが十分に大きくなければ nn::uds::RestultOutOfMemory を返して初期化に失敗します。

コード 3-50. Sender の初期化関数
nn::Result nn::rdt::Sender::Initialize(const nn::rdt::SenderConfig &config);

config には、初期化パラメータをまとめた構造体(nn::rdt::SenderConfig)を指定します。構造体は以下のように定義されています。

コード 3-51. SenderConfig 構造体
struct nn::rdt::SenderConfig
{
    void *pWorkBuf;
    void *pSendBuf;
    u16   sendBufSize;
    u16   nodeId;
    u8    port;
    u8    padding[3];
};

pWorkBuf には、Sender 用に確保したワークメモリの先頭アドレスを指定します。ワークメモリは nn::rdt::Sender::SENDER_WORKBUF_ALIGNMENT ( 8 Byte ) アライメント、nn::rdt::Sender::SENDER_WORKBUF_SIZE ( 32,768 Byte ) 以上のバッファでなければなりません。

pSendBuf sendBufSize には、送信用バッファの先頭アドレスとサイズを指定します。

nodeId port には、通信相手(送信先)のノード ID とポート番号を指定します。ノード ID とポート番号は UDS ライブラリでの指定と同じです。

初期化で渡したワークメモリと送信用バッファは、Finalize() を呼び出すまで解放しないでください。

3.4.1.2. Receiver の初期化

Initialize() を呼び出して、Receiver の初期化を行います。初期化が成功すると、Receiver は内部で暗黙的に nn::uds::EndpointDescriptor を 2 つ生成します。関数内で nn::uds::Attach() を呼び出し、暗黙的に受信バッファを確保しますので、UDS ライブラリの初期化時に指定した受信バッファのサイズが十分に大きくなければ nn::uds::RestultOutOfMemory を返して初期化に失敗します。

コード 3-52. Receiver の初期化関数
nn::Result nn::rdt::Receiver::Initialize(
                        const nn::rdt::ReceiverConfig &config);

config には、初期化パラメータをまとめた構造体(nn::rdt::ReceiverConfig)を指定します。構造体は以下のように定義されています。

コード 3-53. ReceiverConfig 構造体
struct nn::rdt::ReceiverConfig
{
    void *pWorkBuf;
    void *pRecvBuf;
    u16  recvBufSize;
    u16  nodeId;
    u8   port;
    u8   padding[3];
};

pWorkBuf には、Receiver 用に確保したワークメモリの先頭アドレスを指定します。ワークメモリは nn::rdt::Receiver::RECEIVER_WORKBUF_ALIGNMENT ( 8 Byte ) アライメント、nn::rdt::Receiver::RECEIVER_WORKBUF_SIZE ( 128 Byte ) 以上のバッファでなければなりません。

pRecvBuf recvBufSize には、受信用バッファの先頭アドレスとサイズを指定します。

nodeId port には、通信相手(送信元)のノード ID とポート番号を指定します。ノード ID とポート番号は UDS ライブラリでの指定と同じです。

初期化で渡したワークメモリと受信用バッファは、Finalize() を呼び出すまで解放しないでください。

3.4.2. 状態の取得

Sender と Receiver には、それぞれ内部状態が存在します。状態を取得するには、双方のクラスに用意されているメンバ関数 GetStatus() を呼び出してください。

コード 3-54. Sender/Receiver の状態の取得
enum nn::rdt::SenderState nn::rdt::Sender::GetStatus(void) const;
enum nn::rdt::ReceiverState nn::rdt::Receiver::GetStatus(void) const;

Sender と Receiver それぞれの、典型的な状態の遷移を以下に示します。

図 3-5. Sender の状態遷移

NOT_INITIALIZED CLOSED OPEN_REQUESTED OPENING OPENED CLOSE_REQUESTED Initialize() Finalize() Close() Open() Receiverに接続を要求 Receiverからの応答を確認 接続が確立し、データ転送が可能になる Cancel()による中断 CLOSING Receiverに送信終了を通知 Receiverが接続を拒否

Sender の状態は nn::rdt::SenderState で定義されています。上の状態遷移図では、それぞれの状態を定義の先頭にある "SENDER_STATE_" を省略して表記しています。

表 3-25. Sender の状態(nn::rdt::SenderState の定義)

定義

説明

SENDER_STATE_NOT_INITIALIZED

インスタンス生成直後など、初期化が行われていない状態です。

SENDER_STATE_CLOSED

初期化後などに遷移する、インスタンスの初期状態です。

SENDER_STATE_OPEN_REQUESTED

接続を開始した直後に遷移する状態です。

SENDER_STATE_OPENING

通信相手に接続要求を送出し、反応を待っている状態です。

SENDER_STATE_OPENED

接続が確立し、データ送信が可能になった状態です。

SENDER_STATE_CLOSE_REQUESTED

Close() で接続の終了を宣言した直後に遷移する状態です。

SENDER_STATE_CLOSING

送信を完了し、相手にデータ転送終了通知を送出した状態です。

図 3-6. Receiver の状態遷移

NOT_INITIALIZED CLOSED WAITING OPENED WAITING_FINISHED Initialize() Finalize() Close() Wait() 接続が確立し、データ転送が可能になる Senderからのデータ転送終了 通知に確認応答を送出 FINISHED 確認応答がSenderに届いたと考えられる時間が経過 Cancel()による中断

Receiver の状態は nn::rdt::ReceiverState で定義されています。上の状態遷移図では、それぞれの状態を定義の先頭にある "RECEIVER_STATE_" を省略して表記しています。

表 3-26. Receiver の状態(nn::rdt::ReceiverState の定義)

定義

説明

RECEIVER_STATE_NOT_INITIALIZED

インスタンス生成直後など、初期化が行われていない状態です。

RECEIVER_STATE_CLOSED

初期化後などに遷移する、インスタンスの初期状態です。

RECEIVER_STATE_WAITING

通信相手からの接続要求を待っている状態です。

RECEIVER_STATE_OPENED

接続が確立し、データ受信が可能になった状態です。

RECEIVER_STATE_WAITING_FINISHED

データ転送終了通知を受け、確認応答を返した状態です。

RECEIVER_STATE_FINISHED

確認応答が相手に届いたと考えられる状態です。

後述する Cancel() などによって、通信の中断が指示されると CLOSED への遷移が発生します。

RDT の関数は、それぞれ呼び出すことのできる状態が決められています。現在の状態に対して不適切な関数が呼び出された場合は、即座にエラー(nn::rdt::ResultUntimelyFunctionCall)が返されます。

3.4.3. 通信処理の進行

Sender と Receiver の双方に用意されているメンバ関数 Process() は、RDT ライブラリの通信処理を進行させるために呼び出します。データの送受信や確認応答などの送受信処理は Process() 内部で実行されますので、インスタンスの初期化に成功したあとのアプリケーションは、少なくともフレームごと(60 フレーム換算で約 16.667 ミリ秒ごと)に Process() を呼び出すように設計してください。

コード 3-55. 通信処理の進行
nn::Result nn::rdt::Sender::Process(void);
 nn::Result nn::rdt::Receiver::Process(void);
補足:

Sender::Process()nn::uds::ResultBufferIsFull を返す場合がありますが、このエラーは無視してもかまいません。到達が確認されていないデータは自動的に再送されます。

3.4.4. 接続の開始

Sender のメンバ関数 Open() を呼び出すことで、Sender は初期化パラメータで指定した相手のノード ID、ポート番号で動作している Receiver への接続を試みます。このとき、Receiver の状態が WAITING(Receiver のメンバ関数 Wait() の実行後)であれば接続要求は受諾され、Sender と Receiver はともにデータの送受信が可能な状態である OPENED に遷移します。

もし Receiver の状態が CLOSED であるときに Sender から接続を要求された場合、Receiver は接続を拒否するためにリセット信号を返し、これを受信した Sender の状態は CLOSED に遷移します。Open() 後に Sender の状態が CLOSED へ遷移しても、Receiver が Wait() を実行する前である可能性を考えて、数回接続を試みてください。

コード 3-56. 接続の開始
nn::Result nn::rdt::Sender::Open(void);
nn::Result nn::rdt::Receiver::Wait(void);

以下に、接続が開始されるまでの Sender と Receiver の状態遷移を図にしたものを示します。

図 3-7. 接続開始時の状態遷移

CLOSED CLOSED OPEN_REQUESTED WAITING OPENING OPENED OPENED Sender Receiver Open() Wait() 接続要求 接続要求を受諾 CLOSED CLOSED OPEN_REQUESTED OPENING CLOSED Sender Receiver Open() 接続要求 接続要求を拒否 Receiverが接続要求を待っている場合(成功事例) Receiverが接続要求を待っていない場合(失敗事例)

3.4.5. データの送受信

Sender と Receiver の状態がともに OPENED に遷移しているときは、データの送受信が可能です。

送信には nn::rdt::Sender::Send() を、受信には nn::rdt::Receiver::Receive() を呼び出します。Send() は、データを Sender 内部の送信バッファにデータを書き込む処理を、Receive() は Receiver 内部の受信バッファからデータを取り出す処理を実行するだけです。データが実際に無線で送受信される処理は、Process() を呼び出したときに行われます。

なお、RDT 通信で送受信されるデータには「バイト境界」のようなものは存在しません。そのため、仮に Sender がデータを 1024 バイトずつ送信したとしても、そのデータが Receiver に 1024 バイトずつ到着するとは限りません。

補足:

送信側が nn::rdt::Sender::Send() で送信バッファに書き込んだ順序通りに、受信側では nn::rdt::Receiver::Receive() でデータを読み取ることができます。

コード 3-57. データの送受信
nn::Result nn::rdt::Sender::Send(const void* pBuf, size_t bufSize);
nn::Result nn::rdt::Receiver::Receive(
                        void* pBuf, size_t* pRecvSize, size_t bufSize);

3.4.6. 中断処理

ユーザーの指示により通信を中断する場合や UDS ライブラリでネットワーク切断を検知した場合は、Sender/Receiver の双方に用意されているメンバ関数 Cancel() を呼び出して、RDT 通信を中断してください。Cancel() は、状態を強制的に CLOSED に遷移させ、接続相手に向けてリセット信号を送出します。リセット信号を受信した接続相手の状態が CLOSED に遷移し、通信は中断されます。

コード 3-58. 中断処理
void nn::rdt::Sender::Cancel(void);
void nn::rdt::Receiver::Cancel(void);

3.4.7. 接続の終了

Sender がすべてのデータ送信し終えたときは、nn::rdt::Sender::Close() を呼び出してください。Close() は、これ以上送信すべきデータがないことを Sender に通知します。Close() が呼び出されると、状態は CLOSE_REQUESTED に遷移します。この状態に遷移したあとに呼び出された Process() の処理でクローズ指示が Receiver に送信され、Sender の状態は CLOSING に遷移します。

クローズ指示が Receiver に通知されると、Receiver の状態は WAITING_FINISHED に遷移します。この状態に遷移したあとに呼び出された Process() の処理でクローズ指示の確認応答が Sender に送信され、Sender に届いたと判断できるだけの時間が経過したときに Receiver の状態は FINISHED に遷移します。受信側は、Receive() で取得できるデータがなくなったことを確認してから nn::rdt::Receiver::Close() を呼び出してください。Receiver の状態が CLOSED に遷移し、接続が閉じられます。

通信を中断する目的で Close() を呼び出さないでください。nn::rdt::Sender::Close() は、送信すべきデータをすべて正常に送信したことを指示するために用意されています。また、nn::rdt::Receiver::Close() は、受信すべきデータをすべて正常に受信できたことを確認してから呼び出されることを想定しています。

コード 3-59. 接続の終了
nn::Result nn::rdt::Sender::Close(void);
nn::Result nn::rdt::Receiver::Close(void);

以下の図は接続の終了までの状態の遷移を示したものです。

図 3-8. 接続終了までの状態遷移

OPENED OPENED WAITING_FINISHED CLOSING CLOSED CLOSED FINISHED Sender Receiver Close() Close() クローズ指示を送出 確認応答を送出 一定時間が経過

3.4.8. 終了処理

RDT ライブラリの利用を終える際には、Sender と Receiver の双方に用意されているメンバ関数 Finalize() を呼び出して、終了処理を行ってください。Finalize() により、初期化処理の際にインスタンスが暗黙的に確保していた nn::uds::EndpointDescriptor が解放され、初期化処理で渡していたメモリを確保しておく必要もなくなります。

コード 3-60. 終了処理
void nn::rdt::Sender::Finalize(void);
void nn::rdt::Receiver::Finalize(void);