如何将 TCP 套接字更改为非阻塞?

Posted

技术标签:

【中文标题】如何将 TCP 套接字更改为非阻塞?【英文标题】:How do I change a TCP socket to be non-blocking? 【发布时间】:2010-12-05 08:01:24 【问题描述】:

如何使套接字不阻塞?

我知道fcntl() 函数,但我听说它并不总是可靠的。

【问题讨论】:

我只需要将 TCP Socket 转换为非阻塞套接字即可。 只有当你不做错误检查并假设它总是成功时,它才是不可靠的。 :) 【参考方案1】:

“并不总是可靠”是什么意思?如果系统成功将您的套接字设置为非阻塞,它将是非阻塞的。如果套接字操作需要阻塞(例如,如果输出缓冲区已满并且您过于频繁地调用发送/写入),则套接字操作将返回 EWOULDBLOCK

This forum thread 在处理非阻塞调用时有几个优点。

【讨论】:

我认为这不是一个好的答案。你告诉他它应该是可靠的,然后发布一个可能很快过时的论坛帖子。只是我的意见,但我认为有你代表的人会足够周到地提供比懒惰的链接和快速评论更彻底的答案。 @Matt 很抱歉让您失望了。请注意,这个答案已有四年半的历史。我认为我的回答表明我对这个问题缺乏细节感到沮丧。很难与“我听说以明显的方式进行操作并不总是可靠的”进行争论。我仍然这么认为,不知道如何改进。 是的,我后来才意识到这个问题有多老了。认为它是新的,因为其他人更新了它并且它出现在活动列表中......哎呀。没关系。 手册页中的注释:“POSIX.1-2001 允许在这种情况下返回错误 [EAGAIN 或 EWOULDBLOCK],并且不需要这些常量具有相同的值,因此可移植应用程序应该检查这两种可能性。”【参考方案2】:

fcntl()ioctl() 用于设置文件流的属性。当您使用此函数使套接字变为非阻塞时,accept()recv() 等本质上是阻塞的函数将返回错误,errno 将被设置为EWOULDBLOCK。您可以轮询文件描述符集以轮询套接字。

【讨论】:

有没有其他方法可以将 TCPIP 转换为 NON BLOCKING 套接字。我不想依赖这些方法可能是因为我过去的经历 将套接字转换为非阻塞的方式。如果您可以具体说明您的“过去经历”以及为什么您确信这行不通,也许我们可以为您提供帮助。 您可以将SOCK_NONBLOCK 传递给socket 调用。【参考方案3】:

在 C 中将套接字设置为非阻塞的最佳方法是使用 ioctl。下面是一个将接受的套接字设置为非阻塞的示例:

long on = 1L;
unsigned int len;
struct sockaddr_storage remoteAddress;
len = sizeof(remoteAddress);
int socket = accept(listenSocket, (struct sockaddr *)&remoteAddress, &len)
if (ioctl(socket, (int)FIONBIO, (char *)&on))

    printf("ioctl FIONBIO call failed\n");

【讨论】:

不就是异步套接字而不是非阻塞的吗? 在 C/C++ 中,它实际上使用标准套接字/winsock 是非阻塞的。【参考方案4】:

通常,您可以通过使用select(2)poll(2) 或其他一些可用的系统调用来使用普通的阻塞 IO多路复用 多个 IO 操作来达到相同的效果系统。

有关可扩展 IO 多路复用方法的比较,请参阅 The C10K problem。

【讨论】:

不在 POSIX 下,因为 man select 在 BUG 部分中声明,select 可以报告套接字已准备好用于 read,但随后 read 可能会阻塞。所以不完全一样。 我们现在已经大大超越了 C10K 问题。 syscall poll(2) 是 O(n),epoll(2) 是 O(1),这只是 C10K+ 的解决方案 如果 freebsd 是一个选项,则使用 kqueue【参考方案5】:

fcntl() 一直可靠地为我工作。无论如何,这是我用来启用/禁用套接字阻塞的函数:

#include <fcntl.h>

/** Returns true on success, or false if there was an error */
bool SetSocketBlockingEnabled(int fd, bool blocking)

   if (fd < 0) return false;

#ifdef _WIN32
   unsigned long mode = blocking ? 0 : 1;
   return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? true : false;
#else
   int flags = fcntl(fd, F_GETFL, 0);
   if (flags == -1) return false;
   flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
   return (fcntl(fd, F_SETFL, flags) == 0) ? true : false;
#endif

【讨论】:

【参考方案6】:

您被误导了 fcntl() 并不总是可靠的。这是不真实的。

要将套接字标记为非阻塞,代码很简单:

// where socketfd is the socket you want to make non-blocking
int status = fcntl(socketfd, F_SETFL, fcntl(socketfd, F_GETFL, 0) | O_NONBLOCK);

if (status == -1)
  perror("calling fcntl");
  // handle the error.  By the way, I've never seen fcntl fail in this way

在 Linux 下,在内核 > 2.6.27 上,您还可以使用 socket()accept4() 从一开始就创建非阻塞套接字。

例如

   // client side
   int socketfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);

   // server side - see man page for accept4 under linux 
   int socketfd = accept4( ... , SOCK_NONBLOCK);

它节省了一些工作,但便携性较差,因此我倾向于使用fcntl() 进行设置。

【讨论】:

哎呀,刚刚意识到这是一个非常古老的问题。啊,我想是时候提供一个更新的答案了。 可能有点旧,但仍然很有帮助。你的答案似乎正是我想要的。 难道你的内部fcntl 在外部fcntl 失败之前就失败了吗? 根据您编写代码的方式,如果在创建套接字后调用fcntl,您可能会设置竞争条件......这就是添加SOCK_NONBLOCK 标志的原因,所以您可以原子地创建一个非阻塞的套接字 注意fcntl(socketfd, F_GETFL, 0) 可能会失败,所以在将返回值传递到输出之前检查它【参考方案7】:

我知道这是一个老问题,但是对于最终在谷歌上寻找有关如何处理阻塞和非阻塞套接字的信息的每个人来说,这里是对如何处理 I/O 的不同方法的深入解释套接字模式 - http://dwise1.net/pgm/sockets/blocking.html.

快速总结:

那么为什么套接字会阻塞?

处理阻塞套接字的基本编程技术有哪些?

有一个不关心阻塞的设计 使用选择 使用非阻塞套接字。 使用多线程或多任务

【讨论】:

很好的发现,很有帮助。【参考方案8】:

如果您想将 socket 更改为非阻塞,准确地将 accept() 更改为 NON-Blocking 状态然后

    int flags=fcntl(master_socket, F_GETFL);
        fcntl(master_socket, F_SETFL,flags| O_NONBLOCK); /* Change the socket into non-blocking state  F_SETFL is a command saying set flag and flag is 0_NONBLOCK     */                                          

while(1)
    if((newSocket = accept(master_socket, (struct sockaddr *) &address, &addr_size))<0)
                if(errno==EWOULDBLOCK)
                       puts("\n No clients currently available............ \n");
                       continue;
               
    else
          puts("\nClient approched............ \n");
    


【讨论】:

【参考方案9】:

有时使用“send/recv”系列系统调用很方便。如果flags 参数包含MSG_DONTWAIT 标志,则每次调用的行为类似于设置了O_NONBLOCK 标志的套接字。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

【讨论】:

【参考方案10】:

在 Linux 和 BSD 上,您可以直接在非阻塞模式下创建套接字 (https://www.man7.org/linux/man-pages/man7/socket.7.html):

int fd = socket(res->ai_family, res->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC, res->ai_protocol);
if (fd == -1) 
    perror("socket");
    return -1;

在接受连接时可以使用accept4函数以非阻塞模式直接接受新连接(https://man7.org/linux/man-pages/man2/accept.2.html):

int fd = accept4(lfd, NULL, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (fd == -1) 
    perror("accept4");
    return -1;

我不知道为什么接受的答案没有提到这一点。

【讨论】:

accept4() 是一个非标准的 Linux 扩展。

以上是关于如何将 TCP 套接字更改为非阻塞?的主要内容,如果未能解决你的问题,请参考以下文章

在 C 中使用非阻塞套接字连接

windows下在非阻塞TCP套接字上使用SO_SNDBUF的奇怪行为

TCP之非阻塞connect和accept

创建后将 POSIX 套接字的类型从 UDP 更改为 TCP

Python 非阻塞套接字和可靠的 UDP

什么可能导致非阻塞套接字阻塞“recv”?