csocketsnonblockingposix-select

Is non-blocking socket really non-blocking when used with blocking select()?


This is rather theoretical question. If sockets I/O (either read or write) is set to O_NONBLOCK, but then this socket is set in fd_set to select() which blocks (waiting for an event the file descriptor become either readable or writable), then that socket is blocking anyway (due to the select())?

Why would I set the socket to be non-blocking, when even the blocking (default) version once become readable (or writable) (thanks to select()), won't block, because the select() said it has data to read (or write) and thus the socket is able to perform its operation with that data without blocking. So why to bother setting socket non-blocking when select() blocks anyway?

An example of having non-block sockets, but in conjuction with blocking select():

#include <unp.h>

void str_cli(FILE *fp, int sockfd)
{
    int flags, maxfd, stdineof;

    fd_set rset, wset;

    ssize_t nbytes, nactual;
    //struct bufpos { char *read_ptr, *write_ptr; };
    struct bufpos ipos, opos;
    char inbuf[BSIZE], outbuf[BSIZE];

    //--//

    //set nonblocking flag for these fds:
    int nblkFds[3] = {sockfd, STDIN_FILENO, STDOUT_FILENO};
    for (int i = 0; i < 3; i++)
    {
        flags = Fcntl(nblkFds[i], F_GETFL, 0);
        Fcntl(nblkFds[i], F_SETFL, flags | O_NONBLOCK);
    }

    //initialize buffer positions
    ipos.write_ptr = ipos.read_ptr = inbuf;
    opos.write_ptr = opos.read_ptr = outbuf;

    stdineof = 0; //stdin
    maxfd = max(STDOUT_FILENO, sockfd) + 1;

    while (1)
    {
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        //can read from stdin and readptr is not at the end of buffer
        if (stdineof == 0 && opos.read_ptr < &outbuf[BSIZE])
        {
            FD_SET(STDIN_FILENO, &rset);
        }
        //can read from socket and the readptr is not at then end of buffer
        if (ipos.read_ptr < &inbuf[BSIZE])
        {
            FD_SET(sockfd, &rset);
        }
        //difference in outbuf == data to write to socket
        if (opos.read_ptr != opos.write_ptr)
        {
            FD_SET(sockfd, &wset);
        }
        //difference in inbuf == data to write to file
        if (ipos.read_ptr != ipos.write_ptr)
        {
            FD_SET(STDOUT_FILENO, &wset);
        }

        Select(maxfd, &rset, &wset, NULL, NULL);

        if (FD_ISSET(STDIN_FILENO, &rset))
        {
            switch ((nbytes = read(STDIN_FILENO, opos.read_ptr, &outbuf[BSIZE] - opos.read_ptr)))
            {
            case -1:
                perror("read");
                if (errno != EWOULDBLOCK)
                {
                    die("read");
                }

            case 0:
                fprintf(stderr, "%s: EOF on stdin\n", nowtime());
                stdineof = 1;
                if (opos.write_ptr == opos.read_ptr)
                {
                    //everything was written to socket -> we won't be writing enything else -> close the connection by sending FIN
                    Shutdown(sockfd, SHUT_WR);
                }
                break;

            default:
                fprintf(stderr, "%s: read %ld bytes from stdin\n", nowtime(), nbytes);
                //move the read pointer with bytes writen
                opos.read_ptr += nbytes;
                //now those bytes could be writen to socket
                FD_SET(sockfd, &wset);
            }
        }

        if (FD_ISSET(sockfd, &rset))
        {
            switch ((nbytes = read(sockfd, ipos.read_ptr, &inbuf[BSIZE] - ipos.read_ptr)))
            {
            case -1:
                perror("read");
                if (errno != EWOULDBLOCK)
                {
                    die("read");
                }

            case 0:
                fprintf(stderr, "%s: EOF on socket\n", nowtime());
                if (stdineof)
                {
                    //normal termination (client EOF)
                    return;
                }
                else
                {
                    //RST from peer
                    die("str_cli: server terminated prematurely");
                }
                break;

            default:
                fprintf(stderr, "%s: read %ld bytes from socket\n", nowtime(), nbytes);
                //move the read pointer with bytes read
                ipos.read_ptr += nbytes;
                //those bytes could be writen to file
                FD_SET(STDOUT_FILENO, &wset);
            }
        }

        if (FD_ISSET(STDOUT_FILENO, &wset) && (nbytes = ipos.read_ptr - ipos.write_ptr) > 0)
        {
            //the stdout is writeable and there are some bytes to write
            switch ((nactual = write(STDOUT_FILENO, ipos.write_ptr, nbytes)))
            {
            case -1:
                perror("write");
                if (errno != EWOULDBLOCK)
                {
                    die("write");
                }
            default:
                fprintf(stderr, "%s: wrote %ld bytes to stdout\n", nowtime(), nactual);
                ipos.write_ptr += nactual;
                if (ipos.write_ptr == ipos.read_ptr)
                {
                    //back to beginning buffer if all was writen to stdout
                    ipos.write_ptr = ipos.read_ptr = inbuf;
                }
            }
        }

        if (FD_ISSET(sockfd, &wset) && ((nbytes = opos.read_ptr - opos.write_ptr) > 0))
        {
            //the socket is writeable and there are some bytes to write
            switch ((nactual = write(sockfd, opos.write_ptr, nbytes)))
            {
            case -1:
                perror("write");
                if (errno != EWOULDBLOCK)
                {
                    die("write");
                }

            default:
                fprintf(stderr, "%s wrote %ld bytes to socket\n", nowtime(), nactual);
                opos.write_ptr += nactual;
                if (opos.write_ptr == opos.read_ptr)
                {
                    //back to beginning buffer if all was send/writen to socket
                    opos.read_ptr = opos.write_ptr = outbuf;
                    if (stdineof)
                    {
                        //EOF, could send its FIN
                        Shutdown(sockfd, SHUT_WR);
                    }
                }
            }
        }
    }
}

Solution

  • This is rather theoretical question. If sockets I/O (either read or write) is set to O_NONBLOCK, but then this socket is set in fd_set to select() which blocks (waiting for an event the file descriptor become either readable or writable), then that socket is blocking anyway (due to the select())?

    The select is blocking. The socket is still non-blocking.

    Why would I set the socket to be non-blocking, when even the blocking (default) version once become readable (or writable) (thanks to select()), won't block, because the select() said it has data to read (or write) and thus the socket is able to perform its operation with that data without blocking.

    No, no, no! That is not a safe assumption. There is no guarantee that a subsequent read or write will not block. You must set the socket non-blocking if you need a future guarantee that a later operation will not block.

    So why to bother setting socket non-blocking when select() blocks anyway?

    Because you don't want operations on the socket to block. The select function does not guarantee that a future operation won't block and people have gotten burnt by making that assumption in the past.

    For example, you do select on a UDP socket and it says that a receive won't block. But before you call recv, the administrator enables UDP checksums which were previously disabled. Guess what, now your recv will block if the checksum was incorrect on the only received datagram.

    Unless you think you could have foreseen every way something like that could happen, and you definitely can't, you must set the socket non-blocking if you do not wish it to block.