Two ways to make “slow” systems calls nonblocking:
open() with O_NONBLOCKfcntl() to turn on O_NONBLOCK file status flag
    Nonblocking slow system call returns -1 with errno set to EAGAIN
if it would have blocked
APUE presents set_fl() and clr_fl() wrapper functions on fcnt() for
convenience. These wrappers let you set and clear a specified set of flags
for the file descriptor without affecting the rest of the file status flags:
#include "apue.h"
#include <fcntl.h>
void set_fl(int fd, int flags) /* flags are file status flags to turn on */
{
        int		val;
        if ((val = fcntl(fd, F_GETFL, 0)) < 0)
                err_sys("fcntl F_GETFL error");
        val |= flags;		/* turn on flags */
        if (fcntl(fd, F_SETFL, val) < 0)
                err_sys("fcntl F_SETFL error");
}
void clr_fl(int fd, int flags) /* flags are file status flags to turn off */
{
        int		val;
        if ((val = fcntl(fd, F_GETFL, 0)) < 0)
                err_sys("fcntl F_GETFL error");
        val &= ~flags;		/* turn flags off */
        if (fcntl(fd, F_SETFL, val) < 0)
                err_sys("fcntl F_SETFL error");
}
Consider the following example program from APUE Figure 14.1 which reads 500,000
bytes from stdin and writes them to stdout. The stdout is set to non-blocking to
demonstrate that, in some situations, write() may not be able to write the
entire chunk at once:
char buf[500000];
int main(void)
{
    int     ntowrite, nwrite;
    char    *ptr;
    ntowrite = read(STDIN_FILENO, buf, sizeof(buf));
    fprintf(stderr, "read %d bytes\n", ntowrite);
    set_fl(STDOUT_FILENO, O_NONBLOCK);  /* set nonblocking */
    ptr = buf;
    while (ntowrite > 0) {
        errno = 0;
        nwrite = write(STDOUT_FILENO, ptr, ntowrite);
        fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);
        if (nwrite > 0) {
            ptr += nwrite;
            ntowrite -= nwrite;
        }
    }
    clr_fl(STDOUT_FILENO, O_NONBLOCK);  /* clear nonblocking */
}
Below, we run the program with its stdin redirected to read from some large
file and its stdout redirected to write to a file on disk named out. The
write() system call succeeded in writing the entire chunk at once.
$ ./nonblockw <  /boot/config-6.2.0-1019-gcp > out
read 275150 bytes
nwrite = 275150, errno = 0
Now, instead of writing to a normal file, we write to a named pipe named
mypipe. To accomplish this, we’ll have to read from the named pipe from
another terminal window. (Recall that open() on a named pipe blocks until both
ends are opened.)
$ # Run this in terminal 1
$ mkfifo mypipe
$ cat mypipe
<output redacted for brevity>
For convenience, redirect the stderr of the program to a file named err. We
redact repeated error lines below for brevity.
$ # Run this in terminal 2
$ ./nonblockw <  /boot/config-6.2.0-1019-gcp > mypipe 2> err
$ cat err
read 275150 bytes
nwrite = 65536, errno = 0
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = 65536, errno = 0
nwrite = -1, errno = 11
...
nwrite = -1, errno = 11
nwrite = 65536, errno = 0
nwrite = -1, errno = 11
...
nwrite = -1, errno = 11
nwrite = 65536, errno = 0
nwrite = -1, errno = 11
...
nwrite = -1, errno = 11
nwrite = 13006, errno = 0
The program’s write loop ran 100s of times, most of which were EAGAIN
errors. Recall that pipes are fixed-size buffers and that writes block if the
pipe is full.
Consider the read-write loop in cat:
while ((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0)
    if (write(STDOUT_FILENO, buf, n) != n)
        err_sys("write error");
netcat (nc) would have to perform two such loops at the same time:
Can netcat perform blocking I/O on two file descriptors at the same time without forking?
Use select() for I/O multiplexing! The API is shown below:
#include <sys/select.h>
int select(int maxfdp1, // max fd plus 1, or simply pass FD_SETSIZE
           fd_set *restrict readfds,   // see if they're ready for reading
           fd_set *restrict writefds,  // see if they're ready for writing
           fd_set *restrict exceptfds, // see if exceptional condition occurred
                                       // ex) urgent out-of-band data in TCP
           struct timeval *restrict tvptr); // timeout
        // Returns: count of ready descriptors, 0 on timeout, –1 on error
int FD_ISSET(int fd, fd_set *fdset);
        // Returns: nonzero if fd is in set, 0 otherwise
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);
Things to note on select() API:
maxfdp1 parameter, which is the maximum fd across the three file
descriptor sets, plus one. Alternatively, pass FD_SETSIZE, which is the
fixed-size of the fd set imposed by the glibc library.FD_ZERO() to initialize the fd sets; use FD_SET() to indicate that
an fd should be monitoredselect() returns, the fd sets are mutated to include only
those fds that are ready for I/Opoll() does the same thing and has a better APIselect() (as well as poll()) will always get interrupted on signals,
even when the SA_RESTART flag was usedThe example program below implements a select() server with the following features:
int init_server_socket(unsigned short port)
{
    int servsock;
    if ((servsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        die("socket() failed");
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
    if (bind(servsock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
        die("bind() failed");
    if (listen(servsock, 5) < 0)
        die("listen() failed");
    return servsock;
}
static void sig_handler(int sig) {}
int main(int argc, char **argv)
{
    int i;
    int sock;
    char buf[100];
    socklen_t size;
    struct sockaddr_in client_name;
    fd_set active_fd_set, read_fd_set;
    if (argc != 2) {
        fprintf(stderr, "%s <port>\n", argv[0]);
        exit(1);
    }
    sock = init_server_socket(atoi(argv[1]));
    // catch SIGINT
    struct sigaction act, oact;
    act.sa_handler = sig_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_flags |= SA_RESTART;
    if (sigaction(SIGINT, &act, &oact) < 0)
        die("sigaction failed");
    FD_ZERO(&active_fd_set);
    FD_SET(sock, &active_fd_set);
    FD_SET(STDIN_FILENO, &active_fd_set);
    while (1) {
        read_fd_set = active_fd_set;
        if (select(FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0) {
            if (errno == EINTR) {
                fprintf(stderr, "%s\n", "select interrupted");
                continue;
            }
            die("select failed");
        }
        for (i = 0; i < FD_SETSIZE; ++i) {
            if (FD_ISSET(i, &read_fd_set)) {
                if (i == sock) {
                    size = sizeof(client_name);
                    int new = accept(sock,
                                     (struct sockaddr *) &client_name, &size);
                    if (new < 0) {
                        die("accept failed");
                    } else {
                        fprintf(stderr, "new connection from %s (fd:%d)\n",
                                inet_ntoa(client_name.sin_addr), new);
                        FD_SET(new, &active_fd_set);
                    }
                }
                else if (i == STDIN_FILENO) {
                    buf[0] = '\0';
                    read(i, buf, sizeof(buf));
                    if (strncmp(buf, "quit\n", 5) == 0) {
                        exit(0);
                    } else {
                        fprintf(stderr, "unknown command\n");
                    }
                }
                else{
                    int r = read(i, buf, sizeof(buf));
                    if (r < 0) {
                        die("read failed");
                    } else if (r == 0) {
                        fprintf(stderr, "connection closed (fd:%d)\n", i);
                        close(i);
                        FD_CLR(i, &active_fd_set);
                    } else {
                        write(STDOUT_FILENO, buf, r);
                    }
                }
            }
        }
    }
}
Last updated: 2024-02-20