5.4. Basic Features - ReliableProtocol

Description

ReliableProtocol is similar to TCP and has the following characteristics.

  • Data is guaranteed to arrive at the destination.
  • Data is always received in the order that it is sent.
Reference:

With ReliableProtocol communications, receiving stations periodically return an ACK to implicitly keep the communication alive and guarantee that they receive any data that is sent. As a result, ReliableProtocol consumes more communication bandwidth than UnreliableProtocol.

Process Flow

This section describes how to initialize this class, use it to send and receive data, and then finalize it.

Initialization

Start by creating an instance of ReliableProtocol using the Transport::CreateProtocol() function. Pass the handle you got when instantiating UnreliableProtocol to the Transport::GetProtocol() function to get a pointer to the instance you created. Next, call the ReliableProtocol::Initialize() function. Because this includes processes for memory allocation, it must be executed between the BeginSetup() and EndSetup() functions.

Code 5-14. Initializing ReliableProtocol
const u16 RELIABLE_PROTOCOL_PORT = 0; // Port number for ReliableProtocol to use.
static u32 s_hReliable = 0; // Variable that stores the handle to ReliableProtocol.


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

    // Initialization of the Transport class.
    ...
    ...

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

    // Creation and initialization of ReliableProtocol (communication that guarantees arrival of data).
    {
        PIA_ASSERT(s_hReliable == 0);
        s_hReliable = pTransport->CreateProtocol<nn::pia::transport::ReliableProtocol>(RELIABLE_PROTOCOL_PORT);
        PIA_ASSERT(s_hReliable != 0); // Make sure that the return value is not 0. The CreateProtocol() function returns 0 if the same protocol ID already exists.
        const u32 SEND_BUF_NUM = 64; // Send buffer size.
        const u32 RECV_BUF_NUM = 64; // Receive buffer size.
        pTransport->GetProtocol<nn::pia::transport::ReliableProtocol>(s_hReliable)->Initialize(SEND_BUF_NUM, RECV_BUF_NUM);
    }


    // Initialization of other classes.
    ...
    ...

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

Specify the size of the send and receive buffers in the ReliableProtocol::Initialize() function. The values to set depend on many factors such as the maximum send data size, send frequency, network latency, and packet loss rate. You must determine the values that are suitable for your application. The ReliableProtocol::Send() function returns ResultBufferIsFull if the buffers are not large enough. Increase the size of the buffers if you get ResultBufferIsFull too frequently.

Use the Watermarking feature to determine the actual size of the buffers used by your application. The required buffer sizes can vary largely depending on the communication environment. Make sure that you test in a variety of environments to get a range of representative values.

Sending

Call the ReliableProtocol::Send() function to send data. Note the following.

  • If you are sending data to a single station, call the ReliableProtocol::IsInCommunication() function first to make sure that you have a connection to that station.
  • You must include handling for what to do when ReliableProtocol::Send() returns nn::pia::ResultBufferIsFull.

To send the same data to all of the stations in a session, specify STATION_ID_ALL for the StationId parameter when calling the Send() function.

Code 5-15. Sending Data Using ReliableProtocol
const int STATION_DATA_NUM = 4; // The maximum number of stations.
const size_t BUFFER_SIZE = 128; // Size of the buffer that stores data to send using ReliableProtocol.
char buffer[BUFFER_SIZE]; // The instance of the buffer.
 
void sendReliable()
{
    nn::pia::transport::ReliableProtocol* pReliable = pTransport->GetProtocol<nn::pia::transport::ReliableProtocol>(s_hReliable);
    for (int i = 0; i < STATION_DATA_NUM; ++i)
    {
        // The sending station never sends data to itself.
        nn::pia::StationId stationId = s_StationData[i].m_StationId;
        if (stationId == pSession->GetLocalStationId())
        {
            continue;
        }
 
        // When sending data to a single station, only send the data after confirming that you have a connection to the receiving station.
        if (stationId != nn::pia::STATION_ID_ALL && pReliable->IsInCommunication(stationId) == false)
        {
            continue;
        }

        {
            // Send process.
            nn::Result r = pReliable->Send(stationId, buffer, BUFFER_SIZE);
            if (r.IsSuccess())
            {
                // No problem.
            }
            else if (r == nn::pia::ResultBufferIsFull())
            {
                // The send process fails if the send buffer gets full.
                // Make sure that you implement handling for resending data that could not be sent the first time in your application.
                ...
                ...
            }
            else if (r == nn::pia::transport::ResultIsNotInCommunication())
            {
                // This code is never reached if IsInCommunication() is called in advance.
                PIA_ASSERT(false);
            }
            else
            {
                // Other errors require programmatic fixes in the application.
                PIA_ASSERT(false);
            }
        }
    }

Receiving

Call the ReliableProtocol::Receive() function to receive data.

PiaTransport uses the polling method to receive data. ReliableProtocol::Receive() returns ResultNoData when no data has arrived. When this happens, end the receive process.

Code 5-16. Receiving Data Using ReliableProtocol
const size_t BUFFER_SIZE = 128; // Receive buffer size.
char buffer[BUFFER_SIZE]; // The instance of the receive buffer.
 
void receiveReliable()
{
    nn::pia::transport::ReliableProtocol* pReliable = pTransport->GetProtocol<nn::pia::transport::ReliableProtocol>(s_hReliable);
    while (true)
    {
        nn::pia::StationId stationId;
        size_t receivedSize;
        // The actual receive process.
        nn::Result r = pReliable->Receive(&stationId, reinterpret_cast<bit8*>(buffer), &receivedSize, BUFFER_SIZE);
        if (r.IsSuccess())
        {
            // Data received successfully.
            ...
            ...
        }
        else if (r == nn::pia::ResultNoData())
        {
            // The receive process ends because there is no more data to receive.
            break;
        }
        else if (r == nn::pia::ResultBrokenData())
        {
            // Data of an unexpected size arrived.
            // When in this state, receiving with this ReliableProtocol cannot continue, so communications are ended.
            return;
        }
        else if (r == nn::pia::transport::ResultIsNotInCommunication())
        {
            // This code is never used as long as the station is actually participating in the session.
            PIA_ASSERT(false);
        }
        else
        {
            // Other errors require programmatic fixes in the application.
            PIA_ASSERT(false);
        }
    }
}

Finalization

To finalize ReliableProtocol, call the ReliableProtocol::Finalize() function and then call the Transport::DestroyProtocol() function to destroy the instance of ReliableProtocol.

Code 5-17. Finalizing 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; // Clear the handle.
    }
}