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>
205 lines
5.3 KiB
C++
205 lines
5.3 KiB
C++
/**
|
|
* @file TCPSFrameQueue.cpp
|
|
* @brief Implementation of TCPSFrameQueue.
|
|
*/
|
|
|
|
#include "TCPSFrameQueue.h"
|
|
|
|
#include "HighResolutionTimer.h"
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/types.h>
|
|
|
|
namespace MARTe {
|
|
|
|
struct TCPSFrameQueue::Slot {
|
|
Slot() : buf(NULL_PTR(uint8 *)), cap(0u), len(0u), isConfig(false), stream(0u) {}
|
|
~Slot() {
|
|
if (buf != NULL_PTR(uint8 *)) {
|
|
delete[] buf;
|
|
}
|
|
}
|
|
uint8 *buf;
|
|
uint32 cap;
|
|
uint32 len;
|
|
bool isConfig;
|
|
uint16 stream;
|
|
};
|
|
|
|
/** magic(4) + type(1) + counter(4): offset of the header's fragmentIdx field. */
|
|
static const uint32 TCPS_FRAGIDX_OFFSET = 9u;
|
|
|
|
TCPSFrameQueue::TCPSFrameQueue()
|
|
: slots(NULL_PTR(Slot **)), cap(0u), depth(0u), count(0u), sent(0u), lastProgress(0u) {
|
|
}
|
|
|
|
TCPSFrameQueue::~TCPSFrameQueue() {
|
|
if (slots != NULL_PTR(Slot **)) {
|
|
for (uint32 i = 0u; i < cap; i++) {
|
|
delete slots[i];
|
|
}
|
|
delete[] slots;
|
|
}
|
|
}
|
|
|
|
void TCPSFrameQueue::Init(uint32 d) {
|
|
depth = d;
|
|
cap = d + 2u;
|
|
slots = new Slot *[cap];
|
|
for (uint32 i = 0u; i < cap; i++) {
|
|
slots[i] = new Slot();
|
|
}
|
|
count = 0u;
|
|
sent = 0u;
|
|
lastProgress = HighResolutionTimer::Counter();
|
|
}
|
|
|
|
void TCPSFrameQueue::Reserve(uint32 numStreams) {
|
|
const uint32 want = numStreams * (depth + 2u);
|
|
if (want > cap) {
|
|
Slot **grown = new Slot *[want];
|
|
for (uint32 i = 0u; i < cap; i++) {
|
|
grown[i] = slots[i];
|
|
}
|
|
for (uint32 i = cap; i < want; i++) {
|
|
grown[i] = new Slot();
|
|
}
|
|
delete[] slots;
|
|
slots = grown;
|
|
cap = want;
|
|
}
|
|
}
|
|
|
|
/** Remove slot @p i, keeping order; the slot object is recycled at the end. */
|
|
void TCPSFrameQueue::RemoveAt(uint32 i) {
|
|
Slot *v = slots[i];
|
|
for (uint32 j = i + 1u; j < count; j++) {
|
|
slots[j - 1u] = slots[j];
|
|
}
|
|
slots[count - 1u] = v;
|
|
count--;
|
|
}
|
|
|
|
bool TCPSFrameQueue::Push(uint16 streamIdx, bool isConfig, const uint8 *frame,
|
|
uint32 frameBytes, uint32 &dropped) {
|
|
const uint32 firstFree = (sent > 0u) ? 1u : 0u; // slots below this are on the wire
|
|
|
|
if (isConfig) {
|
|
uint32 i = firstFree;
|
|
while (i < count) {
|
|
if (slots[i]->stream == streamIdx) {
|
|
if (!slots[i]->isConfig) {
|
|
dropped++;
|
|
}
|
|
RemoveAt(i);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
} else {
|
|
uint32 dataCount = 0u;
|
|
for (uint32 i = 0u; i < count; i++) {
|
|
if ((slots[i]->stream == streamIdx) && !slots[i]->isConfig) {
|
|
dataCount++;
|
|
}
|
|
}
|
|
if (dataCount >= depth) {
|
|
uint32 victim = count;
|
|
for (uint32 i = firstFree; (i < count) && (victim == count); i++) {
|
|
if ((slots[i]->stream == streamIdx) && !slots[i]->isConfig) {
|
|
victim = i;
|
|
}
|
|
}
|
|
dropped++;
|
|
if (victim == count) {
|
|
return false; // only the frame on the wire is queued
|
|
}
|
|
RemoveAt(victim);
|
|
}
|
|
}
|
|
|
|
if (count >= cap) {
|
|
dropped++;
|
|
return false;
|
|
}
|
|
|
|
Slot &s = *slots[count];
|
|
if (s.cap < frameBytes) {
|
|
if (s.buf != NULL_PTR(uint8 *)) {
|
|
delete[] s.buf;
|
|
}
|
|
s.buf = new uint8[frameBytes];
|
|
s.cap = frameBytes;
|
|
}
|
|
memcpy(s.buf, frame, frameBytes);
|
|
if (frameBytes >= (TCPS_FRAGIDX_OFFSET + 2u)) {
|
|
memcpy(&s.buf[TCPS_FRAGIDX_OFFSET], &streamIdx, 2u);
|
|
}
|
|
s.len = frameBytes;
|
|
s.isConfig = isConfig;
|
|
s.stream = streamIdx;
|
|
if (count == 0u) {
|
|
sent = 0u;
|
|
lastProgress = HighResolutionTimer::Counter();
|
|
}
|
|
count++;
|
|
return true;
|
|
}
|
|
|
|
/** Account for @p bytes written from the head of the queue. */
|
|
void TCPSFrameQueue::Consume(uint32 bytes) {
|
|
while ((bytes > 0u) && (count > 0u)) {
|
|
const uint32 left = slots[0]->len - sent;
|
|
if (bytes >= left) {
|
|
bytes -= left;
|
|
RemoveAt(0u);
|
|
sent = 0u;
|
|
} else {
|
|
sent += bytes;
|
|
bytes = 0u;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TCPSFrameQueue::Flush(int fd) {
|
|
bool alive = true;
|
|
while (alive && (count > 0u)) {
|
|
struct iovec iov[TCPS_MAX_BATCH];
|
|
uint32 n = (count < TCPS_MAX_BATCH) ? count : TCPS_MAX_BATCH;
|
|
for (uint32 i = 0u; i < n; i++) {
|
|
const uint32 off = (i == 0u) ? sent : 0u;
|
|
iov[i].iov_base = &slots[i]->buf[off];
|
|
iov[i].iov_len = slots[i]->len - off;
|
|
}
|
|
struct msghdr msg;
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = n;
|
|
ssize_t w = sendmsg(fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT);
|
|
if (w > 0) {
|
|
lastProgress = HighResolutionTimer::Counter();
|
|
Consume(static_cast<uint32>(w));
|
|
} else if ((w < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {
|
|
break;
|
|
} else if ((w < 0) && (errno == EINTR)) {
|
|
continue;
|
|
} else {
|
|
alive = false;
|
|
}
|
|
}
|
|
return alive;
|
|
}
|
|
|
|
bool TCPSFrameQueue::Empty() const {
|
|
return count == 0u;
|
|
}
|
|
|
|
uint64 TCPSFrameQueue::LastProgress() const {
|
|
return lastProgress;
|
|
}
|
|
|
|
} // namespace MARTe
|