ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
network.c File Reference

🌐 Cross-platform socket I/O with timeout management and connection handling More...

Go to the source code of this file.

Functions

ssize_t send_with_timeout (socket_t sockfd, const void *data, size_t len, uint64_t timeout_ns)
 Send data with timeout using chunked transmission.
 
ssize_t recv_with_timeout (socket_t sockfd, void *buf, size_t len, uint64_t timeout_ns)
 Receive data with timeout.
 
int accept_with_timeout (socket_t listenfd, struct sockaddr *addr, socklen_t *addrlen, uint64_t timeout_ns)
 Accept connection with timeout.
 
asciichat_error_t set_socket_timeout (socket_t sockfd, uint64_t timeout_ns)
 Set socket timeout.
 
asciichat_error_t set_socket_keepalive (socket_t sockfd)
 Set socket keepalive.
 
asciichat_error_t set_socket_nonblocking (socket_t sockfd)
 Set socket non-blocking.
 
asciichat_error_t socket_configure_buffers (socket_t sockfd)
 Configure socket buffers and TCP_NODELAY for optimal performance.
 
const char * network_error_string ()
 Get human-readable error string for network errors.
 
bool connect_with_timeout (socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
 Connect with timeout.
 

Detailed Description

🌐 Cross-platform socket I/O with timeout management and connection handling

Definition in file network/network.c.

Function Documentation

◆ accept_with_timeout()

int accept_with_timeout ( socket_t  listenfd,
struct sockaddr *  addr,
socklen_t *  addrlen,
uint64_t  timeout_ns 
)

Accept connection with timeout.

Parameters
listenfdListening socket
addrClient address structure
addrlenLength of address structure
timeout_secondsTimeout in seconds
Returns
Client socket, or -1 on error

Definition at line 250 of file network/network.c.

250 {
251 // Set up poll for accept timeout (in nanoseconds)
252 struct pollfd pfd;
253 pfd.fd = listenfd;
254 pfd.events = POLLIN;
255 pfd.revents = 0;
256
257 int result = socket_poll(&pfd, 1, (int64_t)timeout_ns);
258
259 if (result <= 0) {
260 if (result == 0) {
261 // Timeout is expected behavior for server waiting for connections
262 asciichat_errno = ERROR_NETWORK_TIMEOUT;
263 asciichat_errno_context.code = ERROR_NETWORK_TIMEOUT;
264 asciichat_errno_context.has_system_error = true;
265 asciichat_errno_context.system_errno = ETIMEDOUT;
266 return -1;
267 }
268
269 if (network_handle_select_error(result)) {
270 return -1; // Don't retry for accept
271 }
272 return -1;
273 }
274
275 // Check if socket is ready
276 if (!(pfd.revents & POLLIN)) {
277 asciichat_errno = ERROR_NETWORK_TIMEOUT;
278 asciichat_errno_context.code = ERROR_NETWORK_TIMEOUT;
279 asciichat_errno_context.has_system_error = true;
280 asciichat_errno_context.system_errno = ETIMEDOUT;
281 return -1;
282 }
283
284 // Check if socket is still valid before attempting accept
285 if (listenfd == INVALID_SOCKET_VALUE) {
286 SET_ERRNO(ERROR_NETWORK, "accept_with_timeout: listening socket is closed");
287 return -1;
288 }
289
290 socket_t accept_result = socket_accept(listenfd, addr, addrlen);
291
292 if (accept_result == INVALID_SOCKET_VALUE) {
293 // Check if this is a socket closed error (common during shutdown)
294 int error_code = socket_get_last_error();
295
296 if (socket_is_invalid_socket_error(error_code)) {
297 // During shutdown, don't log this as an error since it's expected behavior
298 asciichat_errno = ERROR_NETWORK;
299 asciichat_errno_context.code = ERROR_NETWORK;
300 asciichat_errno_context.has_system_error = false;
301 } else {
302 SET_ERRNO_SYS(ERROR_NETWORK_BIND, "accept_with_timeout accept failed: %s", socket_get_error_string());
303 }
304 return -1;
305 }
306
307 return (int)accept_result;
308}
asciichat_error_t error_code
__thread asciichat_error_t asciichat_errno
__thread asciichat_error_context_t asciichat_errno_context
int socket_t

References asciichat_errno, asciichat_errno_context, and error_code.

◆ connect_with_timeout()

bool connect_with_timeout ( socket_t  sockfd,
const struct sockaddr *  addr,
socklen_t  addrlen,
int  timeout_seconds 
)

Connect with timeout.

Parameters
sockfdSocket file descriptor
addrAddress to connect to
addrlenAddress length
timeout_secondsTimeout in seconds
Returns
true on success, false on failure

Definition at line 441 of file network/network.c.

441 {
442 if (sockfd == INVALID_SOCKET_VALUE) {
443 errno = EBADF;
444 return false;
445 }
446
447 // Set socket to non-blocking for timeout control
448 if (set_socket_nonblocking(sockfd) != ASCIICHAT_OK) {
449 return false;
450 }
451
452 // Attempt connection
453 int result = connect(sockfd, addr, addrlen);
454
455 if (result == 0) {
456 // Connected immediately
457 return true;
458 }
459
460 // Check if connection is in progress (expected for non-blocking sockets)
461 int error = socket_get_last_error();
462 if (!socket_is_in_progress_error(error) && !socket_is_would_block_error(error)) {
463 return false;
464 }
465
466 // Use select to wait for connection with timeout
467 fd_set write_fds;
468 struct timeval timeout;
469
470 socket_fd_zero(&write_fds);
471 socket_fd_set(sockfd, &write_fds);
472
473 timeout.tv_sec = timeout_seconds;
474 timeout.tv_usec = 0;
475
476 result = socket_select(sockfd, NULL, &write_fds, NULL, &timeout);
477
478 if (result <= 0) {
479 return false; // Timeout or error
480 }
481
482 if (!socket_fd_isset(sockfd, &write_fds)) {
483 return false; // Socket not ready
484 }
485
486 // Check if connection was successful
487 int error_code = 0;
488 socklen_t error_len = sizeof(error_code);
489
490 if (socket_getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error_code, &error_len) != 0) {
491 return false;
492 }
493
494 if (error_code != 0) {
495 return false;
496 }
497
498 // Connection successful - restore blocking mode
499 if (socket_set_blocking(sockfd) != 0) {
500 log_warn("Failed to restore socket to blocking mode after connect");
501 // Continue anyway - non-blocking mode works but may cause issues with send/recv
502 }
503
504 return true;
505}
asciichat_error_t set_socket_nonblocking(socket_t sockfd)
Set socket non-blocking.

References error_code, and set_socket_nonblocking().

Referenced by server_connection_establish(), and tcp_client_connect().

◆ network_error_string()

const char * network_error_string ( )

Get human-readable error string for network errors.

Parameters
error_codeError code
Returns
Error string

Definition at line 429 of file network/network.c.

429 {
430 return socket_get_error_string();
431}

Referenced by add_client(), server_connection_establish(), socket_configure_buffers(), and tcp_client_connect().

◆ recv_with_timeout()

ssize_t recv_with_timeout ( socket_t  sockfd,
void *  buf,
size_t  len,
uint64_t  timeout_ns 
)

Receive data with timeout.

Parameters
sockfdSocket file descriptor
bufBuffer to receive data
lenLength of buffer
timeout_secondsTimeout in seconds
Returns
Number of bytes received, or -1 on error

Definition at line 182 of file network/network.c.

182 {
183 if (sockfd == INVALID_SOCKET_VALUE) {
184 errno = EBADF;
185 return -1;
186 }
187
188 ssize_t total_received = 0;
189 char *data = (char *)buf;
190
191 while (total_received < (ssize_t)len) {
192 // Set up poll for read timeout (in nanoseconds)
193 struct pollfd pfd;
194 pfd.fd = sockfd;
195 pfd.events = POLLIN;
196 pfd.revents = 0;
197
198 int64_t effective_timeout_ns = network_is_test_environment() ? (1LL * NS_PER_SEC_INT) : (int64_t)timeout_ns;
199 int result = socket_poll(&pfd, 1, effective_timeout_ns);
200 if (result <= 0) {
201 if (result == 0) {
202 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "recv_with_timeout timed out after %llu nanoseconds",
203 (unsigned long long)timeout_ns);
204 return -1;
205 }
206 if (network_handle_select_error(result)) {
207 continue; // Retry
208 }
209 return -1; // Fatal error
210 }
211
212 // Check if socket is ready
213 if (!(pfd.revents & POLLIN)) {
214 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "recv_with_timeout socket not ready after poll");
215 return -1;
216 }
217
218 // Calculate how much we still need to receive
219 size_t bytes_to_recv = len - (size_t)total_received;
220 ssize_t received = socket_recv(sockfd, data + total_received, bytes_to_recv, 0);
221
222 if (received < 0) {
223 int error = errno;
224 if (network_handle_recv_error(error)) {
225 continue; // Retry
226 }
227 return -1; // Fatal error
228 }
229
230 if (received == 0) {
231 // Connection closed by peer
232 log_debug("Connection closed by peer during recv");
233 return total_received; // Return what we got so far
234 }
235
236 total_received += received;
237 }
238
239 return total_received;
240}

Referenced by packet_receive(), and receive_packet_secure().

◆ send_with_timeout()

ssize_t send_with_timeout ( socket_t  sockfd,
const void *  data,
size_t  len,
uint64_t  timeout_ns 
)

Send data with timeout using chunked transmission.

Parameters
sockfdSocket file descriptor
dataData to send
lenLength of data
timeout_secondsTimeout in seconds
Returns
Number of bytes sent, or -1 on error

Definition at line 112 of file network/network.c.

112 {
113 if (sockfd == INVALID_SOCKET_VALUE) {
114 errno = EBADF;
115 return -1;
116 }
117
118 size_t total_sent = 0;
119 const char *data_ptr = (const char *)data;
120
121 while (total_sent < len) {
122 // Calculate chunk size
123 size_t bytes_to_send = len - total_sent;
124 const size_t MAX_CHUNK_SIZE = 65536; // 64KB chunks for reliable TCP transmission
125 if (bytes_to_send > MAX_CHUNK_SIZE) {
126 bytes_to_send = MAX_CHUNK_SIZE;
127 }
128
129 // Set up poll for write timeout (in nanoseconds)
130 struct pollfd pfd;
131 pfd.fd = sockfd;
132 pfd.events = POLLOUT;
133 pfd.revents = 0;
134
135 int64_t effective_timeout_ns = network_is_test_environment() ? (100LL * NS_PER_MS_INT) : (int64_t)timeout_ns;
136 int result = socket_poll(&pfd, 1, effective_timeout_ns);
137 if (result <= 0) {
138 if (result == 0) {
139 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout timed out after %llu nanoseconds",
140 (unsigned long long)timeout_ns);
141 return -1;
142 }
143 if (network_handle_select_error(result)) {
144 continue; // Retry
145 }
146 return -1; // Fatal error
147 }
148
149 // Check if socket is ready for writing
150 if (!(pfd.revents & POLLOUT)) {
151 SET_ERRNO_SYS(ERROR_NETWORK_TIMEOUT, "send_with_timeout socket not ready for writing after poll");
152 return -1;
153 }
154
155 // Use platform-specific send
156 ssize_t sent = socket_send(sockfd, data_ptr + total_sent, bytes_to_send, 0);
157
158 if (sent < 0) {
159 int error = errno;
160 if (network_handle_send_error(error)) {
161 continue; // Retry
162 }
163 return -1; // Fatal error
164 }
165
166 if (sent > 0) {
167 total_sent += (size_t)sent;
168 }
169 }
170
171 return (ssize_t)total_sent;
172}

Referenced by packet_send().

◆ set_socket_keepalive()

asciichat_error_t set_socket_keepalive ( socket_t  sockfd)

Set socket keepalive.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 355 of file network/network.c.

355 {
356 if (sockfd == INVALID_SOCKET_VALUE) {
357 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
358 }
359 int result = socket_set_keepalive_params(sockfd, true, KEEPALIVE_IDLE, KEEPALIVE_INTERVAL, KEEPALIVE_COUNT);
360 if (result != 0) {
361 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket keepalive parameters");
362 }
363 return ASCIICHAT_OK;
364}

◆ set_socket_nonblocking()

asciichat_error_t set_socket_nonblocking ( socket_t  sockfd)

Set socket non-blocking.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 371 of file network/network.c.

371 {
372 if (sockfd == INVALID_SOCKET_VALUE) {
373 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
374 }
375 int result = socket_set_nonblocking(sockfd, true);
376 if (result != 0) {
377 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket non-blocking mode");
378 }
379 return ASCIICHAT_OK;
380}

Referenced by connect_with_timeout().

◆ set_socket_timeout()

asciichat_error_t set_socket_timeout ( socket_t  sockfd,
uint64_t  timeout_ns 
)

Set socket timeout.

Parameters
sockfdSocket file descriptor
timeout_nsTimeout in nanoseconds (converted to milliseconds for socket level)
Returns
ASCIICHAT_OK on success, error code on failure

Sets socket-level timeouts (SO_RCVTIMEO/SO_SNDTIMEO) as a safety net fallback. Actual precision depends on platform: milliseconds on most systems, microseconds on some. Works in conjunction with application-level timeouts via socket_poll.

Definition at line 320 of file network/network.c.

320 {
321 if (sockfd == INVALID_SOCKET_VALUE) {
322 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket file descriptor");
323 }
324
325 // Convert nanoseconds to milliseconds for socket-level timeout
326 // Note: socket-level timeouts have ~millisecond granularity on most platforms
327 uint64_t timeout_ms = timeout_ns / (1000 * 1000);
328 if (timeout_ms == 0 && timeout_ns > 0) {
329 timeout_ms = 1; // Ensure at least 1ms for non-zero timeouts
330 }
331
332 // Set both receive and send timeouts using struct timeval
333 struct timeval tv;
334 tv.tv_sec = (time_t)(timeout_ms / 1000);
335 tv.tv_usec = (long)((timeout_ms % 1000) * 1000);
336
337 // Set receive timeout
338 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
339 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket receive timeout");
340 }
341
342 // Set send timeout
343 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) {
344 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to set socket send timeout");
345 }
346
347 return ASCIICHAT_OK;
348}

Referenced by add_client().

◆ socket_configure_buffers()

asciichat_error_t socket_configure_buffers ( socket_t  sockfd)

Configure socket buffers and TCP_NODELAY for optimal performance.

Parameters
sockfdSocket file descriptor
Returns
ASCIICHAT_OK on success, ERROR_NETWORK_CONFIG on failure

Definition at line 387 of file network/network.c.

387 {
388 if (sockfd == INVALID_SOCKET_VALUE) {
389 return SET_ERRNO_SYS(ERROR_NETWORK, "Invalid socket file descriptor");
390 }
391
392 int failed_options = 0;
393
394 // Attempt to configure 1MB send buffer for optimal frame transmission
395 int send_buffer_size = 1024 * 1024;
396 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, sizeof(send_buffer_size)) < 0) {
397 log_warn("Failed to set send buffer size to 1MB: %s", network_error_string());
398 failed_options++;
399 }
400
401 // Attempt to configure 1MB receive buffer for optimal frame reception
402 int recv_buffer_size = 1024 * 1024;
403 if (socket_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_buffer_size, sizeof(recv_buffer_size)) < 0) {
404 log_warn("Failed to set receive buffer size to 1MB: %s", network_error_string());
405 failed_options++;
406 }
407
408 // Attempt to enable TCP_NODELAY to disable Nagle's algorithm for low-latency transmission
409 // This must be set even if buffer configuration fails, as it's essential for real-time video.
410 int tcp_nodelay = 1;
411 if (socket_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(tcp_nodelay)) < 0) {
412 log_warn("Failed to set TCP_NODELAY: %s", network_error_string());
413 failed_options++;
414 }
415
416 // Return error only if ALL options failed
417 if (failed_options >= 3) {
418 return SET_ERRNO_SYS(ERROR_NETWORK, "Failed to configure all socket options");
419 }
420
421 return ASCIICHAT_OK;
422}
const char * network_error_string()
Get human-readable error string for network errors.

References network_error_string().

Referenced by server_connection_establish(), and tcp_client_connect().