如何获取 recvmsg 的 msg_control 缓冲区的大小?

How do I get the size of the msg_control buffer for recvmsg?

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

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

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

我的问题是如何获取应该为 header

中的 msg_control 字段分配的缓冲区大小

基于the doc,您需要为msg_control分配大小为msg_controllen的缓冲区。要事先知道大小,您可以像之前那样调用 recvmsg(fd, &hdr, MSG_PEEK | MSG_TRUNC)。 MSG_PEEK 不会删除消息并且 MSG_TRUNC 将允许 return 消息的大小,即使缓冲区太小也是如此。

几个解决方案:

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

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

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

除了 macOS(其核心基于 FreeBSD 核心,因此在 BSD-systems 中可能也没有什么不同)我不能代表其他平台,而且 POSIX 标准也无济于事,因为它留下了协议定义的几乎所有细节,但默认情况下 recvmsg 在 macOS 上针对 UDP 套接字的默认行为是根本不传递任何控制数据。无论您在输入上设置 msg_control 什么尺寸,它在输出上总是 0 。如果你想接收任何控制数据,你首先必须明确地为套接字启用它。

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

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

现在您将获得记录为

的 IPv4 套接字的目标地址

The msg_control field in the msghdr structure points to a buffer that contains a cmsghdr structure followed by the IP address. The cmsghdr fields have the following values:

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) returns 16 对我来说。

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

对于原始套接字和其他更模糊的套接字,默认情况下某些控制数据可能始终包含在输出中,即使未明确启用,但此控制数据的大小将始终相等,并且不会随数据包波动像数据包有效负载大小一样打包。因此,一旦您知道了正确的尺寸,您就可以相信它不会改变,至少在没有您 enabling/disabling 任何选择的情况下不会改变。

如果您的控制数据缓冲区太小,MSG_CTRUNC 标志总是在输出中设置(即使您没有在输入上设置任何标志),那么您需要增加控制数据缓冲区大小并重试(使用下一个数据包或使用相同的数据包,如果您使用 MSG_PEEK 作为输入标志),直到您曾经能够在输出上没有获得 MSG_CTRUNC 标志的情况下进行该调用.最后看看msg_control字段说的是什么。在输入时,它是可用缓冲区 space 的数量,但在输出时,它包含实际使用的缓冲区 space 的确切数量。这是您接收该套接字所有未来数据包的控制数据所需的确切缓冲区大小,除非您更改将导致 more/less 控制数据被发送的选项,然后您只需要再次检测该大小相同和以前一样。

更完整的例子,你也可以看看: