Understanding Synchronous and Asynchronous I/O with Five Network I/O Models
Synchronous vs Asynchronous I/O
In synchronous I/O operations, the requesting process waits until the operation completes. This approach requires the initiator to block while data moves from kernel space to user space. The process either polls continuously or remains idle waiting for I/O readiness.
Conversely, asynchronous I/O allows processes to continue execution immediately after initiating an operation. Completion notifications arrive later via callbacks or signals. Data transfer occurs through kernel threads, eliminating blocking during the copy phase.
Five Network I/O Models
Each I/O read involves two phases:
- Waiting for data to become available in kernel buffers
- Copying data from kernel space to application memory
Linux provides five distinct I/O handling mechanisms:
Blocking I/O
The default socket behavior where system calls like recvfrom block until data arrives and is copied to user space. Applications remain suspended throughout both I/O phases.
Non-blocking I/O
Socket operations return immediately even when no data is ready. Applications repeatedly check for availability using polling loops. While the initial wait phase avoids blocking, copying data still requires suspension.
Configuration methods:
// During socket creation
int sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
// Modifying existing socket
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
I/O Multiplexing
Single-threaded applications monitor multiple file descriptors simultaneously. When any descriptor becomes active, the monitoring function returns control. Three primary implementations exist.
Select Model
Monitors three descriptor sets for readability, writability, and exceptions within specified timeouts:
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
// Helper macros
FD_ZERO(fd_set *fdset); // Clear all bits
FD_SET(int fd, fd_set *fdset); // Set specific bit
FD_CLR(int fd, fd_set *fdset); // Clear specific bit
FD_ISSET(int fd, fd_set *fdset); // Test bit status
Server implementation example:
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr;
fd_set active_fds, working_fds;
server_socket = socket(AF_INET, SOCK_STREAM, 0);
// Setup and bind server address...
listen(server_socket, 5);
FD_ZERO(&active_fds);
FD_SET(server_socket, &active_fds);
while(1) {
working_fds = active_fds;
select(FD_SETSIZE, &working_fds, NULL, NULL, NULL);
for(int fd = 0; fd < FD_SETSIZE; fd++) {
if(FD_ISSET(fd, &working_fds)) {
if(fd == server_socket) {
// Handle new connection
client_socket = accept(server_socket, NULL, NULL);
FD_SET(client_socket, &active_fds);
} else {
// Process client data
char buffer;
if(read(fd, &buffer, 1) <= 0) {
close(fd);
FD_CLR(fd, &active_fds);
} else {
buffer++;
write(fd, &buffer, 1);
}
}
}
}
}
}
Poll Model
Similar functionality to select but without hard limits on monitored descriptors:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t count, int timeout_ms);
struct pollfd {
int fd; // File descriptor
short events; // Requested events
short revents; // Returned events
};
// Event types
POLLIN // Data available for reading
POLLOUT // Ready for writing
POLLERR // Error condition
Poll-based server:
#include <poll.h>
#define MAX_CONNECTIONS 1024
int main() {
struct pollfd connections[MAX_CONNECTIONS];
int server_fd = setup_server();
int active_count = 1;
connections[0].fd = server_fd;
connections[0].events = POLLIN;
while(1) {
poll(connections, active_count, 1000);
for(int i = 0; i < active_count; i++) {
if(connections[i].revents & POLLIN) {
if(connections[i].fd == server_fd) {
// Accept new client
int client = accept(server_fd, NULL, NULL);
connections[active_count].fd = client;
connections[active_count].events = POLLIN;
active_count++;
} else {
// Handle client request
char data;
if(read(connections[i].fd, &data, 1) <= 0) {
close(connections[i].fd);
// Remove from array
} else {
data++;
write(connections[i].fd, &data, 1);
}
}
}
}
}
}
Epoll Model
Advanced event notification interface providing better scalability than select/poll through edge-triggered and level-triggered modes.