6.1. PiaTransport の概念

ここでは PiaTransport の概念と、内部動作の詳細について説明します。

プロトコルの概念

同一の通信路を利用する通信であっても、多種多様な形態があり、それぞれ性質が異なります(例: UDP, TCP, IP 電話など)。これらの性質の異なる通信を効率よく行うために、Pia では目的に応じたプロトコル(Pia プロトコル)を用意しています。

Pia プロトコルの基底クラスとして transport::Protocol が用意され、各種通信の性質に応じて派生クラスが用意されています(例: transport::UnreliableProtocol, transport::ReliableProtocol, chat::VoiceProtocol)。

アプリケーションがステーション間で通信を行う際は、目的の応じた Pia プロトコルのインスタンスを作成し、そのインスタンスの送受信 API を呼び出すことになります。

プロトコルの送受信 API と送受信スレッドとの関係

PiaTransport をセットアップすると、送信スレッドと受信スレッドが作成されます。これらの送受信スレッドと、アプリケーションスレッドが呼びだす各プロトコルの送受信 API との関係を図示します。

図 6-1. アプリケーションスレッドと送信スレッド

送信側の動作を簡単に説明すると、以下のようになります。

  1. アプリケーションスレッドは nn::pia::xxx::ProtocolN::Send() を呼び出し、データの送信を指示します。
  2. 送信データは PiaTransport の送信バッファに蓄えられます。
  3. Pia の送信スレッドが送信バッファからデータを取り出します。
  4. 取り出したデータが SDK の各ライブラリの送信 API に渡され、データが送信されます。
図 6-2. アプリケーションスレッドと受信スレッド

同様に受信側の動作を簡単に説明すると、以下のようになります。

  1. Pia の受信スレッドが SDK の各ライブラリの受信 API を呼び出し、データを受信します。
  2. 受信したデータは PiaTransport の受信バッファに蓄えられます。
  3. アプリケーションスレッドは nn::pia::xxx::ProtocolN::Receive() を呼び出し、データの受信を指示します。
  4. 受信バッファからデータが取り出され、アプリケーションに渡されます。

一般に、I/O アクセスの発生する API 呼び出しは長時間ブロックする傾向にありますが、前述したとおり、PiaTransport では送受信スレッドが SDK の送受信 API を呼び出します。 そのため、アプリケーションスレッドが SDK の送受信 API 呼び出しでブロックすることはありません。

内部動作詳細

上記は簡略化した解説でした。ここでは PiaTransport におけるディスパッチ関数と送受信処理、送受信スレッドの詳細を説明します。

ディスパッチ関数の動作

PiaTransport には、以下の処理を担当するディスパッチ関数が用意されています。

  • 送受信スレッドと送受信バッファ間でのデータの受け渡し
  • パケット生成処理
  • パケット解析処理
  • その他各種プロトコルの処理
  • ネットワークの切断検知

このディスパッチ関数は、nn::pia::common::Scheduler::Dispatch() から暗黙的に呼び出されます。ディスパッチ処理が定期的に実行されないと、Pia は正常に動作できませんので、アプリケーションは nn::pia::common::Scheduler::Dispatch() を毎フレーム 1 ~ 2 回の間隔で定期的に呼び出す必要があります。

送信の動作詳細

送信処理は以下のように行われます。

  • アプリケーションが Protocol0::Send() を実行します。データが Pia 内部のパケット生成処理用バッファにコピーされます。
  • アプリケーションが Protocol1::Send() を実行します。データが Pia 内部のパケット生成処理用バッファにコピーされます。
  • ...
  • アプリケーションが ProtocolN::Send() を実行します。データが Pia 内部のパケット生成処理用バッファにコピーされます。
  • ディスパッチ関数が実行されると、パケット生成処理によってこれらのデータ群が効率的にパケットにまとめられ(パケットバンドリング)、送信スレッドに渡されます。
  • 送信スレッドがパケットをネットワークに送出します。

受信の動作詳細

対応する受信処理は以下のように行われます。

  • 受信スレッドがネットワークからパケットを取得します。
  • ディスパッチ関数が実行されると、受信スレッドが取得したパケットがパケット解析処理用バッファに渡され、パケットが解体されてデータが復元されます。
  • アプリケーションが Protocol0::Receive() を実行します。復元されたデータがアプリケーションに渡されます。
  • アプリケーションが Protocol1::Receive() を実行します。復元されたデータがアプリケーションに渡されます。
  • ...
  • アプリケーションが ProtocolN::Receive() を実行します。復元されたデータがアプリケーションに渡されます。

送受信スレッドの動作

送受信スレッドの動作を以下の擬似コードで示します。

コード 6-1. 送信スレッドの動作
while(true)
{
    if(isSendPacketExist())
    {
        // 送信対象のパケットが残っていれば、連続して send() を実行します。
        send(packet);
    }
    else
    {
        // 送信対象のパケットが無くなると、送信スレッドはスリープします。
        sleep(sendThreadSleepSpan);
    }
}
コード 6-2. 受信スレッドの動作
while(true)
{
    if(isReceivePacketExist())
    {
        // 受信対象のパケットが残っていれば、連続して receive() を実行します。
        receive(&packet);
    }
    else
    {
        // 受信対象のパケットが無くなると、受信スレッドはスリープします。
        sleep(receiveThreadSleepSpan);
    }
}

送受信スレッドには比較的高い優先度が与えられ、定期的にパケットの送受信を行います。 この送受信によってスレッドはブロックし、より優先度の低いスレッド(メインスレッド等)に実行権が移されます。 また、送受信対象のパケットが無くなると、送受信スレッドはスリープし、スレッドの実行権が譲られます。

送受信スレッドのスリープ間隔は transport::ThreadStreamManager::SetSendThreadSleepTimeSpan() と transport::ThreadStreamManager::SetReceiveThreadSleepTimeSpan() で指定することもできます。スリープ間隔を短くすると、データが滞留する時間が短縮され、プログラム動作のレスポンスが向上する可能性があります。ただし、スレッドスイッチが頻繁に発生することで、全体的な CPU 負荷は上昇する可能性があります。

警告:

Pia 送受信スレッドへの CPU 割り当てが滞った場合、Pia のシステム処理に悪影響が出る可能性があります。

送受信スレッドの優先度はアプリケーションが指定することもできますが、通常、送受信スレッドの優先度はアプリケーションが Pia API を呼び出すスレッドの優先度よりも高くします。アプリケーションが Pia 送受信スレッドよりも高い優先度のスレッドを作成するなどした場合は、そのスレッドが長時間 CPU を独占しないように注意が必要です。