
NtpUdpChannel C++ library
v8.1.5
Table of contents
- Overview
- Library files
- Versions
- NtpUdpChannel class description
- Build and connect to your project
- Portability
- Examples
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;
}