使用MSG_NONBLOCK和MSG_WAITALL的recv函数

11

我希望使用带有非阻塞标志MSG_NONBLOCK的recv系统调用。但是,使用此标志系统调用可能会在完整请求满足之前返回。

  • 我可以添加MSG_WAITALL标志吗?它会是非阻塞的吗?
  • 还是说我应该将阻塞的recv重写为带有非阻塞recv的循环?

你想节省用户进程内存(用于缓冲不完整的消息),因此希望使用内核内存。我怀疑这样做是否可行。 - Lorinczy Zsigmond
3个回答

5

对于至少在Linux上的IPv4 TCP接收,如果指定了MSG_NONBLOCK(或文件描述符设置为非阻塞),则将忽略MSG_WAITALL。

来自Linux内核中net/ipv4/tcp.c中的tcp_recvmsg():

if (copied >= target && !sk->sk_backlog.tail)
        break;

if (copied) {
        if (sk->sk_err ||
            sk->sk_state == TCP_CLOSE ||
            (sk->sk_shutdown & RCV_SHUTDOWN) ||
            !timeo ||
            signal_pending(current))
                break;

如果指定了MSG_DONTWAIT,则此功能中的目标大小将设置为请求的大小,否则将设置为较小的值(至少为1)。函数将在以下情况下完成:
  1. 已复制足够的字节
  2. 存在套接字错误
  3. 套接字已关闭或关闭
  4. timeo为0(套接字设置为非阻塞)
  5. 进程有待处理的信号
对我来说,这似乎可能是Linux中的一个错误,但无论如何它都不会按照您想要的方式工作。看起来dec-vt100的解决方案可以解决问题,但如果您尝试从多个进程或线程中的同一套接字接收,则存在竞争条件。
也就是说,在您的线程执行peek后,另一个线程/进程可以执行recv()调用,导致您的线程在第二个recv()上阻塞。

timeo 为零只是表示没有设置读取超时,对吗? - user207421

3

我为同样的问题做了以下操作,但是我希望得到一些确认,证明这个方案可以正常工作...

ssize_t recv_allOrNothing(int socket_id, void *buffer, size_t buffer_len, bool block = false)
{
    if(!block)
    {
        ssize_t bytes_received = recv(socket_id, buffer, buffer_len, MSG_DONTWAIT | MSG_PEEK);

        if (bytes_received == -1)
            return -1;

        if ((size_t)bytes_received != buffer_len)
            return 0;
    }

    return recv(socket_id, buffer, buffer_len, MSG_WAITALL);
}

3

编辑:

普通的recv()将返回调用时tcp缓冲区中请求字节数的任何内容。MSG_DONTWAIT只是避免了在套接字上没有准备好读取的数据时阻塞。MSG_WAITALL请求阻塞,直到可以读取请求的全部字节数。因此,您不会得到“全部或无”的行为。如果没有数据存在,则最多应该获得EAGAIN并阻塞,直到完整消息可用为止。

您可能能够使用MSG_PEEK或ioctl()与FIONREAD(如果系统支持)创建出您想要的有效行为,但我不知道您如何仅使用recv()标志来实现您的目标。


1
非阻塞操作可以仅返回所需消息的一部分。如果非阻塞接收只想返回消息的一部分,我想从中得到EAGAIN错误代码。因此,我需要具有“全有或全无”行为的非阻塞接收函数。 - osgx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接