ntpdatachannel_web_logos

NtpUdpChannel C++ library

v8.1.5

Table of contents

Overview

NtpUdpChannel C++ library simplifies sending and receiving data over the UDP transport layer via low bandwidth networks. The library streams data taking into account channel bandwidth and controls data loss. The library depends on UdpSocket library (provides methods to work with UDP sockets). It also provides applications to test communication via network. The library utilizes C++17 standard. The library doesn’t have any third-party dependency to be specially installed in the OS. The library is compatible with Linux and Windows OS.

NtpUdpChannel is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.

Versions

Table 1 - Library versions.

Version Release date What’s new
7.2.0 22.05.2022 - Protocol parser changed.
8.0.0 28.02.2024 - Interface changed.
- Documentation updated.
8.0.1 16.04.2024 - Documentation updated.
8.0.2 22.05.2024 - UdpSocket updated.
- Documentation updated.
8.1.0 30.07.2024 - CMake structure updated.
- NtpParser moved to implementation folder.
8.1.1 22.06.2025 - Updated UdpSocket submodule.
8.1.2 07.02.2026 - Updated UdpSocket submodule.
8.1.3 17.07.2026 - Updated UdpSocket submodule.
8.1.4 16.10.2026 - UdpSocket sub-repository removed: the UdpSocket repository must be in the folder next to the library repository.
- Packet decoder rejects packets with a wrong size and packets with inconsistent packet IDs (a short DATA packet could crash the decoder).
- Parameters of init(…), write(…) and read(…) are checked.
- Data races and undefined behavior fixed, debug output removed.
- Compiler warnings and security flags added to all targets.
- Documentation and examples updated.
8.1.5 25.10.2026 - Compiler warnings of the packet parser fixed (-Wconversion); the behavior is unchanged.

Library files

The library is supplied only by source code. The user is given a set of files in the form of a CMake project (repository). The repository structure is shown below:

CMakeLists.txt ----------------- Main CMake file of the library.
src ---------------------------- Library source code folder.
    CMakeLists.txt ------------- CMake file of the library.
    NtpUdpChannel.h ------------ Main library header file.
    NtpUdpChannelVersion.h ----- Header file with library version.
    NtpUdpChannelVersion.h.in -- File for CMake to generate version header.
    NtpUdpChannel.cpp ---------- Main C++ implementation file.
    impl
        NtpParser.h ------------ NTP protocol parser header file.
        NtpParser.cpp ---------- NTP protocol parser source file.
examples ----------------------- Folder with examples
    CMakeLists.txt ------------- CMake file to include examples.
    NtpUdpChannelReceiver ------ Folder with data receiver example.
        CMakeLists.txt --------- CMake file of data receiver example.
        main.cpp --------------- Source code of data receiver example.
    NtpUdpChannelSender -------- Folder with data sender example.
        CMakeLists.txt --------- CMake file of data sender example.
        main.cpp --------------- Source code of data sender example.
    NtpUdpChannelBenchmark ----- Folder with benchmark application.
        CMakeLists.txt --------- CMake file of benchmark application.
        main.cpp --------------- Source code of benchmark application.

NtpUdpChannel class description

Class declaration

NtpUdpChannel class declared in NtpUdpChannel.h file. Class declaration:

class NtpUdpChannel
{
public:
    // Get string of current library version.
    static std::string getVersion();

    // Class constructor.
    NtpUdpChannel();

    // Class destructor.
    ~NtpUdpChannel();

    // The class can not be copied or moved.
    NtpUdpChannel(const NtpUdpChannel& src) = delete;
    NtpUdpChannel(NtpUdpChannel&& src) = delete;
    NtpUdpChannel& operator=(const NtpUdpChannel& src) = delete;
    NtpUdpChannel& operator=(NtpUdpChannel&& src) = delete;

    // Init communication channel.
    bool init(std::string config);

    // Write data to channel.
    bool write(uint8_t* data, uint32_t dataSize, int32_t logicPort = 0,
               uint32_t resendsCount = 0, float bandwidthMbps = 0,
               int32_t waitConfirmedMs = 0);

    // Read data from channel.
    bool read(uint8_t* dataBuff, uint32_t dataBuffSize,
              uint32_t& inputSize, int32_t& logicPort,
              int32_t timeoutMs = 0);

    // Get open status.
    bool isOpen();
};

The methods write(…), read(…) and isOpen() can be called from several threads at the same time. The method init(…) and the destructor must not be called at the same time as any other method. The constructor allocates the buffers of the protocol parser (about 192 MiB) and throws std::bad_alloc if the memory is not available.

getVersion method

The getVersion() method returns string of current class version in format “Major.Minor.Patch”. Method declaration:

static std::string getVersion();

Method can be used without NtpUdpChannel class instance:

std::cout << "NtpUdpChannel version: " << rapidpixel::NtpUdpChannel::getVersion();

Console output:

NtpUdpChannel version: 8.1.5

init method

The init(…) method to initialize communication channel. Method declaration:

bool init(std::string config);
Parameter Value
config Initialization string hostUdpPort;dstIp;dstUdpPort; (host UDP port, destination IP address, destination UDP port) separated by “;”. The ports are decimal numbers from 0 to 65535, the destination IP address is an IPv4 address in dotted decimal notation (host names are not resolved). Spaces are ignored, the last “;” is optional. Example: “50001;192.168.1.1;50002”. To provide communication sender and receiver used two UDP ports: one to receive data (the socket is bound to the host UDP port on all local IPv4 interfaces) and one to send data.

Returns: TRUE if the communication channel init or FALSE if not: the initialization string is not valid or the sockets or the threads can not be created (the channel is stopped in this case). If the channel is running, the method stops it and starts it again with new parameters.

write method

The write(…) method send data to communication channel. Method declaration:

bool write(uint8_t* data, uint32_t dataSize, int32_t logicPort = 0,
           uint32_t resendsCount = 0, float bandwidthMbps = 0,
           int32_t waitConfirmedMs = 0);
Parameter Value
data Pointer to the data to send. Must not be nullptr and must be valid for dataSize bytes. The data is copied before the method returns.
dataSize Size of data to send in bytes, from 1 to 4169727.
logicPort Logic port number to send data. The library supports 16 logic ports (0 - 15): only the 4 least significant bits of the value are used.
resendsCount Number of times the data is sent, from 0 to 255 (0 and 1 - the data is sent once).
bandwidthMbps Bandwidth of communication channel in Mbit/s. Method controls sending bandwidth. The value is used for all data sent by the channel until the next call.
value <= 0 (or not a number) - the method will send data as fast as possible (this can lead to packet loss in the communication channel).
waitConfirmedMs Timeout in milliseconds to wait sent and confirmation of the data.
value < 0 - method will wait until all packets of the data are sent, without time limit (the receiver does not confirm the data). The method returns FALSE immediately if the channel is not initialized;
value == 0 - method will not wait for the data to be sent;
value > 0 - the receiver confirms the data. Method will wait until the data is sent (up to the specified time) and then until the data is confirmed (up to the specified time).

Returns: TRUE if the data is put in the queue of packets to send (waitConfirmedMs == 0), sent (waitConfirmedMs < 0) or sent and confirmed (waitConfirmedMs > 0). FALSE if the data is not sent or not confirmed in time or if a parameter is not valid: data is nullptr, dataSize is 0 or greater than 4169727, resendsCount is greater than 255.

read method

The read(…) method reads data from communication channel. The data is removed from the channel when it is copied. Method declaration:

bool read(uint8_t* dataBuff, uint32_t dataBuffSize,
          uint32_t& inputSize, int32_t& logicPort,
          int32_t timeoutMs = 0);
Parameter Value
dataBuff Pointer to buffer. Must not be nullptr and must be valid for dataBuffSize bytes.
dataBuffSize Size of buffer. If the buffer is smaller than the data the method returns FALSE and the data stays in the channel.
inputSize Size of read data. The value is changed only if the method returns TRUE.
logicPort Logic port number to receive data (0 - 15, only the 4 least significant bits of the value are used). The value is not changed by the method.
timeoutMs Timeout in ms to wait for data:
value < 0 - the method will not wait and will return the oldest data which is ready;
value == 0 - the method will not wait and will return the newest data which is ready (data which is ready for more than 1000 seconds is not returned: use a negative value to read it);
value > 0 - the method will wait specified time for data.

Returns: TRUE if data is copied or FALSE if no new data, the data has already been read, the buffer is too small or dataBuff is nullptr.

isOpen method

The isOpen() method intended to obtain communication channel open status. Method declaration:

bool isOpen();

Returns: TRUE after a successful call of write(…) which did not wait for a confirmation (waitConfirmedMs <= 0). The init(…) method sets the value to FALSE: a channel which is initialized and has not sent data yet is not open.

Build and connect to your project

Build stand alone

The repository does not contain sub-repositories. The library UdpSocket must be cloned into the same folder as NtpUdpChannel. The folder structure must be:

<work folder>
    NtpUdpChannel
    UdpSocket

Typical commands to build the library and the examples (use the URLs of the repositories provided to you):

git clone <NtpUdpChannel repository URL>
git clone <UdpSocket repository URL>
cd NtpUdpChannel
mkdir build
cd build
cmake ..
make

On Windows use cmake --build . --config Release instead of make. If NtpUdpChannel is the top-level CMake project, the main CMakeLists.txt file adds the folder ../UdpSocket before the library itself. If the folder does not exist CMake stops with an error.

CMake options

Table 2 - CMake options of the repository. In the options below ${PARENT} is the prefix defined by the top-level project (empty if NtpUdpChannel is the top-level project).

Option Default Description
${PARENT}_NTP_UDP_CHANNEL ON Build the library.
${PARENT}_NTP_UDP_CHANNEL_EXAMPLES ON (stand alone), OFF (included in other project) Build the examples.
${PARENT}_PURE_UDP_CHANNEL_EXAMPLES_RECEIVER
${PARENT}_PURE_UDP_CHANNEL_EXAMPLES_SENDER
${PARENT}_PURE_UDP_CHANNEL_EXAMPLES_BENCHMARK
ON Build the receiver, sender and benchmark examples. The options are set to ON each time the examples are configured: the examples can not be switched off one by one.
${PARENT}_SUBMODULE_CACHE_OVERWRITE ON Forced rewriting of the options above in the CMake cache. The top-level project sets it to OFF when it includes NtpUdpChannel as a sub-repository.
CMAKE_BUILD_TYPE Release (if not specified) Build type.

Connect to your project

If you want connect NtpUdpChannel library to your CMake project as source code you can make follow. For example, if your repository has structure:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp

Create 3rdparty folder in your repository folder and copy NtpUdpChannel and UdpSocket repository folders there (UdpSocket is the library which NtpUdpChannel depends on). Structure of your repository:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp
3rdparty
    NtpUdpChannel
    UdpSocket

Create CMakeLists.txt file in 3rdparty folder. CMakeLists.txt should add UdpSocket and then NtpUdpChannel and should contain:

cmake_minimum_required(VERSION 3.13)

################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)

################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)

################################################################################
## CONFIGURATION
## 3rd-party submodules configuration
################################################################################
SET(${PARENT}_SUBMODULE_UDP_SOCKET                      ON  CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_UDP_SOCKET)
    SET(${PARENT}_UDP_SOCKET                            ON  CACHE BOOL "" FORCE)
    SET(${PARENT}_UDP_SOCKET_EXAMPLES                   OFF CACHE BOOL "" FORCE)
endif()

SET(${PARENT}_SUBMODULE_NTP_UDP_CHANNEL                 ON  CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_NTP_UDP_CHANNEL)
    SET(${PARENT}_NTP_UDP_CHANNEL                       ON  CACHE BOOL "" FORCE)
    SET(${PARENT}_NTP_UDP_CHANNEL_EXAMPLES              OFF CACHE BOOL "" FORCE)
endif()

################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_UDP_SOCKET)
    add_subdirectory(UdpSocket)
endif()

if (${PARENT}_SUBMODULE_NTP_UDP_CHANNEL)
    add_subdirectory(NtpUdpChannel)
endif()

File 3rdparty/CMakeLists.txt adds folders UdpSocket and NtpUdpChannel to your project and excludes examples (UdpSocket and NtpUdpChannel examples) from compiling (by default examples are excluded from compiling if NtpUdpChannel is included as sub-repository). NtpUdpChannel does not add UdpSocket when it is included in another project: your project must add it, before or after NtpUdpChannel. If UdpSocket is not added the compiler stops with the error “UdpSocket.h: No such file or directory”. The new structure of your repository:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp
3rdparty
    CMakeLists.txt
    NtpUdpChannel
    UdpSocket

Next you need include folder 3rdparty in main CMakeLists.txt file of your repository. Add string at the end of your main CMakeLists.txt:

add_subdirectory(3rdparty)

Next you have to include NtpUdpChannel library in your src/CMakeLists.txt file:

target_link_libraries(${PROJECT_NAME} NtpUdpChannel)

Done!

Portability

The library uses standard C++17 only (compiler extensions are not used) and does not use SIMD instructions. The packet header is written byte by byte, the most significant byte first: the data exchange does not depend on the byte order of the hosts. The library was built and tested with GCC 11 and GCC 12 on x86-64 Linux and with GCC 11 for AArch64 Linux (the tests were run with qemu-user).

Examples

Data sender example

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <string>
#include <thread>
#include <vector>

#include "NtpUdpChannel.h"



// Entry point.
int main()
{
    std::cout<< "=================================================" << std::endl;
    std::cout<< "NtpUdpChannelSender "                              << std::endl;
    std::cout<< "=================================================" << std::endl;
    std::cout<< "Library versions: "                                << std::endl;
    std::cout<< "NtpUdpChannel:....."<< rapidpixel::NtpUdpChannel::getVersion()
             << std::endl;
    std::cout<< "-------------------------------------------------" << std::endl;
    std::cout<< std::endl;

    std::string answer = "n";
    std::cout << "Host UDP port\t: 50002" << std::endl;
    std::cout << "Dst IP Addr\t: 127.0.0.1" << std::endl;
    std::cout << "Dst UDP port\t: 50001" << std::endl;
    std::cout << "Sending period\t: 100 ms" << std::endl;
    std::cout << "Use default settings ? (y|n): ";
    std::cin >> answer;

    std::string initString = "";

    // Enter host UDP port.
    int hostUdpPort = 50002;
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter host UDP port: ";
        std::cin >> hostUdpPort;
    }
    initString += std::to_string(hostUdpPort) + ";";

    // Enter destination IP.
    std::string dstIp = "127.0.0.1";
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter destination IP: ";
        std::cin >> dstIp;
    }
    initString += dstIp + ";";

    // Enter destination UDP port.
    int dstUdpPort = 50001;
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter destination UDP port: ";
        std::cin >> dstUdpPort;
    }
    initString += std::to_string(dstUdpPort) + ";";

    // Enter sending data period [ms].
    int sendDataTimeoutMs = 0;
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter sending data period, ms: ";
        std::cin >> sendDataTimeoutMs;
    }

    // Check the input.
    if (!std::cin)
    {
        std::cout << "ERROR: Wrong input" << std::endl;
        return -1;
    }

    // Init NTP UDP channel. The init string is checked by the channel.
    rapidpixel::NtpUdpChannel channel;
    if (!channel.init(initString))
    {
        std::cout << "ERROR: UDP channel not init" << std::endl;
        return -1;
    }

    // Calculate and init data buffer.
    std::vector<uint8_t> data(1024); // Any value from 1 to 4169727.
    int32_t logicPort = 0;
    uint32_t resendsCount = 1;
    float bandwidthMbps = 10;
    int32_t waitConfirmedMs = -1;
    uint8_t count = 0;
    while (true)
    {
        std::fill(data.begin(), data.end(), count);
        ++count;

        // Send data.
        if (!channel.write(data.data(), static_cast<uint32_t>(data.size()),
                           logicPort, resendsCount, bandwidthMbps,
                           waitConfirmedMs))
        {
            std::cout << "ERROR: Send data problem" << std::endl;
            break;
        }
        std::cout << "Send " << data.size() << " bytes" << std::endl;

        // Wait 100 ms for respond.
        uint32_t inputSize = 0;
        int32_t inputLogicPort = 0;
        int32_t timeoutMs = 100;
        if (!channel.read(data.data(), static_cast<uint32_t>(data.size()),
                          inputSize, inputLogicPort, timeoutMs))
        {
            std::cout << "No respond" << std::endl;
        }
        else if (inputSize > 0)
        {
            std::cout << "Get " << inputSize << " bytes" << std::endl;
        }

        std::this_thread::sleep_for(
                    std::chrono::milliseconds(sendDataTimeoutMs));
    }

    return 1;
}

Data receiver example

#include <cstdint>
#include <iostream>
#include <string>
#include <vector>

#include "NtpUdpChannel.h"



// Entry point.
int main()
{
    std::cout<< "=================================================" << std::endl;
    std::cout<< "NtpUdpChannelReceiver "                            << std::endl;
    std::cout<< "=================================================" << std::endl;
    std::cout<< "Library versions: "                                << std::endl;
    std::cout<< "NtpUdpChannel:....."<< rapidpixel::NtpUdpChannel::getVersion()
             << std::endl;
    std::cout<< "-------------------------------------------------" << std::endl;
    std::cout<< std::endl;

    std::string answer = "n";
    std::cout << "Host UDP port\t: 50001" << std::endl;
    std::cout << "Dst IP Addr\t: 127.0.0.1" << std::endl;
    std::cout << "Dst UDP port\t: 50002" << std::endl;
    std::cout << "Waiting period\t: 1000 ms" << std::endl;
    std::cout << "Use default settings ? (y|n): ";
    std::cin >> answer;

    std::string initString = "";

    // Enter host UDP port.
    int hostUdpPort = 50001;
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter host UDP port: ";
        std::cin >> hostUdpPort;
    }
    initString += std::to_string(hostUdpPort) + ";";

    // Enter destination IP.
    std::string dstIp = "127.0.0.1";
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter destination IP: ";
        std::cin >> dstIp;
    }
    initString += dstIp + ";";

    // Enter destination UDP port.
    int dstUdpPort = 50002;
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter destination UDP port: ";
        std::cin >> dstUdpPort;
    }
    initString += std::to_string(dstUdpPort) + ";";

    // Enter the waiting period for input data, ms.
    int waitDataTimeoutMs = 1000;
    if (!(answer == "y" || answer == "Y"))
    {
        std::cout << "Enter the waiting period for input data, ms: ";
        std::cin >> waitDataTimeoutMs;
    }

    // Check the input.
    if (!std::cin)
    {
        std::cout << "ERROR: Wrong input" << std::endl;
        return -1;
    }

    // Init NTP UDP channel. The init string is checked by the channel.
    rapidpixel::NtpUdpChannel channel;
    if (!channel.init(initString))
    {
        std::cout << "ERROR: UDP channel not init" << std::endl;
        return -1;
    }

    // Calculate and init data buffer.
    std::vector<uint8_t> buff(1024); // Any value from 1 to 4169727.
    uint32_t inputSize = 0;

    // Main loop.
    while (true)
    {
        // Wait Timeout ms for new data.
        int32_t logicPort = 0;
        if (channel.read(buff.data(), static_cast<uint32_t>(buff.size()),
                         inputSize, logicPort, waitDataTimeoutMs))
        {
            std::cout << "Get " << inputSize << " bytes" << std::endl;

            // Send respond.
            if (!channel.write(buff.data(), inputSize))
            {
                std::cout << "ERROR: Send respond problem" << std::endl;
                break;
            }
            std::cout << "Send " << inputSize << " bytes" << std::endl;
        }
        else
        {
            std::cout << "No data" << std::endl;
        }
    }

    return 1;
}

Table of contents