Generic Source & SinkΒΆ

This introductory example shows how to use the generic source and sink classes. No data is transmitted over the network, and the source and sink objects are parts of the same program for the sake of simplicity. This example can be a starting point if you want to integrate score into an application that already has a custom socket framework.

The complete example is shown below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright (c) 2017 Steinwurf ApS
// Distributed under the "STEINWURF EVALUATION LICENSE 1.0".
// See accompanying file LICENSE.rst or
// http://www.steinwurf.com/licensing

#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <system_error>

#include <score/api/sink.hpp>
#include <score/api/manual_source.hpp>

/// @example pure_source_sink.cpp
///
/// This example shows how to use the generic API of the pure source and
/// sink classes. No data is transmitted over the network, and the source
/// and sink are parts of the same program.

int main()
{
    // Create a pure source and sink object
    score::api::manual_source source(score::api::source_profile::stream);
    score::api::sink sink;

    // We will generate 10 random messages and pass them through the source
    // and sink
    uint32_t message_count = 10;
    std::vector<std::vector<uint8_t>> messages;
    for (uint32_t i = 0; i < message_count; i++)
    {
        // Generate a random message and store it for later verification
        std::vector<uint8_t> message(1 + rand() % 1000);
        std::generate(message.begin(), message.end(), rand);
        messages.push_back(message);

        source.read_message(message.data(), message.size());

        // If this is the last message, we also flush the source to indicate
        // that no more data will be added
        if (i == message_count - 1)
            source.flush();

        // Check if the source has any outgoing packets
        while (source.has_data_packet())
        {
            // Get the data packet from the source
            std::vector<uint8_t> data_packet(source.data_packet_size());
            source.write_data_packet(data_packet.data());

            // This packet could be transmitted over the network, but in this
            // example we forward it directly to the sink
            std::error_code error;
            sink.read_data_packet(
                data_packet.data(),
                data_packet.size(),
                error);

            if (error)
            {
                std::cerr << "sink.read_message() error: "
                          << error.message() << std::endl;
                return error.value();
            }

            // After processing a data packet, the sink might generate
            // some snack packets
            while (sink.has_snack_packet())
            {
                std::vector<uint8_t> snack_packet(
                    sink.snack_packet_size());

                sink.write_snack_packet(snack_packet.data());

                // The snack packet is forwarded directly to the source.
                // In a real application, this would be sent over the network.
                source.read_snack_packet(
                    snack_packet.data(), snack_packet.size(), error);

                if (error)
                {
                    std::cerr << "source.read_snack_packet() error: "
                              << error.message() << std::endl;
                    return error.value();
                }
            }
        }
    }

    // After processing all packets generated by the source, the sink
    // should have all the original messages available
    uint32_t received_messages = 0;
    while (sink.has_message())
    {
        // Retrieve the original messages in-order using get_data()
        std::error_code error;
        std::vector<uint8_t> message(sink.message_size());
        sink.write_message(message.data(), error);

        if (error)
        {
            std::cerr << "sink.get_data() error: "
                      << error.message() << std::endl;
            return error.value();
        }
        // Verify the decoded messages against the original data
        if (message == messages[received_messages])
        {
            std::cout << "Message " << received_messages
                      << ": data verified" << std::endl;
        }
        else
        {
            std::cout << "Message " << received_messages
                      << ": data corrupted" << std::endl;
        }

        received_messages++;
    }

    if (received_messages == message_count)
        std::cout << "All messages received " << std::endl;

    return 0;
}

First we create a manual_source and a sink object. The manual source allows us to configure each protocol parameter manually, whereas the auto_sender class can automatically adjust various protocol parameters using automatic controllers. In this example, we use the manual source with the stream profile which is optimized for low delay.

After the initialization, we generate a small number of random messages and we push each message to the source using the read_message function. The source transforms these user messages into outgoing data packets and we retrieve these packets with the write_data_packet function. Of course, these packet buffers could be transmitted over the network using any socket interface, but in this example we immediately forward these to the sink using read_data_packet.

The sink might generate SNACK packets to provide feedback to the source. We get these packets from the sink using write_snack_packet, and then they are delivered to the source using read_snack_packet.

After processing all packets generated by the source, the sink should have all the original messages available. We retrieve these messages in order using the sink.write_message function and we also verify the decoded messages against the original data.

In a real application, this interaction between the source and sink objects can be realized with an asynchronous socket interface. The receiver socket should listen for data packets that should be passed to the sink and the same socket can be used to transmit SNACK packets back to the source. Therefore the sender socket should also continuously listen for these feedback packets, and the source might generate additional data packets reacting to the feedback.