
UdpDataChannel C++ library
v4.0.0
Table of contents
- Overview
- Versions
- Library files
- UdpDataChannel class description
- Build and connect to your project
- Example
Overview
UdpDataChannel C++ library provides point-to-point high bitrate communication between two applications based on UDP. The library includes UdpDataChannel (works as server or client) class and two additional classes: UdpDataClient (connects to server, for compatibility with previous versions) and UdpDataServer (wait connection from client, for compatibility with previous versions). All classes provide two-way communication. The library uses C++17 standard and only depends on open source UdpSocket (source code included, Apache 2.0 license) which provide functions to work with UDP sockets. This library compatible with Linux and Window. Server principles: the server initializes the user-specified UDP port and waits for the client to connect from any port. The server supports connection of only one client. When a client connects the server remembers the client’s IP and UDP port to exchange messages with them. The client sends special commands to connect. Client principles: the client initialized any first available UDP port in the OS. After initialization client sends connection messages to server port (to connect the user must say to client the server’s UDP port and IP). Once connection established the client can exchanges messages with server. To send data server and client split data into separate UDP packets with special header data (8 bytes header + payload). The maximum packet size (header + payload) is set by the maxPacketSize parameter of the init(…) method: allowed range from 64 to 65536 bytes, default 1030 bytes (8 bytes header + 1022 bytes payload, fits a standard 1500-byte MTU). Bigger values (e.g. 9000) enable jumbo packets on networks that support them; values above 65507 (the IPv4 UDP datagram payload limit) are accepted but clamped to 65507 for sending. The parameter affects only sending: every packet header carries the sender’s packet payload size, so the receiver reassembles data using the sender’s packet size and always accepts packets up to 65536 bytes. The server and the client may use different maxPacketSize values without affecting the data exchange. Receiver (client or server) collect UDP packets and detect when whole input data being send by sender collected. The receiver sends confirmation to sender about receiving each block of 8 packets and confirmation about whole data being received. Sender sends data four times until it will get confirmation from receiver. If particular packet was confirmed the sender doesn’t resend it. The library can handle intensive data exchange close to channel bandwidth limit. The library allows user to set maximum channel bandwidth to prevent network overloading. Sender (client or server) controls interval between UDP packets being sent. The library allows to send only up to 65536 * (min(maxPacketSize, 65507) - 8) bytes in one time (66977792 bytes with the default packet size; for jumbo packet sizes the limit is capped at the largest whole number of packets not exceeding 1073741823 bytes).
Versions
Table 1 - Library versions.
| Version | Release date | What’s new |
|---|---|---|
| 1.0.0 | 20.11.2023 | - First version of the library. |
| 1.1.0 | 12.02.2024 | - Repository structure reorganized. - Added separate classes for client and server. - Documentation updated. - Example added. |
| 1.1.1 | 13.04.2024 | - Documentation updated. |
| 1.1.2 | 22.05.2024 | - UdpSocket updated. - Documentation updated. |
| 1.1.3 | 11.06.2024 | - CPU overloading when no output data fixed. |
| 1.2.0 | 17.07.2024 | - CMake updated. - Repository structure changed. - Example updated. |
| 1.2.1 | 23.07.2024 | - Files structure updated. |
| 1.2.2 | 20.02.2025 | - Additional mutex for shared buffers are added. |
| 2.0.0 | 25.03.2025 | - UdpDataChannel base class has been added. - Test applications and example have been updated. |
| 2.1.0 | 03.06.2025 | - Update data notification algorithm. - Add notification messages to confirm receiving block of packets. - Documentation update. |
| 2.1.1 | 09.06.2025 | - Update data notification algorithm. - Documentation update. |
| 2.1.2 | 07.02.2025 | - Updated UdpSocket submodule. |
| 3.0.0 | 10.07.2026 | - Added getBuffer(…) zero-copy data receiving (returns a pointer to an internal buffer, no data copy). - Added back-pressure sending mode selected by channelBandwidthKbps <= 0 (paces by output socket buffer occupancy). - Enlarged input reassembly buffers and socket receive buffer for more reliable reception. |
| 3.1.0 | 14.07.2026 | - Fixed a shutdown hang: close()/destructor could block forever waiting to join an idle output thread (missing condition-variable notification). - Reworked interval pacing (channelBandwidthKbps > 0): packets are now sent in batches and paced once per batch by the exact byte airtime (nanosecond resolution). This fixes the send-rate overshoot of the previous fixed microsecond per-packet slot and removes the per-packet busy-spin at high bitrate. - Fixed a rare loss of the first single-packet frame when the frame data-ID wrapped to 0. - Updated UdpSocket submodule (auto-raises net.core.rmem_max so the large receive buffer is not silently clamped). |
| 3.2.0 | 15.07.2026 | - Added the sendFirst parameter to send(…) (default false, backward compatible). With true the output queue is cleared of not-yet-sent items and the new data becomes the next frame transmitted (latest-only), without interrupting the frame currently on the wire — for real-time streams where only the freshest data matters. - Added the sendfirsttest functional test (FIFO vs latest-only over loopback). |
| 4.0.0 | 27.07.2026 | - Added the maxPacketSize parameter to init(…) (default 1030, backward compatible API): sets the maximum UDP packet size (header + payload) used to split outgoing data, allowed range [64:65536] bytes — enables jumbo packets. Values above 65507 (the IPv4 UDP datagram payload limit) are clamped to 65507 for sending. - The data packet header grew from 6 to 8 bytes and now carries the packet payload size, so the receiver reassembles data using the sender’s packet size: the server and the client may use different maxPacketSize values without affecting the data exchange. The 4.x wire format is not compatible with 3.x and older versions. - The receiving side now accepts packets up to 65536 bytes regardless of the local maxPacketSize. - Maximum data size per send(…) now depends on maxPacketSize: 65536 * (min(maxPacketSize, 65507) - 8) bytes (66977792 bytes with the default packet size); for jumbo packet sizes it is capped at the largest whole number of packets not exceeding 1073741823 bytes. - Added the packetsizetest functional test (different packet sizes on server and client, parameter validation, size limits). - Interval pacing (channelBandwidthKbps > 0) improvements: the pacing batch is computed from the configured packet size; a late wake-up is now repaid with a bounded catch-up (up to 20 ms of airtime and 2 MB of data) instead of being discarded, so the average rate stays on target with coarse OS timers (e.g. the default Windows timer tick); the minimum batch is 1 packet, so jumbo packets at low bitrates are paced one by one (minimal bursts); close() now aborts an in-flight paced transmission promptly instead of waiting out the whole frame airtime. |
Library files
The library supplied by source code only. The user would be 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.
3rdparty ------------------------ Folder with third-party libraries.
CMakeLists.txt -------------- CMake file to include third-party libraries.
UdpSocket ------------------- Folder with UdpSocket library source code.
src ----------------------------- Folder with the library source code.
CMakeLists.txt -------------- CMake file of the library.
UdpDataChannel.cpp ---------- C++ implementation file.
UdpDataChannel.h ------------ Main header file.
UdpDataChannelVersion.h ----- Header file which includes library version.
UdpDataChannelVersion.h.in -- CMake service file to generate version header.
clienttest ---------------------- Folder for client test application.
CMakeLists.txt -------------- CMake file for client test application.
main.cpp -------------------- Source code of client test application.
servertest ---------------------- Folder for server test application.
CMakeLists.txt -------------- CMake file for server application.
main.cpp -------------------- Source code of server test application.
sendfirsttest ------------------- Folder for the sendFirst functional test.
CMakeLists.txt -------------- CMake file for the sendFirst test.
main.cpp -------------------- Source code of the sendFirst test (FIFO vs latest-only).
packetsizetest ------------------ Folder for the maxPacketSize functional test.
CMakeLists.txt -------------- CMake file for the packet size test.
main.cpp -------------------- Source code of the packet size test (different packet sizes on server and client).
example ------------------------- Folder for example application.
CMakeLists.txt -------------- CMake file for example application.
main.cpp -------------------- Source code of example application.
UdpDataChannel class description
UdpDataChannel class declaration
UdpDataChannel class declared in UdpDataChannel.h file. Class declaration:
namespace cr
{
namespace clib
{
class UdpDataChannel
{
public:
/// Get current library version.
static std::string getVersion();
/// Class constructor.
UdpDataChannel();
/// Class destructor.
~UdpDataChannel();
/// Initialize in client or server mode.
bool init(std::string serverIp,
uint16_t serverPort,
int channelBandwidthKbps = 1000000,
int maxPacketSize = 1030);
/// Close client.
void close();
/// Get input data from connected client.
bool get(uint8_t* data, int bufferSize, int& dataSize, int timeoutMsec = 0);
/// Get input data without copying (zero-copy).
uint8_t* getBuffer(int& dataSize, int timeoutMsec = 0);
/// Send data to the client.
bool send(uint8_t* data, int size, bool sendFirst = false);
/// Get connection status.
bool isConnected();
/// Get open status.
bool isInit();
};
}
}
In additional to UdpDataChannel class the UdpDataClient and UdpDataServer classes are declared in UdpDataChannel.h file. This classes are designed to provide compatibility with software which is using previous version of UdpDataChanel library. This classes just provide different signature of init(…) method. UdpDataClient and UdpDataServer classes declaration:
class UdpDataServer : public UdpDataChannel
{
public:
/// Get current library version.
static std::string getVersion();
/// Initialize server with port number and bandwidth.
bool init(uint16_t port, int channelBandwidthKbps = 1000000,
int maxPacketSize = 1030);
};
class UdpDataClient : public UdpDataChannel
{
public:
/// Get current library version.
static std::string getVersion();
};
getVersion method
The getVersion() method returns string of library version. Method declaration:
static std::string getVersion();
Method can be used without UdpDataChannel class instance:
cout << "UdpDataChannel v: " << cr::clib::UdpDataChannel::getVersion();
Console output:
UdpDataChannel v: 4.0.0
init method
The init(…) method of UdpDataChannel class initializes client or server (depends on parameters). The client choses first available in OS UDP port from 20000 to 65535 range. After UDP port initialization the client starts communication threads and will start connection to server. The server will initialize given port and will start waiting connection from client. After connection established the server and client will be able for data exchange. The init(…) method will run internal communication threads (one for receiving data and one for streaming data). Method declaration:
bool init(std::string serverIp, uint16_t serverPort, int channelBandwidthKbps = 1000000, int maxPacketSize = 1030);
| Parameter | Description |
|---|---|
| serverIp | Server IP. Client will connect to this IP. If ”“ or “0.0.0.0” the library will be initialized as server. |
| serverPort | Server UDP port. Client will connect to this port. Server will initialize this port for client’s connection. |
| channelBandwidthKbps | Sending pacing control (two modes): > 0 - interval mode: the output thread sends packets in batches sized to about 1 millisecond of airtime at the target rate (from 1 to 512 packets, depending on the bitrate and maxPacketSize) and then waits, once per batch, for the exact airtime of the bytes it just sent, so the average send rate matches this bandwidth (kbps) for any packet size. The wait is computed in nanoseconds from the real byte count; if a batch’s interval is long enough the thread sleeps, otherwise it briefly spins. If a wake-up is late (coarse OS timers, e.g. the default 15.6 ms Windows timer tick) the lag is repaid by sending the following batches without waiting, so the average rate stays on target; the catch-up burst is capped at 20 ms of airtime and 2 MB of data. <= 0 - back-pressure mode: the library ignores any target rate and paces by the output socket send-buffer occupancy (it throttles while the buffer is above 80% full). |
| maxPacketSize | Maximum size of one UDP packet (8 bytes header + payload), in bytes, used to split outgoing data. Allowed range [64:65536]; out-of-range values make the method fail. Default 1030 bytes (1022 bytes payload) fits a standard 1500-byte MTU without IP fragmentation. Set a bigger value (e.g. 9000) to use jumbo packets on networks that support them. Values above 65507 (the IPv4 UDP datagram payload limit) are accepted but clamped to 65507 for sending. The parameter affects only sending: every packet header carries the sender’s packet payload size, so the receiver reassembles data using the sender’s packet size and always accepts packets up to 65536 bytes. The server and the client may use different maxPacketSize values without affecting the data exchange. |
Returns: TRUE if the library was successfully initialized or FALSE if not (also FALSE if maxPacketSize is out of the allowed range).
close method
The close(…) method closes client or server and releases all resources. This method stops all threads, closes all sockets and also releases all memory allocated in data buffers. Method declaration:
void close();
get method
The get(…) method returns received data. The library has internal buffer (size of 16 elements). The library put received data to this buffer. When user call get(…) method it returns data from buffer. The buffer can hold several portions of data (up to 16). In case multiple data in input buffer, the method will return the earliest one received or last one if necessary. This way the data will not be lost if the user does not have time to read it. If no input data from client or server the method will wait until it will come or particular timeout. The method is thread-safe and can be called from multiple threads. Method declaration:
bool get(uint8_t* data, int bufferSize, int& dataSize, int timeoutMsec = 0);
| Parameter | Description |
|---|---|
| data | A pointer to a buffer where the received data will be stored. |
| bufferSize | The size of the buffer allocated for storing the received data. If input data has bigger size than buffer the method will return FALSE. |
| dataSize | Size of input data. If no input data size will be 0. |
| timeoutMsec | The timeout period for receiving data, in milliseconds. Values: -1 - wait until data will come. 0 - just check if we have new data. more 0 - wait timeout, msec. less -1 - wait timeout (-1 * timeoutMsec) but take last received data. It is necessary when user need receive only last data and ignore previous. |
Returns: TRUE if there is new data or FALSE if not.
getBuffer method
The getBuffer(…) method returns received data without copying it (zero-copy). Instead of copying into a user buffer like get(…), it returns a pointer to the library’s internal buffer that holds the data. The pointer stays valid until the next call to getBuffer(…), get(…) or close(…) on the same object: the library guarantees the internal receive thread will not overwrite that buffer while the caller holds it (the buffer is excluded from the internal free/ready buffer pool until the next call). Use this method for high bitrate streams to avoid copying large frames. Method declaration:
uint8_t* getBuffer(int& dataSize, int timeoutMsec = 0);
| Parameter | Description |
|---|---|
| dataSize | Size of the returned data. 0 if no data. |
| timeoutMsec | The timeout period for receiving data, in milliseconds. Values: -1 - wait until data will come. 0 - just check if we have new data. more 0 - wait timeout, msec. less -1 - wait timeout (-1 * timeoutMsec) but take last received data and drop older ones. |
Returns: Pointer to the internal data buffer, or nullptr if no data. Do not free the returned pointer and finish reading it before the next getBuffer(…) call.
send method
The send(…) method sends data. The library has an internal output queue (size of 16 elements). The library puts data to the output queue. Internal thread reads data from the queue, splits it by UDP packets and sends them. This way the data will not be lost if the user calls the method multiple times. The method is thread-safe and can be called from multiple threads. The sendFirst flag chooses the queueing policy for the call: by default (false) the data is appended and waits behind anything already queued (FIFO); with true the queue is cleared of any not-yet-sent items and this data becomes the next item on the wire (latest-only) — useful for real-time streams where transmitting a stale backlog would only add latency. Method declaration:
bool send(uint8_t* data, int size, bool sendFirst = false);
| Parameter | Description |
|---|---|
| data | A pointer to the data buffer containing the data to be sent. |
| size | Data size to send. Maximum size is 65536 * payload bytes, where payload = min(maxPacketSize, 65507) - 8: one data portion is split into at most 65536 UDP packets. For jumbo packet sizes the maximum is additionally capped at the largest whole number of packets not exceeding 1073741823 bytes. With the default packet size (1030 bytes) the maximum is 66977792 bytes. If size exceeds the maximum the method will return FALSE. |
| sendFirst | Output-queue policy for this call. false (default) — FIFO: the data is appended to the output queue; if items are already queued the new data waits its turn behind them (the queue is capped at 16 items, dropping the oldest when full). true — latest-only: every item still waiting in the output queue (not yet picked up by the sending thread) is discarded and this data becomes the next item transmitted. The item currently being transmitted is not interrupted — the new data goes on the wire right after the current transmission finishes, with no stale queued item sent before or after it. |
Returns: TRUE if the data accepted to send or FALSE if not.
isConnected method
The isConnected(…) method return connection status between client and server. After initialization the client tries connect to server and server waits connection from client. Once the client connected to server the method will be returning TRUE. Method declaration:
bool isConnected();
Returns: TRUE if the client is currently connected to the server or FALSE if not.
isInit method
The isInit(…) method return client or server initialization status. Method declaration:
bool isInit();
Returns: TRUE if the client is initialized or FALSE if not.
Build and connect to your project
Typical commands to build the library:
cd UdpDataChannel
mkdir build
cd build
cmake ..
make
If you want to connect UdpDataChannel to your CMake project as source code you can follow these steps. For example, if your repository has structure:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
Create folder 3rdparty in you repository and copy UdpDataChannel repository folder there. The new structure of your repository:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
UdpDataChannel
Create CMakeLists.txt file in 3rdparty folder. CMakeLists.txt 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)
################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_UDP_DATA_CHANNEL)
add_subdirectory(UdpDataChannel)
endif()
File 3rdparty/CMakeLists.txt adds folder UdpDataChannel to your project and excludes test applications from compiling (by default test applications and example excluded from compiling if UdpDataChannel included as sub-repository). The new structure of your repository:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
CMakeLists.txt
UdpDataChannel
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 UdpDataChannel library in your src/CMakeLists.txt file:
target_link_libraries(${PROJECT_NAME} UdpDataChannel)
Done!
Example
This examples demonstrates how to use server and client. The application creates thread for server which waits data from client and send responses (the same data). Client sends random data to server in main thread and waits response from server. Source code of example:
#include <iostream>
#include <thread>
#include "UdpDataChannel.h"
void serverThreadFunction()
{
// Init as server.
cr::clib::UdpDataChannel server;
if (!server.init("0.0.0.0", 58002))
return;
// Thread loop.
uint8_t buffer[256];
while (true)
{
// Wait data from client for 2 sec.
int size = 0;
if (!server.get(buffer, 256, size, 2000))
continue;
// Send the same data back to client.
if (!server.send(buffer, size))
std::cout << "Can't send data to client" << std::endl;
}
}
int main(void)
{
// Run server thread.
std::thread serverThread(&serverThreadFunction);
// Init as client.
cr::clib::UdpDataChannel client;
if (!client.init("127.0.0.1", 58002))
return -1;
// Main loop.
uint8_t buffer[256];
while (true)
{
// Prepare random data for server.
int size = (rand() % 128) + 1;
memset(buffer, (rand() % 255), size);
// Send data to server.
if (!client.send(buffer, size))
std::cout << "Can't send data to server" << std::endl;
else
std::cout << size << " bytes to server" << std::endl;
// Wait data from server. 500 msec.
size = 0;
if (client.get(buffer, 256, size, 500))
std::cout << size << " bytes from server" << std::endl;
else
std::cout << "No data from server" << std::endl;
}
return 1;
}