簡體   English   中英

與阻塞 select() 一起使用時,非阻塞套接字真的是非阻塞的嗎?

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

這是一個相當理論的問題。 如果 sockets I/O( readwrite )設置為O_NONBLOCK ,但隨后此套接字在fd_set中設置為select()阻塞(等待文件描述符變為可讀或可寫的事件),則該套接字正在阻塞無論如何(由於select() )?

為什么我要將套接字設置為非阻塞,即使阻塞(默認)版本一旦變得可讀(或可寫)(感謝select() )也不會阻塞,因為select()說它有數據讀取(或寫入),因此套接字能夠使用該數據執行其操作而不會阻塞。 那么,當 select() 阻塞時,為什么還要設置套接字非阻塞呢?

具有non-block sockets 的示例,阻塞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);
                    }
                }
            }
        }
    }
}

這是一個相當理論的問題。 如果 sockets I/O(讀或寫)設置為 O_NONBLOCK,但隨后在 fd_set 中將此套接字設置為 select() 阻塞(等待文件描述符變為可讀或可寫的事件),則該套接字正在阻塞無論如何(由於select())?

select阻塞。 套接字仍然是非阻塞的。

為什么我要將套接字設置為非阻塞,即使阻塞(默認)版本一旦變得可讀(或可寫)(感謝 select()),也不會阻塞,因為 select() 表示它有數據讀取(或寫入),因此套接字能夠使用該數據執行其操作而不會阻塞。

不不不。 這不是一個安全的假設。 不能保證后續的readwrite不會阻塞。 如果您需要將來保證以后的操作不會阻塞,則必須將套接字設置為非阻塞。

那么,當 select() 阻塞時,為什么還要設置套接字非阻塞呢?

因為您不希望套接字上的操作被阻塞。 select function 不保證未來的操作不會阻塞,並且人們在過去做出這種假設已經被燒毀了。

例如,您在 UDP 套接字上執行select並表示接收不會阻塞。 但在您調用recv之前,管理員會啟用之前禁用的 UDP 校驗和。 你猜怎么着,現在如果校驗和在唯一收到的數據報上不正確,你的recv將阻塞。

除非你認為你可以預見到這樣的事情可能會發生,而且你絕對不能,如果你不希望它阻塞,你必須將套接字設置為非阻塞。

select忽略文件描述符上的非阻塞標志,因為它沒有任何意義去關注它。

  • select正在(可能)同時檢查多個文件描述符,這些文件描述符可能具有不同的非阻塞標志。 應該注意哪些?
  • select有自己的顯式超時,用於確定調用是阻塞還是非阻塞,或者阻塞了有限的時間。

可以說,可以在文件描述符上設置非阻塞“狀態”標志是一個糟糕的設計——最好在每次調用時指定你想要阻塞還是非阻塞。 實際上,如果您使用recvsend而不是readwrite ,您可以這樣做。 在文件描述符上設置“非阻塞”的所有真正作用是它發出的調用沒有以其他方式指定阻塞或非阻塞,非阻塞。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM