Files
Martino FerrariandClaude Opus 4.6 50d85363a3 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>
2026-11-06 13:41:27 +01:00

175 lines
6.6 KiB
C++

#ifndef TCPSTREAMSERVICE_H_
#define TCPSTREAMSERVICE_H_
/**
* @file TCPStreamService.h
* @brief One in-process TCP streaming server shared by any number of
* TCPStreamer DataSources, served by a pool of worker threads.
*
* @details Declared once, at the root of the configuration:
* <pre>
* +StreamService = {
* Class = TCPStreamService
* Port = 44500
* Workers = 2
* }
* </pre>
* and referenced from any number of DataSources:
* <pre>
* +Fast = { Class = TCPStreamer Service = StreamService StreamName = Fast ... }
* +Slow = { Class = TCPStreamer Service = StreamService StreamName = Slow ... }
* </pre>
*
* Wire protocol: plain UDPS framing over TCP, exactly like TCPSServer, plus one
* client-to-server frame. Right after connecting, a client sends a SUBSCRIBE
* frame (UDPS_TYPE_SUBSCRIBE, payload = stream name, 1..255 bytes). The service
* then sends that stream's CONFIG followed by its DATA frames. One connection =
* one stream. A connection that does not subscribe within HandshakeTimeout
* seconds, or that names an unknown stream, is closed (clients retry).
*
* Threads: one acceptor plus `Workers` I/O threads. A connection lives on one
* worker. Producer threads never touch sockets: they copy each frame into every
* subscriber's bounded queue and poke the owning worker through a self-pipe, so
* a slow client only loses its own frames and never delays a producer or
* another client.
*
* @par Parameters (all optional)
* | Parameter | Type | Default | Description |
* |--------------------|--------|---------|-------------|
* | Port | uint32 | 44500 | TCP listen port; 0 = ephemeral (see GetBoundPort()). |
* | Workers | uint32 | 2 | I/O threads (1..32). |
* | MaxClients | uint32 | 64 | Simultaneous connections (1..1024). |
* | MaxStreams | uint32 | 32 | Registered streams (1..256). |
* | ClientQueueDepth | uint32 | 8 | DATA frames queued per client (1..4096). |
* | ClientStallTimeout | uint32 | 5 | Seconds without progress before eviction; 0 disables. |
* | HandshakeTimeout | uint32 | 5 | Seconds allowed to send SUBSCRIBE. |
* | SendBufferSize | uint32 | 0 | SO_SNDBUF for client sockets; 0 keeps the OS default. |
* | CPUMask | uint32 | 0xFFFFFFFF | Affinity of the service threads. |
* | StackSize | uint32 | 65536 | Stack of the service threads. |
*/
#include "EmbeddedServiceMethodBinderI.h"
#include "ExecutionInfo.h"
#include "FastPollingMutexSem.h"
#include "MutexSem.h"
#include "Object.h"
#include "SingleThreadService.h"
#include "StructuredDataI.h"
#include "TCPSStreamTapI.h"
#include "UDPSProtocol.h"
namespace MARTe {
class TCPSWorker;
class TCPSServiceStream;
struct TCPSConn;
/** Longest stream name accepted in a SUBSCRIBE frame. */
static const uint32 TCPS_MAX_STREAM_NAME = 255u;
/** Most streams one connection may multiplex. */
static const uint32 TCPS_MAX_CONN_STREAMS = UDPS_TCP_MAX_STREAMS;
/** Longest SUBSCRIBE payload: a comma separated list of stream names. */
static const uint32 TCPS_MAX_SUBSCRIBE_PAYLOAD = UDPS_TCP_MAX_SUBSCRIBE_PAYLOAD;
class TCPStreamService : public Object, public EmbeddedServiceMethodBinderI {
public:
CLASS_REGISTER_DECLARATION()
TCPStreamService();
virtual ~TCPStreamService();
/** Reads the parameters, opens the listening socket and starts the threads. */
virtual bool Initialise(StructuredDataI &data);
/** Acceptor thread entry point; do not call directly. */
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);
/** @return the port actually bound. */
uint16 GetBoundPort() const;
/** @return subscribed connections across all streams. */
uint32 GetClientCount() const;
uint32 GetStreamCount() const;
uint32 GetWorkerCount() const;
/** @return DATA frames dropped for slow clients, summed over registered streams. */
uint64 GetClientDrops() const;
/** @return connections closed by the stall timeout (all time). */
uint32 GetStallEvictions() const;
/** @return connections closed for a bad / unknown SUBSCRIBE (all time). */
uint32 GetRejectedSubscriptions() const;
/* ---- stream side ---- */
/** @return false if a stream with this name is already registered or the table is full. */
bool RegisterStream(TCPSServiceStream *s);
/** Remove the stream and make its subscribers close. */
void UnregisterStream(TCPSServiceStream *s);
/**
* Be told when streams register / unregister. The streams already registered are
* replayed to the observer before AddObserver returns. Notifications are serialised
* (and may nest on one thread), they never run under the topology lock, and once
* RemoveObserver returns no callback is running or will start.
* @return false if the observer table is full.
*/
bool AddObserver(TCPSStreamObserverI *obs);
void RemoveObserver(TCPSStreamObserverI *obs);
/* ---- worker side (internal) ---- */
/**
* Bind a connection to the streams named in @p streamList (comma separated,
* modified in place). The position in the list is the stream index stamped
* on every frame. All-or-nothing: @return false if any name is unknown,
* duplicated or malformed.
*/
bool Attach(TCPSConn *c, char8 *streamList);
void Detach(TCPSConn *c);
void ConnClosed();
void NoteEviction();
void NoteRejected();
uint32 QueueDepth() const;
uint64 StallTicks() const;
uint64 HandshakeTicks() const;
/** Max subscribers a stream may hold. */
uint32 MaxClients() const;
private:
void Shutdown();
uint32 port;
uint16 boundPort;
uint32 numWorkers;
uint32 maxClients;
uint32 maxStreams;
uint32 queueDepth;
uint32 stallTimeoutS;
uint32 handshakeTimeoutS;
uint32 sendBufferSize;
uint32 cpuMask;
uint32 stackSize;
uint64 stallTicks;
uint64 handshakeTicks;
int listenFd;
SingleThreadService acceptThread;
bool acceptorStarted;
TCPSWorker **workers;
uint32 workersCreated;
/** Most observers (TCPStreamJoin objects) one service can feed. */
static const uint32 TCPS_MAX_OBSERVERS = 16u;
MutexSem notifyMutex; ///< recursive; serialises observer calls with (un)registration
TCPSStreamObserverI *observers[TCPS_MAX_OBSERVERS];
uint32 numObservers;
mutable FastPollingMutexSem topology; ///< guards streams[] and every TCPSConn::stream
TCPSServiceStream **streams;
uint32 numStreams;
volatile int32 connCount;
volatile int32 evictions;
volatile int32 rejected;
};
} // namespace MARTe
#endif // TCPSTREAMSERVICE_H_