6.4. 基本機能 - ReliableProtocol

概要

ReliableProtocol は TCP に似たプロトコルで、以下のような性質を持ちます。

  • データ到達保証がある
  • データは送信した順に受信される
参考:

ReliableProtocol はデータ到達を保証するために、受信側が定期的に ACK を返すなどの通信を暗黙的に行っています。そのため、ReliableProtocol は UnreliableProtocol などよりも通信帯域を多く消費することに注意が必要です。

処理の流れ

以下、初期化処理、送信処理、受信処理、終了処理の流れを説明します。

初期化処理

最初に Transport::CreateProtocol() を用いて ReliableProtocol のインスタンスを作成します。インスタンス作成時に得られたハンドルを Transport::GetProtocol() に渡すとインスタンスへのポインタが得られますので、続いて ReliableProtocol::Initialize() を呼び出します。これらはメモリアロケーションが伴う処理ですので、BeginSetup() ~ EndSetup() 区間の中で実行する必要があります。

コード 6-14. ReliableProtocol の初期化処理
const uint16_t ReliableProtocolPort = 0; // ReliableProtocol が使用するポート番号。
static uint32_t s_hReliable = 0; // ReliableProtocol のハンドルを保持する変数。

void initializePiaTransport()
{
    nn::pia::Result r = nn::pia::transport::BeginSetup();
    PIA_ASSERT(r.IsSuccess());

    // Transport クラスの初期化処理など
    ...
    ...

    nn::pia::transport::Transport* pTransport = nn::pia::transport::Transport::GetInstance();
    PIA_ASSERT(PIA_IS_VALID_POINTER(pTransport));

    // ReliableProtocol(データ到達保証のある通信)の作成と初期化。
    {
        PIA_ASSERT(s_hReliable == 0);
        s_hReliable = pTransport->CreateProtocol<nn::pia::transport::ReliableProtocol>(ReliableProtocolPort);
        PIA_ASSERT(s_hReliable != 0); // CreateProtocol()は、既に同じプロトコルIDが存在していた場合は0を返すので、そうではないことを確認
        const uint32_t SendBufferNum = 64; // 送信バッファサイズ
        const uint32_t ReceiveBufferNum = 64; // 受信バッファサイズ
        pTransport->GetProtocol<nn::pia::transport::ReliableProtocol>(s_hReliable)->Initialize(SendBufferNum, ReceiveBufferNum);
    }

    // その他のクラスの初期化処理など
    ...
    ...

    r = nn::pia::transport::EndSetup();
    PIA_ASSERT(r.IsSuccess());
}

ReliableProtocol::Initialize() では、送受信バッファのサイズを指定します。サイズの適正値は 最大の送信データのサイズ、送信頻度、ネットワークのレイテンシ、パケットロス率などに関連してきますので、一概に言及することはできませんが、 もしバッファサイズが不足していると、ReliableProtocol::Send() が ResultBufferIsFull を返すという症状となって現れますので、これが許容できる以上に頻発するようであればバッファサイズを増やすことを検討してください。

ウォーターマークを使って実際に使われたバッファサイズを知ることができます。必要なバッファサイズは通信環境に大きく影響を受けますので、さまざまな環境での計測をお勧めします。

送信処理

送信処理は ReliableProtocol::Send() で行います。以下の点に注意してください。

  • 単一ステーション宛ての送信は、前もって ReliableProtocol::IsInCommunication() を呼び出し、通信中であることを確認しておくこと
  • ReliableProtocol::Send() が nn::pia::ResultBufferIsFull を返した場合のケアを行うこと

同一のデータを全ステーションに向けて送信する場合は、ReliableProtocol::SendToAll() を呼び出します。

コード 6-15. ReliableProtocol の送信処理
const int StationDataNum = 4; // 最大 Station 数
const uint32_t BufferSize = 128; // ReliableProtocol で送信するデータを格納するバッファサイズ
char buffer[BufferSize]; // バッファの実体
 
void sendReliable()
{
    nn::pia::transport::ReliableProtocol* pReliable = pTransport->GetProtocol<nn::pia::transport::ReliableProtocol>(s_hReliable);
    for (int i = 0; i < StationDataNum; ++i)
    {
        // 自分自身に送信する必要はありません。
        nn::pia::StationId stationId = s_StationData[i].m_StationId;
        if (stationId == pSession->GetLocalStationId())
        {
            continue;
        }
 
        // 単一ステーション宛ての送信は、送信先と通信中であることを確認してから送信します。
        if (!s_StationData[i].m_bAllSation && pReliable->IsInCommunication(stationId) == false)
        {
            continue;
        }

        {
            // 送信処理
            nn::pia::Result r;
            if (s_StationData[i].m_bAllStation)
            {
                r = pReliable->SendToAll(buffer, BufferSize);
            }
            else
            {
                r = pReliable->Send(stationId, buffer, BufferSize);
            }

            if (r.IsSuccess())
            {
                // No problem.
            }
            else if (r == nn::pia::ResultBufferIsFull())
            {
                // 送信バッファが満杯のため、送信に失敗しました。
                // 送信できなかったデータはアプリケーション側で再送をケアする必要があります。
                ...
                ...
            }
            else if (r == nn::pia::ResultNotInCommunication())
            {
                // 通信中ではありません。
                // Send() 呼び出し時には、IsInCommunication() であらかじめチェックしておけば、ここには到達しません。
            }
            else if (r == nn::pia::ResultNotFound())
            {
                // 指定された送信先が見つかりません。
                // Send() 呼び出し時にのみ返ります。IsInCommunication() であらかじめチェックしておけば、ここには到達しません。
                PIA_ASSERT(false);
            }
            else if (r == nn::pia::ResultTemporaryUnavailable())
            {
                // セッション移行処理中のため、一時的に API を利用できない状態です。ジョイントセッション機能使用時にのみ返ります。
                // Send() 呼び出し時にのみ返ります。
            }
            else
            {
                // その他のエラーはプログラムの修正が必要です。
                PIA_ASSERT(false);
            }
        }
    }

受信処理

受信処理は ReliableProtocol::Receive() で行います。

PiaTransport におけるデータ受信はポーリング方式です。データ未到着の場合は、ReliableProtocol::Receive() が ResultNoData を返します。この場合、受信処理を打ち切るなどのハンドリングを行う必要があります。

コード 6-16. ReliableProtocol の受信処理
const uint32_t BufferSize = 128; // 受信バッファのサイズ
char buffer[BufferSize]; // 受信バッファの実体
 
void receiveReliable()
{
    nn::pia::transport::ReliableProtocol* pReliable = pTransport->GetProtocol<nn::pia::transport::ReliableProtocol>(s_hReliable);
    while (true)
    {
        nn::pia::StationId stationId;
        uint32_t receivedSize;
        // 実際の受信処理
        nn::pia::Result r = pReliable->Receive(&stationId, reinterpret_cast<uint8_t*>(buffer), &receivedSize, BufferSize);
        if (r.IsSuccess())
        {
            // 受信に成功しました。
            ...
            ...
        }
        else if (r == nn::pia::ResultNoData())
        {
            // 受信データがないので受信処理を終了します。
            break;
        }
        else if (r == nn::pia::ResultBrokenData())
        {
            // 想定しないサイズのデータが届いています。
            // この状態になると、この ReliableProtocol での受信を継続できないので、通信を終了します。
            return;
        }
        else if (r == nn::pia::ResultNotInCommunication())
        {
            // セッションに参加中ならば、ここに到達することはありません。
            PIA_ASSERT(false);
        }
        else
        {
            // その他のエラーはプログラムの修正が必要です。
            PIA_ASSERT(false);
        }
    }
}

終了処理

終了処理は ReliableProtocol::Finalize() を呼び出した後、Transport::DestroyProtocol() で ReliableProtocol インスタンスを破棄します。

コード 6-17. ReliableProtocol の終了処理
void finalizeReliable()
{
    nn::pia::transport::ReliableProtocol* pReliable = pTransport->GetProtocol<nn::pia::transport::ReliableProtocol>(s_hReliable);
    if(PIA_IS_VALID_POINTER(pReliable))
    {
        pReliable->Finalize();
        pTransport->DestroyProtocol(s_hReliable);
        s_hReliable = 0; // ハンドルのクリア
    }
}