Non-Blocking I/O
The recv function can operate in a non-blocking manner by passing the MSG_DONTWAIT flag as its final argument, though this approach is often cumbersome for consistent behavior. A more persistent method involves specifying the O_NONBLOCK flag during the open system call.
Alternatively, the fcntl function modifies the status flags of an existing file descriptor, instructing the kernel to treat it as non-blocking.
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
// Commands include:
// F_DUPFD: Duplicate file descriptor
// F_GETFD / F_SETFD: Get/set file descriptor flags
// F_GETFL / F_SETFL: Get/set file status flags (e.g., O_NONBLOCK)
// F_GETOWN / F_SETOWN: Get/set asynchronous I/O ownership
// F_GETLK / F_SETLK / F_SETLKW: Get/set record locks
To configure a descriptor for non-blocking operation, retrieve its current flags and apply a bitwise OR with O_NONBLOCK.
void enable_nonblocking(int descriptor) {
int current_flags = fcntl(descriptor, F_GETFL, 0);
if (current_flags == -1) {
perror("Failed to get flags");
return;
}
if (fcntl(descriptor, F_SETFL, current_flags | O_NONBLOCK) == -1) {
perror("Failed to set non-blocking flag");
}
}
On Linux terminals, pressing Ctrl+D signals the end of standard input (EOF).
When a non-blocking descriptor is read but no data is available, system calls like read or recv return an error status. This does not signify a true failure; rather, it indicates that the resource is temporarily unavailable. The errno variable is set to EAGAIN (or EWOULDBLOCK, typically value 11) to distinguish this condition from actual errors.
#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
char buffer[1024];
while (true) {
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
std::cout << "Echo: " << buffer << std::endl;
} else if (bytes_read == 0) {
std::cout << "Input closed." << std::endl;
break;
} else {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
std::cerr << "Data not ready, waiting... (errno: " << errno << ")" << std::endl;
sleep(1);
} else {
perror("Read failure");
break;
}
}
}
I/O Multiplexing
Polling non-blocking descriptors can consume unnecessary CPU cycles. I/O multiplexing provides an efficient alternative to monitor multiple file descriptors simultaneously. Common implementations include select, poll, and epoll.