STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
TCPClient.cpp
Go to the documentation of this file.
2
3#include <arpa/inet.h>
4#include <netinet/in.h>
5#include <netinet/tcp.h>
6#include <sys/socket.h>
7#include <unistd.h>
8
9#include <cerrno>
10#include <cstring>
11#include <iostream>
12
13namespace daq_comms {
14namespace transport {
15
19
23
24bool TCPClient::connect(const std::string& host, uint16_t port) {
25 disconnect();
26
27 socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
28 if (socket_fd_ < 0) {
29 last_error_ = "Failed to create socket: " + std::string(strerror(errno));
30 return false;
31 }
32
33 struct sockaddr_in server_addr = {};
34 server_addr.sin_family = AF_INET;
35 server_addr.sin_port = htons(port);
36
37 if (inet_aton(host.c_str(), &server_addr.sin_addr) == 0) {
38 last_error_ = "Invalid host address: " + host;
39 ::close(socket_fd_);
40 socket_fd_ = -1;
41 return false;
42 }
43
44 // TCP keepalive
45 int keepalive = 1;
46 setsockopt(socket_fd_, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
47
48 // TCP_NODELAY — send immediately, no Nagle buffering
49 int nodelay = 1;
50 setsockopt(socket_fd_, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
51
52 // Large send buffer (1 MB) — gives headroom before backpressure
53 int sndbuf = 1024 * 1024;
54 setsockopt(socket_fd_, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
55
56 // Write timeout 5s — avoid dropping batches on brief backpressure; drain in daq_bridge keeps
57 // recv side clear
58 struct timeval tv;
59 tv.tv_sec = 5;
60 tv.tv_usec = 0;
61 setsockopt(socket_fd_, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
62
63 if (::connect(socket_fd_, reinterpret_cast<struct sockaddr*>(&server_addr),
64 sizeof(server_addr)) < 0) {
65 last_error_ = "Failed to connect: " + std::string(strerror(errno));
66 ::close(socket_fd_);
67 socket_fd_ = -1;
68 return false;
69 }
70
71 connected_ = true;
72 buffer_pos_ = 0;
73 return true;
74}
75
77 if (connected_) {
78 flush();
79 ::close(socket_fd_);
80 socket_fd_ = -1;
81 connected_ = false;
82 }
83}
84
86 return connected_;
87}
88
89bool TCPClient::write_all(const void* data, size_t len) {
90 if (!connected_) {
91 last_error_ = "Not connected";
92 return false;
93 }
94
95 std::lock_guard<std::mutex> lock(write_mutex_);
96 return _write_all(data, len);
97}
98
99bool TCPClient::write_buffered(const void* data, size_t len) {
100 if (!connected_) {
101 last_error_ = "Not connected";
102 return false;
103 }
104
105 std::lock_guard<std::mutex> lock(write_mutex_);
106
107 const uint8_t* bytes = static_cast<const uint8_t*>(data);
108 size_t remaining = len;
109
110 while (remaining > 0) {
111 size_t available = BUFFER_SIZE - buffer_pos_;
112 size_t to_copy = (remaining < available) ? remaining : available;
113
114 std::memcpy(write_buffer_.data() + buffer_pos_, bytes, to_copy);
115 buffer_pos_ += to_copy;
116 bytes += to_copy;
117 remaining -= to_copy;
118
119 // Auto-flush when buffer is 80% full
120 if (buffer_pos_ >= (BUFFER_SIZE * 8 / 10)) {
122 }
123 }
124
125 return true;
126}
127
129 if (!connected_) {
130 return;
131 }
132
133 std::lock_guard<std::mutex> lock(write_mutex_);
135}
136
137std::string TCPClient::last_error() const {
138 return last_error_;
139}
140
141bool TCPClient::_write_all(const void* data, size_t len) {
142 const uint8_t* bytes = static_cast<const uint8_t*>(data);
143 size_t total_sent = 0;
144
145 while (total_sent < len) {
146 ssize_t sent = 0;
147 do {
148 sent = ::write(socket_fd_, bytes + total_sent, len - total_sent);
149 } while (sent < 0 && errno == EINTR);
150
151 if (sent < 0) {
152 if (errno == EAGAIN || errno == EWOULDBLOCK) {
153 // SO_SNDTIMEO fired — Elodin is backpressuring.
154 // Drop this write rather than blocking the UDP pipeline.
155 last_error_ = "Write timeout (backpressure)";
156 return false;
157 }
158 last_error_ = "Write failed: " + std::string(strerror(errno));
159 connected_ = false; // Mark dead so main loop can reconnect
160 return false;
161 }
162 if (sent == 0) {
163 last_error_ = "Connection closed by peer";
164 connected_ = false;
165 return false;
166 }
167 total_sent += sent;
168 }
169
170 return true;
171}
172
174 if (buffer_pos_ > 0) {
176 buffer_pos_ = 0;
177 }
178}
179
180ssize_t TCPClient::read(void* buffer, size_t max_len) {
181 if (!connected_) {
182 last_error_ = "Not connected";
183 return -1;
184 }
185
186 // Set socket to non-blocking for this read
187 int flags = fcntl(socket_fd_, F_GETFL, 0);
188 fcntl(socket_fd_, F_SETFL, flags | O_NONBLOCK);
189
190 ssize_t received = ::read(socket_fd_, buffer, max_len);
191
192 // Restore blocking mode
193 fcntl(socket_fd_, F_SETFL, flags);
194
195 if (received < 0) {
196 if (errno == EAGAIN || errno == EWOULDBLOCK) {
197 return 0; // No data available (non-blocking)
198 }
199 last_error_ = "Read failed: " + std::string(strerror(errno));
200 return -1;
201 }
202
203 return received;
204}
205
206bool TCPClient::read_exact(void* buffer, size_t len) {
207 if (!connected_) {
208 last_error_ = "Not connected";
209 return false;
210 }
211
212 // Read exactly len bytes (blocking)
213 uint8_t* ptr = static_cast<uint8_t*>(buffer);
214 size_t remaining = len;
215
216 while (remaining > 0) {
217 ssize_t r = ::read(socket_fd_, ptr, remaining);
218 if (r < 0) {
219 if (errno == EINTR) {
220 continue; // Retry on interrupt
221 }
222 if (errno == EAGAIN || errno == EWOULDBLOCK) {
223 // SO_RCVTIMEO fired. Only a caller that has consumed nothing can be told "nothing
224 // arrived" — once part of a packet has been read, returning here would leave the
225 // rest of it in the socket and every later read would parse payload as a header.
226 // Mid-packet, keep waiting: the peer owes us the remainder.
227 if (remaining == len) {
228 last_error_ = "TIMEOUT";
229 return false;
230 }
231 continue;
232 }
233 last_error_ = "Read failed: " + std::string(strerror(errno));
234 connected_ = false; // Mark dead so the caller can reconnect
235 return false;
236 } else if (r == 0) {
237 last_error_ = "Socket closed unexpectedly";
238 // Only the write path used to do this, so a read-only consumer (heartbeat_service)
239 // saw is_connected() report true forever on a closed socket and its "reconnect on
240 // next iteration" never fired.
241 connected_ = false;
242 return false;
243 }
244 ptr += r;
245 remaining -= r;
246 }
247
248 return true;
249}
250
251void TCPClient::set_recv_timeout_ms(int timeout_ms) {
252 if (socket_fd_ < 0)
253 return;
254 struct timeval tv;
255 tv.tv_sec = timeout_ms / 1000;
256 tv.tv_usec = (timeout_ms % 1000) * 1000;
257 setsockopt(socket_fd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
258}
259
260} // namespace transport
261} // namespace daq_comms
void flush()
Flush buffered data.
static constexpr size_t BUFFER_SIZE
Definition TCPClient.hpp:99
std::vector< uint8_t > write_buffer_
ssize_t read(void *buffer, size_t max_len)
Read data from socket (non-blocking).
void disconnect()
Disconnect from server.
Definition TCPClient.cpp:76
void set_recv_timeout_ms(int timeout_ms)
Set socket receive timeout (0 = blocking).
bool _write_all(const void *data, size_t len)
bool write_all(const void *data, size_t len)
Write data directly (for VTable registration).
Definition TCPClient.cpp:89
bool read_exact(void *buffer, size_t len)
Read exactly len bytes (blocking).
bool connect(const std::string &host, uint16_t port)
Connect to TCP server.
Definition TCPClient.cpp:24
bool write_buffered(const void *data, size_t len)
Write data with buffering (for data publishing).
Definition TCPClient.cpp:99
std::string last_error() const
Get last error message.
bool is_connected() const
Check if connected.
Definition TCPClient.cpp:85
static const emmc_port_t port
Definition emmc_stm32.c:210
*brief write string representation of the JSON pointer to stream *sa const json_pointer & ptr
Definition json.hpp:13895
static osMutexId_t lock
Definition mode.c:11
static boot_report_t r