// Copyright (c) 2016 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<vector>#include<algorithm>#include<score/api/udp_sender.hpp>intmain(){// Create the io_service objectscore::api::io_serviceio;// Create the score sender object with the object profilescore::api::udp_sendersender(io,score::api::source_profile::object);// Configure the destination address and port on the sender.// Address can be any IPv4 address, including multicast and broadcast.std::error_codeerror;sender.add_remote("224.0.0.251",7891,error);if(error){std::cerr<<"Could not add remote. Error: "<<error.message()<<std::endl;returnerror.value();}sender.set_error_message_callback([&io](std::stringoperation,std::error_codeerror){std::cerr<<"Error in "<<operation<<": "<<error.message()<<std::endl;// Stop if something went wrongio.stop();});// At this point the sender is ready to send data over the network.// For now, we just put in dummy data. Let us assume the data was loaded// from a small file:uint32_tmessage_size=100000;std::vector<uint8_t>message_in(message_size);// Fill the data buffer with random bytesstd::generate(message_in.begin(),message_in.end(),rand);// Add the data to the sendersender.write_message(message_in.data(),message_in.size());// In this example, we use a single-byte message with value '0' as the// end-of-transmission message. The receiver does not expect more data// after receiving this same message.// Note that any unique message can be used for this purpose.std::vector<uint8_t>eot(1,0);sender.write_message(eot.data(),eot.size());// Ensure that the EOT message is transmitted immediatelysender.flush();// Set what to do when the sender transmission queue is empty.// We just stop the IO service immediately.sender.set_on_queue_empty_callback([&io](){io.stop();});// Run the event loop of the io_serviceio.run();std::cout<<"Sent: "<<sender.sent_bytes()<<" bytes"<<std::endl;return0;}

First we create an io_service and a sender object, then configure the
destination address for the sender.

After the initialization, we allocate a small buffer that should be transmitted
to the receiver(s). We could fill this block with some actual data (e.g. from
a small file), but that is not relevant here. We write this data block to the
sender with write_message. After this, we send a small 1-byte message using
the same write_message function. This small 1-byte message is the
end-of-transmission message. When the receiving application sees this message,
it knows no data more will arrive, and it can shut down.
The end-of-transmission message format is defined by the application, and it
can be anything. After this, we set a callback to be executed when the sender’s
transmission queue is empty and no more data is to be sent. We stop the
IO service in this callback.

The actual network operations start when we run the io_service (this is the
event loop that drives the sender). The event loop will terminate when the
sender finishes all transmissions, because we explicitly stop the io_service
in our on_queue_empty_callback callback. This also means
that receivers that have not received everything when this callback is executed
will never finish transmission. This approach does not leave much time for
repairing packet loss. A more advanced application may want to leave time for
receivers to request and receive repair data before shutting down
the event loop.

// 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<vector>#include<string>#include<iostream>#include<score/api/udp_receiver.hpp>intmain(){// Create the io_service objectscore::api::io_serviceio;// Create the receiver objectscore::api::udp_receiverreceiver(io);// Bind the score receiver to a specific address and port// The address can be a local IPv4 address or a multicast/broadcast address// It can also be "0.0.0.0" to listen on all local interfacesstd::error_codeec;receiver.bind("224.0.0.251",7891,ec);if(ec){std::cerr<<"Could not bind score receiver. Error: "<<ec.message()<<std::endl;returnec.value();}receiver.set_error_message_callback([](std::stringoperation,std::error_codeerror){std::cerr<<"Error in "<<operation<<": "<<error.message()<<std::endl;});// This callback function will be executed when a message is received.// In this example, we just print how many bytes were received.autoread_message=[&io](constauto&message){std::cout<<"Received: "<<message.size()<<" bytes."<<std::endl;// We use a single-byte message with value '0' as the// end-of-transmission message.if(message.size()==1&&message[0]==0){// Shut down the io_service, as no more data will comeio.stop();std::cout<<"IO service stopped."<<std::endl;}};// Set the callback to be used when message is receivedreceiver.set_message_ready_callback(read_message);// Run the event loop of the io_serviceio.run();return0;}

The initialization steps are very similar for the receiver, but we also set
a callback function that will be executed when data is received. We could
process the incoming data, but here the read_message lambda function just
prints the size of the received block and checks if the received message is the
defined end-of-transmission messsage. The receiver event loop is stopped when
this message is received.
Note that we have written a single block of data on the sender side,
so the same block will be received in one go (i.e. read_message will be called
once when the full block is available, and finally when the end-of-transmission
message is received.). If we send multiple blocks, this callback function would
be invoked for each block.
The data messages are atomic in score, so no partial message will be passed
to the read_message function.