udpsclient: a TCP transport mode (SUBSCRIBE, no CONNECT) and a TCP mock server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-06 13:44:26 +01:00
1 parent 21b1945769
commit 7b8c932ab7
3 files changed
+194 -4

No files matched your search

+59 -3
View File
@@ -174,6 +174,7 @@ struct udps_client {
char server_addr[128];
char mcast_group[128];
char iface_addr[128];
char stream_name[256];
struct sockaddr_in server_sa;
int udp_fd; /**< Unicast receive socket, or joined multicast socket. */
@@ -1149,9 +1150,50 @@ static int connect_multicast(udps_client_t *c) {
return 0;
}
/** TCP transport: connect, then SUBSCRIBE when a stream name is set. */
static int connect_tcp(udps_client_t *c) {
struct timeval tv;
size_t name_len = strlen(c->stream_name);
c->tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
if (c->tcp_fd < 0) {
return fail(c, "TCP socket() failed: %s", strerror(errno));
}
set_recv_buffer(c->tcp_fd, c->cfg.recv_buffer_bytes);
/* Linux bounds a blocking connect() with SO_SNDTIMEO, so an unreachable host
* cannot hold the polling thread for minutes. */
tv.tv_sec = 3;
tv.tv_usec = 0;
(void)setsockopt(c->tcp_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, (socklen_t)sizeof tv);
if (connect(c->tcp_fd, (struct sockaddr *)&c->server_sa,
(socklen_t)sizeof c->server_sa) != 0) {
int e = errno;
close_sockets(c);
return fail(c, "TCP connect to %s:%u failed: %s", c->server_addr,
(unsigned)c->cfg.server_port, strerror(e));
}
/* A half-received frame must not wedge the poll loop (read_exact_tcp blocks). */
tv.tv_sec = 1;
(void)setsockopt(c->tcp_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, (socklen_t)sizeof tv);
if (name_len > 0u) {
uint8_t pkt[UDPS_HEADER_SIZE + 255u];
build_header(pkt, UDPS_PKT_SUBSCRIBE, 0u, 0u, 1u, (uint32_t)name_len);
memcpy(pkt + UDPS_HEADER_SIZE, c->stream_name, name_len);
if (send(c->tcp_fd, pkt, UDPS_HEADER_SIZE + name_len, MSG_NOSIGNAL) !=
(ssize_t)(UDPS_HEADER_SIZE + name_len)) {
int e = errno;
close_sockets(c);
return fail(c, "SUBSCRIBE failed: %s", strerror(e));
}
}
return 0;
}
static int do_connect(udps_client_t *c) {
int rc = (c->mcast_group[0] != '\0') ? connect_multicast(c)
: connect_unicast(c);
int rc = c->cfg.use_tcp ? connect_tcp(c)
: (c->mcast_group[0] != '\0') ? connect_multicast(c)
: connect_unicast(c);
if (rc != 0) {
c->disconnect_t = now_mono();
return rc;
@@ -1174,7 +1216,10 @@ static void do_disconnect(udps_client_t *c, const char *why) {
if (!c->connected) {
return;
}
(void)send_control(c, UDPS_PKT_DISCONNECT);
if (!c->cfg.use_tcp) {
/* TCP: a write to a peer that closed would raise SIGPIPE; the server sees the close itself. */
(void)send_control(c, UDPS_PKT_DISCONNECT);
}
close_sockets(c);
c->connected = 0;
c->disconnect_t = now_mono();
@@ -1267,17 +1312,28 @@ udps_client_t *udps_client_create(const udps_client_config_t *cfg) {
if (c == NULL) {
return NULL;
}
if (cfg->use_tcp && cfg->stream_name != NULL && strlen(cfg->stream_name) > 255u) {
free(c);
return NULL;
}
c->cfg = *cfg;
c->udp_fd = -1;
c->tcp_fd = -1;
copy_str(c->server_addr, sizeof c->server_addr, cfg->server_addr);
copy_str(c->mcast_group, sizeof c->mcast_group, cfg->multicast_group);
copy_str(c->iface_addr, sizeof c->iface_addr, cfg->interface_addr);
copy_str(c->stream_name, sizeof c->stream_name, cfg->stream_name);
/* The config's string pointers must not be used again: they belong to the
* caller and the copies above are what the client works from. */
c->cfg.server_addr = c->server_addr;
c->cfg.multicast_group = (c->mcast_group[0] != '\0') ? c->mcast_group : NULL;
c->cfg.interface_addr = (c->iface_addr[0] != '\0') ? c->iface_addr : NULL;
c->cfg.stream_name = c->stream_name;
if (c->cfg.use_tcp) {
c->cfg.multicast_group = NULL;
c->mcast_group[0] = '\0';
c->cfg.reorder_window_s = 0.0;
}
if (c->cfg.max_packet_bytes == 0u) {
c->cfg.max_packet_bytes = 1u * 1024u * 1024u;