
UdpDataChannel C++ library
v4.0.2
Table of contents
- Overview
- Versions
- Library files
- UdpDataChannel class description
- Build and connect to your project
- Portability
- 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) and UdpDataServer (waits connection from client). All classes provide two-way communication. The library uses C++17 standard and only depends on the UdpSocket library which provides functions to work with UDP sockets (the UdpSocket repository is not part of this repository, see Build stand alone). This library compatible with Linux and Windows. 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).
UdpDataChannel 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 |
|---|---|---|
| 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. |
| 4.0.1 | 16.10.2026 | - The repository does not contain sub-repositories any more: the UdpSocket repository must be located next to the UdpDataChannel repository (see Build stand alone). Build and connect instructions updated. - Fixed memory amplification: a data packet of 9 bytes could make the receiver allocate and fill about 1 GB (the size of a data portion is announced in the packet header). The memory of the input buffers is now committed by the OS only when packets are written; if the memory is not available the data portion is dropped instead of terminating the process. - Fixed the order of the output queue: if more than 16 send(…) calls were made before the sending thread woke up, the newest data could be transmitted before older data. Now the oldest queued item is dropped and the order is kept. - Fixed undefined behavior of get(…) and getBuffer(…) with the timeout INT_MIN. - get(…) sets dataSize to 0 if it returns FALSE. - The constructor does not call srand(…) any more: the global random generator of the application is not changed. - The ping period and the connection timeout use the monotonic clock: a change of the system time does not break the connection. - init(…) returns FALSE if the IP address is not valid, a socket can not be opened or a thread can not be started (the output socket is opened by init(…), not by the sending thread, so a failure is not left unnoticed). - close() releases the input buffers which grew above the initial size. - getBuffer(…) does not lose an internal buffer if it is called from several threads at the same time (the returned buffer is valid until the next call of getBuffer(…) of any thread). - The destructor of UdpDataChannel is virtual; copy and move of the classes are deleted. - The 2 bytes fields of the data packet header are stored in little-endian byte order on all platforms (the packets are the same as before on little-endian CPUs). - Strict compiler warnings and hardening flags (stack protector, FORTIFY_SOURCE, stack clash protection, CET/BTI, full RELRO, non-executable stack, control flow guard) are enabled for the library, the test applications and the example; standard C++ without compiler extensions. - packetsizetest extended: packet layout, invalid and damaged packets, API misuse, output queue order. Test applications and the example check the input and stop on Ctrl+C. |
| 4.0.2 | 25.10.2026 | - The library links the thread library (CMake target Threads::Threads): programs built on a C library older than glibc 2.34 no longer need -pthread. |
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.
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, size limits, packet layout, invalid and damaged packets, API misuse, output queue order). The optional argument **quick** skips the test with a data portion of about 1 GB, the argument **slow** does the same and lowers the channel bandwidth to 100 Mbps (for runs under sanitizers or an emulator).
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 rapidpixel
{
class UdpDataChannel
{
public:
/// Data packet header size, bytes.
static constexpr int PACKET_HEADER_SIZE = 8;
/// Default max packet size (header + payload), bytes.
static constexpr int DEFAULT_PACKET_SIZE = 1030;
/// Minimum allowed value of init(...)'s maxPacketSize, bytes.
static constexpr int MIN_PACKET_SIZE = 64;
/// Maximum allowed value of init(...)'s maxPacketSize, bytes.
static constexpr int MAX_PACKET_SIZE = 65536;
/// Max UDP datagram payload transmittable over IPv4, bytes.
static constexpr int IPV4_MAX_PACKET_SIZE = 65507;
/// Hard cap of data size accepted by send(...), bytes.
static constexpr int MAX_DATA_SIZE = 1073741823;
/// Get current library version.
static std::string getVersion();
/// Class constructor.
UdpDataChannel();
/// Class destructor.
virtual ~UdpDataChannel();
/// Copy and move are not allowed.
UdpDataChannel(const UdpDataChannel& src) = delete;
UdpDataChannel(UdpDataChannel&& src) = delete;
UdpDataChannel& operator=(const UdpDataChannel& src) = delete;
UdpDataChannel& operator=(UdpDataChannel&& src) = delete;
/// 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();
};
}
The UdpDataClient and UdpDataServer classes are also declared in UdpDataChannel.h file. UdpDataServer provides the init(…) method which takes only the UDP port (the library is initialized as server), UdpDataClient has no additional methods. All other methods are the methods of UdpDataChannel. 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:
std::cout << "UdpDataChannel v: " << rapidpixel::UdpDataChannel::getVersion();
Console output:
UdpDataChannel v: 4.0.2
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). If the channel is already initialized it is closed first (if maxPacketSize is out of the allowed range the method fails before that and the state of the channel is not changed). The method must not be called while another thread is inside any method of the same object. Method declaration:
bool init(std::string serverIp, uint16_t serverPort, int channelBandwidthKbps = 1000000, int maxPacketSize = 1030);
| Parameter | Description |
|---|---|
| serverIp | Server IPv4 address in dotted decimal notation (host names are not resolved). 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 (the IP address is not valid, maxPacketSize is out of the allowed range, the UDP sockets can not be opened or the threads can not be started). After a failure other than the maxPacketSize range the channel is closed.
close method
The close(…) method closes client or server. This method stops all threads, closes all sockets, clears the output data buffers and drops the received data and releases the memory of the input data buffers which grew above the initial size (the other buffers are released by the destructor). The method can be called at any time (also if the library is not initialized). A thread which waits in get(…) or getBuffer(…) with the timeout -1 is not woken up by this method: do not close the library while another thread waits without timeout. The method must not be called while another thread is inside any method of the same object. Method declaration:
void close();
get method
The get(…) method returns received data. The library has internal buffer (size of 32 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 32). 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 the buffer is full the oldest portion of data is dropped. 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. Must not be nullptr and must be valid for bufferSize bytes. |
| bufferSize | The size of the buffer allocated for storing the received data, must be greater than 0. If input data has bigger size than buffer the method will return FALSE and the portion of data is dropped. |
| dataSize | Size of input data. If the method returns FALSE the size is 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 (no data came in time, data is nullptr, bufferSize is not greater than 0 or the data is bigger than the buffer).
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. The returned buffer is valid only until the next call of the method (from any thread): call the method from one thread at a time. 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. If the queue is full the oldest item which is still waiting is dropped to make room for the new data. The item which is being transmitted is never dropped: if the buffer for the new data is occupied by it, the method waits until the transmission of this item is finished. The method copies the data, the buffer can be reused after the call. The method is thread-safe and can be called from multiple threads (the calls are executed one by one). 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. Must not be nullptr and must be valid for size bytes. |
| 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 is not greater than 0 or 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 waiting item 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 (data is nullptr, size is out of range, the library is not initialized or not connected, or the memory is not available).
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. The method is thread-safe. 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. The method is thread-safe. Method declaration:
bool isInit();
Returns: TRUE if the client is initialized or FALSE if not.
Build and connect to your project
Build stand alone
The repository does not contain sub-repositories. The UdpSocket library which UdpDataChannel depends on must be cloned into the same folder as UdpDataChannel. The folder structure must be:
<work folder>
UdpDataChannel
UdpSocket
Typical commands to build the library, the test applications and the example (use the URLs of the repositories provided to you):
git clone <UdpDataChannel repository URL>
git clone <UdpSocket repository URL>
cd UdpDataChannel
mkdir build
cd build
cmake ..
make
On Windows use cmake --build . --config Release instead of make. If UdpDataChannel 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. The test applications and the example are built into the folder bin of the build folder.
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 UdpDataChannel is the top-level project).
| Option | Default | Description |
|---|---|---|
| ${PARENT}_UDP_DATA_CHANNEL | ON | Build the library. |
| ${PARENT}_UDP_DATA_CHANNEL_CLIENT_TEST | ON (stand alone), OFF (included in other project) | Build the client test application. |
| ${PARENT}_UDP_DATA_CHANNEL_SERVER_TEST | ON (stand alone), OFF (included in other project) | Build the server test application. |
| ${PARENT}_UDP_DATA_CHANNEL_SENDFIRST_TEST | ON (stand alone), OFF (included in other project) | Build the sendFirst functional test. |
| ${PARENT}_UDP_DATA_CHANNEL_PACKET_SIZE_TEST | ON (stand alone), OFF (included in other project) | Build the packet size functional test. |
| ${PARENT}_UDP_DATA_CHANNEL_EXAMPLE | ON (stand alone), OFF (included in other project) | Build the example. |
| ${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 UdpDataChannel as a sub-repository. |
| CMAKE_BUILD_TYPE | Release (if not specified) | Build type. |
Connect to your project
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 UdpSocket and UdpDataChannel repository folders there. The new structure of your repository:
CMakeLists.txt
src
CMakeList.txt
yourLib.h
yourLib.cpp
3rdparty
UdpDataChannel
UdpSocket
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)
################################################################################
## 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_UDP_DATA_CHANNEL ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_UDP_DATA_CHANNEL)
SET(${PARENT}_UDP_DATA_CHANNEL ON CACHE BOOL "" FORCE)
SET(${PARENT}_UDP_DATA_CHANNEL_CLIENT_TEST OFF CACHE BOOL "" FORCE)
SET(${PARENT}_UDP_DATA_CHANNEL_SERVER_TEST OFF CACHE BOOL "" FORCE)
SET(${PARENT}_UDP_DATA_CHANNEL_SENDFIRST_TEST OFF CACHE BOOL "" FORCE)
SET(${PARENT}_UDP_DATA_CHANNEL_PACKET_SIZE_TEST OFF CACHE BOOL "" FORCE)
SET(${PARENT}_UDP_DATA_CHANNEL_EXAMPLE 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_UDP_DATA_CHANNEL)
add_subdirectory(UdpDataChannel)
endif()
File 3rdparty/CMakeLists.txt adds the folders UdpSocket and UdpDataChannel to your project and excludes test applications and the example from compiling. When UdpDataChannel is included in another project it does not add UdpSocket by itself: your project must add UdpSocket (as in the file above). If UdpSocket is not added the build 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
UdpDataChannel
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 UdpDataChannel library in your src/CMakeLists.txt file:
target_link_libraries(${PROJECT_NAME} UdpDataChannel)
Done!
Portability
The library is written in standard C++17 and does not contain CPU specific code (no SIMD). The 2 bytes fields of the data packet header (packet ID, max packet ID and payload size) and the block ID of a block confirmation message are stored in little-endian byte order on every platform, so computers with different byte order can exchange data. The library, the test applications and the example are built without compiler warnings with GCC 9, GCC 11 and GCC 12 for x86-64 Linux and with GCC 11 for aarch64 Linux (the tests were run on x86-64 Linux and on aarch64 Linux under qemu-user).
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. The application works until Ctrl+C is pressed. Source code of example:
#include <atomic>
#include <csignal>
#include <cstdint>
#include <cstring>
#include <functional>
#include <iostream>
#include <thread>
#include "UdpDataChannel.h"
namespace
{
/// Stop flag, set by the signal handler (Ctrl+C).
std::atomic<bool> g_stop{false};
static_assert(std::atomic<bool>::is_always_lock_free,
"the flag is used in a signal handler");
/// Size of the data buffers, bytes.
constexpr int BUFFER_SIZE{256};
/// Max size of the data sent by the client, bytes.
constexpr int MAX_DATA_SIZE{128};
/// Signal handler: stop the application.
void onSignal(int)
{
g_stop = true;
}
/// Server thread function: send the received data back to the client.
void serverThreadFunction(rapidpixel::UdpDataChannel& server)
{
// Thread loop.
uint8_t buffer[BUFFER_SIZE];
while (!g_stop)
{
// Wait data from client for 200 msec.
int size = 0;
if (!server.get(buffer, BUFFER_SIZE, size, 200))
{
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()
{
std::signal(SIGINT, &onSignal);
// Init as server.
rapidpixel::UdpDataChannel server;
if (!server.init("0.0.0.0", 58002))
{
std::cout << "Can't initialize server" << std::endl;
return -1;
}
// Init as client.
rapidpixel::UdpDataChannel client;
if (!client.init("127.0.0.1", 58002))
{
std::cout << "Can't initialize client" << std::endl;
return -1;
}
// Run server thread.
std::thread serverThread(&serverThreadFunction, std::ref(server));
// Main loop.
uint8_t buffer[BUFFER_SIZE];
uint32_t random = 1;
while (!g_stop)
{
// Prepare random data for server.
random = random * 1664525U + 1013904223U;
int size = static_cast<int>((random >> 8) % MAX_DATA_SIZE) + 1;
std::memset(buffer, static_cast<int>((random >> 16) % 255),
static_cast<size_t>(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, BUFFER_SIZE, size, 500))
{
std::cout << size << " bytes from server" << std::endl;
}
else
{
std::cout << "No data from server" << std::endl;
}
}
// Stop the server thread.
serverThread.join();
return 0;
}