feat(tcp): TCPStreamer DataSources, in-process TCPStreamService, stream multiplexing and TCPStreamJoin
Add TCPStreamer/TCPStreamerClient (TCP transport behind the UDPSServerI/UDPSClientI seam), an optional single in-process TCPStreamService serving many streams over worker threads, multiplexed SUBSCRIBE (comma list, stream index in fragmentIdx), and TCPStreamJoin, which merges several streams into one sample-and-hold stream paced by a master. StreamHub takes a comma list of streams over one connection. Includes GTests, E2E scenarios s58-s73 and docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
5562877c99
commit
50d85363a3
79 files changed
+12348
-138
No files matched your search
@@ -84,6 +84,7 @@ UDPStreamer::UDPStreamer()
|
||||
: MemoryDataSourceI(), EmbeddedServiceMethodBinderI(), executor(*this) {
|
||||
port = UDPS_DEFAULT_PORT;
|
||||
maxPayloadSize = UDPS_DEFAULT_MAX_PAYLOAD;
|
||||
server = new UDPSServer();
|
||||
cpuMask = 0xFFFFFFFFu;
|
||||
stackSize = THREADS_DEFAULT_STACKSIZE;
|
||||
publishMode = UDPStreamerPublishStrict;
|
||||
@@ -135,7 +136,11 @@ UDPStreamer::~UDPStreamer() {
|
||||
}
|
||||
}
|
||||
|
||||
(void)server.Stop();
|
||||
if (server != NULL_PTR(UDPSServerI *)) {
|
||||
(void)server->Stop();
|
||||
delete server;
|
||||
server = NULL_PTR(UDPSServerI *);
|
||||
}
|
||||
|
||||
HeapI *heapAccum = GlobalObjectsDatabase::Instance()->GetStandardHeap();
|
||||
if (accumBuffer != NULL_PTR(uint8 *)) {
|
||||
@@ -192,7 +197,7 @@ bool UDPStreamer::Initialise(StructuredDataI &data) {
|
||||
|
||||
if (ok) {
|
||||
if (!data.Read("MaxPayloadSize", maxPayloadSize)) {
|
||||
maxPayloadSize = UDPS_DEFAULT_MAX_PAYLOAD;
|
||||
maxPayloadSize = DefaultMaxPayloadSize();
|
||||
REPORT_ERROR(ErrorManagement::Information,
|
||||
"MaxPayloadSize not specified; using default %u.",
|
||||
maxPayloadSize);
|
||||
@@ -286,38 +291,58 @@ bool UDPStreamer::Initialise(StructuredDataI &data) {
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
// Build server config with already-resolved Port and MaxPayloadSize so
|
||||
// UDPSServer::Initialise() always sees them, even when defaults were used.
|
||||
ConfigurationDatabase serverCfg;
|
||||
(void)serverCfg.Write("Port", static_cast<uint32>(port));
|
||||
(void)serverCfg.Write("MaxPayloadSize", maxPayloadSize);
|
||||
// Forward optional multicast / timeout params if present in caller's data.
|
||||
StreamString mcGroup;
|
||||
if (data.Read("MulticastGroup", mcGroup) && (mcGroup.Size() > 0u)) {
|
||||
(void)serverCfg.Write("MulticastGroup", mcGroup.Buffer());
|
||||
uint32 dp = 0u;
|
||||
if (data.Read("DataPort", dp)) {
|
||||
(void)serverCfg.Write("DataPort", dp);
|
||||
}
|
||||
StreamString iface;
|
||||
if (data.Read("Interface", iface)) {
|
||||
(void)serverCfg.Write("Interface", iface);
|
||||
} else {
|
||||
ok = false;
|
||||
REPORT_ERROR(ErrorManagement::InitialisationError,
|
||||
"Missing mandatory interface for multicasting");
|
||||
}
|
||||
}
|
||||
uint32 clientTimeout = 0u;
|
||||
if (data.Read("ClientTimeout", clientTimeout)) {
|
||||
(void)serverCfg.Write("ClientTimeout", clientTimeout);
|
||||
}
|
||||
ok = server.Initialise(serverCfg);
|
||||
ok = InitialiseTransport(data);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint32 UDPStreamer::DefaultMaxPayloadSize() const {
|
||||
return UDPS_DEFAULT_MAX_PAYLOAD;
|
||||
}
|
||||
|
||||
bool UDPStreamer::InitialiseTransport(StructuredDataI &data) {
|
||||
bool ok = true;
|
||||
// Build server config with already-resolved Port and MaxPayloadSize so
|
||||
// UDPSServer::Initialise() always sees them, even when defaults were used.
|
||||
ConfigurationDatabase serverCfg;
|
||||
(void)serverCfg.Write("Port", static_cast<uint32>(port));
|
||||
(void)serverCfg.Write("MaxPayloadSize", maxPayloadSize);
|
||||
// Forward optional multicast / timeout params if present in caller's data.
|
||||
StreamString mcGroup;
|
||||
if (data.Read("MulticastGroup", mcGroup) && (mcGroup.Size() > 0u)) {
|
||||
(void)serverCfg.Write("MulticastGroup", mcGroup.Buffer());
|
||||
uint32 dp = 0u;
|
||||
if (data.Read("DataPort", dp)) {
|
||||
(void)serverCfg.Write("DataPort", dp);
|
||||
}
|
||||
StreamString iface;
|
||||
if (data.Read("Interface", iface)) {
|
||||
(void)serverCfg.Write("Interface", iface);
|
||||
} else {
|
||||
ok = false;
|
||||
REPORT_ERROR(ErrorManagement::InitialisationError,
|
||||
"Missing mandatory interface for multicasting");
|
||||
}
|
||||
}
|
||||
uint32 clientTimeout = 0u;
|
||||
if (data.Read("ClientTimeout", clientTimeout)) {
|
||||
(void)serverCfg.Write("ClientTimeout", clientTimeout);
|
||||
}
|
||||
ok = server->Initialise(serverCfg);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void UDPStreamer::SetServer(UDPSServerI *newServer) {
|
||||
if (server != NULL_PTR(UDPSServerI *)) {
|
||||
delete server;
|
||||
}
|
||||
server = newServer;
|
||||
}
|
||||
|
||||
UDPSServerI *UDPStreamer::Transport() const { return server; }
|
||||
|
||||
bool UDPStreamer::SetConfiguredDatabase(StructuredDataI &data) {
|
||||
bool ok = MemoryDataSourceI::SetConfiguredDatabase(data);
|
||||
if (!ok) {
|
||||
@@ -804,7 +829,7 @@ bool UDPStreamer::PrepareNextState(const char8 *const currentStateName,
|
||||
const char8 *const nextStateName) {
|
||||
bool ok = true;
|
||||
|
||||
ok = server.Start();
|
||||
ok = server->Start();
|
||||
|
||||
/* Build the CONFIG payload and cache it in the server so any CONNECT client
|
||||
* receives it immediately. The config is static for the lifetime of this
|
||||
@@ -818,7 +843,7 @@ bool UDPStreamer::PrepareNextState(const char8 *const currentStateName,
|
||||
if (cfgBuf != NULL_PTR(uint8 *)) {
|
||||
uint32 cfgPayloadSize = 0u;
|
||||
if (BuildConfigPayload(cfgBuf, configBufSize, cfgPayloadSize)) {
|
||||
(void)server.SendConfig(cfgBuf, cfgPayloadSize);
|
||||
(void)server->SendConfig(cfgBuf, cfgPayloadSize);
|
||||
} else {
|
||||
REPORT_ERROR(ErrorManagement::Warning,
|
||||
"Could not build initial CONFIG payload.");
|
||||
@@ -992,7 +1017,7 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
|
||||
|
||||
/* --- Poll for incoming control commands (CONNECT / DISCONNECT / ACK) ---
|
||||
*/
|
||||
server.ServiceClients();
|
||||
server->ServiceClients();
|
||||
|
||||
/* Synchronise() already gates publication to the correct rate (size/time
|
||||
* for Accumulate, every-Nth for Decimate, every call for Strict). The
|
||||
@@ -1015,12 +1040,12 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
|
||||
}
|
||||
bufMutex.FastUnLock();
|
||||
|
||||
if ((fill > 0u) && server.HasClients()) {
|
||||
if ((fill > 0u) && server->HasClients()) {
|
||||
SerializeAccumulated(scratchBuffer, scratchTimestamps, fill);
|
||||
uint32 sendBytes =
|
||||
UDPS_TIMESTAMP_BYTES + 4u + fill * singleCycleWireBytes;
|
||||
packetCounter++;
|
||||
if (!server.SendData(packetCounter, wireBuffer, sendBytes)) {
|
||||
if (!server->SendData(packetCounter, wireBuffer, sendBytes)) {
|
||||
REPORT_ERROR(ErrorManagement::Warning,
|
||||
"Failed to send Accumulate DATA packet (counter=%u).",
|
||||
packetCounter);
|
||||
@@ -1040,11 +1065,11 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
|
||||
}
|
||||
bufMutex.FastUnLock();
|
||||
|
||||
if (pending && server.HasClients()) {
|
||||
if (pending && server->HasClients()) {
|
||||
QuantizeAndSerialize(scratchBuffer, ts);
|
||||
|
||||
packetCounter++;
|
||||
if (!server.SendData(packetCounter, wireBuffer, totalWireBytes)) {
|
||||
if (!server->SendData(packetCounter, wireBuffer, totalWireBytes)) {
|
||||
REPORT_ERROR(ErrorManagement::Warning,
|
||||
"Failed to send DATA packet (counter=%u).",
|
||||
packetCounter);
|
||||
@@ -1056,7 +1081,7 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
|
||||
}
|
||||
|
||||
if (info.GetStage() == ExecutionInfo::TerminationStage) {
|
||||
(void)server.Stop();
|
||||
(void)server->Stop();
|
||||
REPORT_ERROR(ErrorManagement::Information,
|
||||
"UDPStreamer background thread terminated.");
|
||||
}
|
||||
@@ -1367,9 +1392,9 @@ uint16 UDPStreamer::GetPort() const { return port; }
|
||||
|
||||
uint32 UDPStreamer::GetMaxPayloadSize() const { return maxPayloadSize; }
|
||||
|
||||
bool UDPStreamer::IsClientConnected() const { return server.HasClients(); }
|
||||
bool UDPStreamer::IsClientConnected() const { return server->HasClients(); }
|
||||
|
||||
bool UDPStreamer::IsMulticast() const { return server.IsMulticast(); }
|
||||
bool UDPStreamer::IsMulticast() const { return server->IsMulticast(); }
|
||||
|
||||
uint32 UDPStreamer::GetDroppedPublications() const { return droppedPublications; }
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "SingleThreadService.h"
|
||||
#include "StreamString.h"
|
||||
#include "UDPSServer.h"
|
||||
#include "UDPSTransportI.h"
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Class declaration */
|
||||
@@ -333,6 +334,33 @@ public:
|
||||
*/
|
||||
uint32 GetDroppedPublications() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief MaxPayloadSize used when the configuration does not specify one.
|
||||
* @details Transport specific (a datagram budget for UDP, a frame budget for
|
||||
* TCP), hence overridable. Called from Initialise(), not the constructor.
|
||||
*/
|
||||
virtual uint32 DefaultMaxPayloadSize() const;
|
||||
|
||||
/**
|
||||
* @brief Configures the transport from the already-resolved Port and
|
||||
* MaxPayloadSize plus whatever transport keys @p data carries.
|
||||
* @details The default implementation configures the UDPSServer (unicast /
|
||||
* multicast). A subclass that installs another transport with SetServer()
|
||||
* overrides this to configure it instead.
|
||||
*/
|
||||
virtual bool InitialiseTransport(StructuredDataI &data);
|
||||
|
||||
/**
|
||||
* @brief Replaces the transport. Takes ownership; only valid before Start().
|
||||
*/
|
||||
void SetServer(UDPSServerI *newServer);
|
||||
|
||||
/**
|
||||
* @brief The transport currently in use (owned by this object).
|
||||
*/
|
||||
UDPSServerI *Transport() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Serializes the CONFIG payload into buf and sets payloadSize.
|
||||
@@ -417,8 +445,8 @@ private:
|
||||
uint8 *wireBuffer; /**< Pre-allocated buffer for the serialized DATA payload */
|
||||
uint32 totalWireBytes; /**< Total bytes of all signals after quantization + 8-byte timestamp prefix */
|
||||
|
||||
/* Networking — handled by UDPSServer */
|
||||
UDPSServer server; /**< Multi-client session manager (unicast and multicast) */
|
||||
/* Networking — handled by a UDPSServerI (UDPSServer by default) */
|
||||
UDPSServerI *server; /**< Owned transport; see SetServer() */
|
||||
|
||||
/* Thread management */
|
||||
SingleThreadService executor; /**< Background thread service */
|
||||
|
||||
@@ -135,4 +135,6 @@
|
||||
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicUDPSocket.h \
|
||||
/home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \
|
||||
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \
|
||||
../../../..//Common/UDP/UDPSProtocol.h
|
||||
../../../..//Common/UDP/UDPSProtocol.h \
|
||||
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h \
|
||||
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h
|
||||
@@ -135,4 +135,6 @@ UDPStreamer.o: UDPStreamer.cpp \
|
||||
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicUDPSocket.h \
|
||||
/home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \
|
||||
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \
|
||||
../../../..//Common/UDP/UDPSProtocol.h
|
||||
../../../..//Common/UDP/UDPSProtocol.h \
|
||||
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h \
|
||||
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h
|
||||
Reference in new issue
Block a user