[英]Is non-blocking socket really non-blocking when used with blocking select()?
這是一個相當理論的問題。 如果 sockets I/O( read
或write
)設置為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() 表示它有數據讀取(或寫入),因此套接字能夠使用該數據執行其操作而不會阻塞。
不不不。 這不是一個安全的假設。 不能保證后續的read
或write
不會阻塞。 如果您需要將來保證以后的操作不會阻塞,則必須將套接字設置為非阻塞。
那么,當 select() 阻塞時,為什么還要設置套接字非阻塞呢?
因為您不希望套接字上的操作被阻塞。 select
function 不保證未來的操作不會阻塞,並且人們在過去做出這種假設已經被燒毀了。
例如,您在 UDP 套接字上執行select
並表示接收不會阻塞。 但在您調用recv
之前,管理員會啟用之前禁用的 UDP 校驗和。 你猜怎么着,現在如果校驗和在唯一收到的數據報上不正確,你的recv
將阻塞。
除非你認為你可以預見到這樣的事情可能會發生,而且你絕對不能,如果你不希望它阻塞,你必須將套接字設置為非阻塞。
select
忽略文件描述符上的非阻塞標志,因為它沒有任何意義去關注它。
select
正在(可能)同時檢查多個文件描述符,這些文件描述符可能具有不同的非阻塞標志。 應該注意哪些?select
有自己的顯式超時,用於確定調用是阻塞還是非阻塞,或者阻塞了有限的時間。 可以說,可以在文件描述符上設置非阻塞“狀態”標志是一個糟糕的設計——最好在每次調用時指定你想要阻塞還是非阻塞。 實際上,如果您使用recv
和send
而不是read
和write
,您可以這樣做。 在文件描述符上設置“非阻塞”的所有真正作用是它發出的調用沒有以其他方式指定阻塞或非阻塞,非阻塞。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.