【问题标题】:How do I get the size of the msg_control buffer for recvmsg?如何获取 recvmsg 的 msg_control 缓冲区的大小?
【发布时间】:2018-07-29 10:30:36
【问题描述】:

在使用 recvmsg 时,我使用 MSG_TRUNCMSG_PEEK,如下所示:

msgLen = recvmsg(fd, &hdr, MSG_PEEK | MSG_TRUNC)

这给了我为下一条消息分配的缓冲区大小

我的问题是如何获得应该为标题内的msg_control 字段分配的缓冲区大小

【问题讨论】:

  • 这看起来可能是相关的:mirbsd.org/htman/i386/man3/CMSG_DATA.htm
  • @Galik 这些宏用于缓冲区中的消息,您已经阅读了它们
  • 在这种情况下,只需将数据读入完整的缓冲区,然后按照 Galik 的建议进行分解。
  • @o_weisman 我的问题是我应该分配多大的缓冲区才能接收所有消息。 Galik 提供了一个宏链接,用于在将消息从内核接收到本地缓冲区后处理缓冲区内的消息。
  • msg_control不是刚刚被调用者填写的吗?我的意思是,据文档所说,recvmsg 填充了标题,所以我猜你不需要为msg_control 分配存储空间。

标签: c++ linux recvmmsg


【解决方案1】:

基于the doc,需要为msg_control分配大小为msg_controllen的缓冲区。要事先了解尺寸,您可以像 recvmsg(fd, &hdr, MSG_PEEK | MSG_TRUNC) 一样拨打电话。 MSG_PEEK 不会删除消息,MSG_TRUNC 将允许返回消息的大小,即使缓冲区太小。

一些解决方案:

  • 调用recvmsg(fd, &hdr, MSG_PEEK | MSG_TRUNC)并根据返回的大小在hdr中初始化缓冲区,然后再次调用它而不带标志。
  • 分配一个足够大的缓冲区,如果您事先知道消息的大小,然后调用 recvmsg。如果发生错误(返回-1),如果消息被截断(MSG_TRUNC 或 MSG_CTRUNC)检查错误代码

【讨论】:

  • 这将为我提供数据的大小,而不是msg_controllen 的大小,以便接收所有辅助数据
  • 这就是为什么我说您可以使用 MSG_PEEK 进行第一次调用以获取数据的大小,并相应地分配缓冲区。或者只是分配一个足够大的缓冲区。调用 recvmsg 后,msg_controllen 将更新为接收到的数据的实际大小。
  • recvmsg 的调用不会更新msg_controllen 我尝试将msg_control 设置为NULLmsg_controllen 为0。在我知道包含控制权的调用上都没有变化数据。
  • 来自文档:The field msg_control, which has length msg_controllen, points to a buffer for other protocol control-related messages or miscellaneous ancillary data. When recvmsg() is called, msg_controllen should con‐ tain the length of the available buffer in msg_control; upon return from a successful call it will contain the length of the control mes‐ sage sequence. 也许它不起作用,因为 msg_control 为 NULL,或者消息类型。
  • still 它永远不会返回将要写入的数据的大小。在我看来,问题在于recvmsg 接受MSG_TRUNC 作为flags 字段中的参数,但不接受MSG_CTRUNC
【解决方案2】:

恐怕您无法从 Posix.1g 套接字 API 中获得该值。不确定所有实现,但在 Linux 中不可能。您可能会注意到,辅助数据缓冲区中没有提供控制流,因此您需要自己实现它,以防您在进程之间发送大量信息。另一方面,对于常见的情况,您已经知道在编译时将收到什么(但您可能已经知道这一点)。如果您需要实现自己的控制流,请考虑到,在 Linux 中,辅助数据 seems to behave 就像流套接字一样。

但是,您可以在/proc/sys/net/core/optmem_max 中获取/设置最坏情况场景的缓冲区长度,请参阅cmsg(3)。所以,我想你可以将它设置为一个合理的值并声明一个那么大的缓冲区。

【讨论】:

    【解决方案3】:

    我不能代表 macOS 以外的其他平台(其核心基于 FreeBSD 核心,所以在 BSD 系统中也可能没有什么不同),而且 POSIX 标准也没有帮助,因为它几乎留下了所有细节由协议定义,但默认情况下,recvmsg 在 macOS 上对于 UDP 套接字的行为是根本不传递任何控制数据。无论您在输入上设置msg_control 的大小,它在输出上始终是0。如果您希望接收任何控制数据,您首先必须为套接字显式启用它。

    例如如果您想知道数据包的地址、源地址和目标地址(msg_name 只给您接收数据包的源地址),那么您必须这样做:

    int yes = 1;
    setsockopt(soc, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes));
    

    现在您将获得 IPv4 套接字的目标地址,记录为

    msghdr 结构中的 msg_control 字段指向一个缓冲区 包含一个 cmsghdr 结构,后跟 IP 地址。 cmsghdr 字段具有以下值:

    cmsg_len = sizeof(struct in_addr)
    cmsg_level = IPPROTO_IP
    cmsg_type = IP_RECVDSTADDR
    

    这意味着您需要在我的系统上提供至少 16 个字节的存储空间,因为 struct cmsghdr 在该系统上始终是 12 个字节(4 倍 32 位),而 IPv4 地址是另外 4 个字节,即 16 个字节。这个值需要使用CMSG_SPACE 宏正确四舍五入,但在我的系统上,宏只确保它是 32 位的倍数,而 16 字节已经是这样的倍数,所以CMSG_SPACE(16) 为我返回16

    因为我事先知道我启用了哪些选项以及我将收到哪些控制数据,所以我可以提前准确计算出所需的空间。

    对于原始套接字和其他更模糊的套接字,默认情况下某些控制数据可能始终包含在输出中,即使未显式启用也是如此,但此控制数据的大小将始终相等,并且不会在数据包之间波动正如数据包有效载荷大小一样。因此,一旦您知道正确的大小,您就可以依靠它不会改变的事实,至少在您启用/禁用任何选项的情况下不会改变。

    如果您的控制数据缓冲区太小,则始终在输出中设置MSG_CTRUNC 标志(即使您没有在输入上设置任何标志),那么您需要增加控制数据缓冲区大小并尝试再次(如果您使用MSG_PEEK 作为输入标志,则使用下一个数据包或相同的数据包),直到您曾经能够进行该调用而没有在输出上获得MSG_CTRUNC 标志。最后看看msg_control 字段的内容。在输入时,它是可用的缓冲区空间量,但在输出时,它包含实际使用的确切缓冲区空间量。这是接收该套接字的所有未来数据包的控制数据所需的确切缓冲区大小,除非您更改将导致发送更多/更少控制数据的选项,然后您只需要再次检测该大小,方法与之前。

    更完整的例子,你也可以看看:
    https://stackoverflow.com/a/49308499/15809

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-28
      • 1970-01-01
      • 2010-10-11
      • 1970-01-01
      • 2013-05-07
      相关资源
      最近更新 更多