接收多播数据包的用户缓冲区大小?

3
下面的代码来自Git。它加入了一个多播组并接收数据包。
在这里,我们循环接收数据并将其存储在名为msgbuf的缓冲区中:
while (1) 
{
    char msgbuf[MSGBUFSIZE];
    const int addrlen = sizeof(addr);
    const int nbytes = recvfrom(fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &addr, &addrlen);

我该如何选择缓冲区msgBuf的大小?它只需要是最大数据包大小吗?还是我需要在处理第一个数据包时存储多个数据包?

完整代码:

int main(int argc, char *argv[])
{
    if (argc != 3) {
       printf("Command line args should be multicast group and port\n");
       printf("(e.g. for SSDP, `listener 239.255.255.250 1900`)\n");
       return 1;
    }

    char* group = argv[1]; // e.g. 239.255.255.250 for SSDP
    int port = atoi(argv[2]); // 0 if error, which is an invalid port

    if(port <= 0)
    {
        perror("Invalid port");
        return 1;
    }


    // create what looks like an ordinary UDP socket
    //
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) 
    {
        perror("socket");
        return 1;
    }

    // allow multiple sockets to use the same PORT number
    //
    u_int yes = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes)) < 0)
    {
       perror("Reusing ADDR failed");
       return 1;
    }

        // set up destination address
    //
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); // differs from sender
    addr.sin_port = htons(port);

    // bind to receive address
    //
    if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) 
    {
        perror("bind");
        return 1;
    }

    // use setsockopt() to request that the kernel join a multicast group
    //
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(group);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

    if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof(mreq)) < 0)
    {
        perror("setsockopt");
        return 1;
    }

    // now just enter a read-print loop
    //
    while (1) 
    {
        char msgbuf[MSGBUFSIZE];
        const int addrlen = sizeof(addr);
        const int nbytes = recvfrom(fd, msgbuf, MSGBUFSIZE, 0, (struct sockaddr *) &addr, &addrlen);

        if (nbytes < 0) 
        {
            perror("recvfrom");
            return 1;
        }
        msgbuf[nbytes] = '\0';
        puts(msgbuf);
     }

    return 0;
}

如果recvfrom()读取了MSGBUFSIZE字节,则该代码会导致UB。然后,msgbuf[nbytes] = '\0'会导致缓冲区溢出。如果这是生产代码,那么实际上数据包可能会更小。 - Ingo Leonhardt
@IngoLeonhardt 我很感谢你的观点,但这个问题是关于如何确定缓冲区大小的,所以说溢出有点毫无意义。我需要知道通常情况下如何确定缓冲区大小。它是基于最大数据包大小吗?还是我必须存储多个数据包,因此只需使缓冲区巨大? - user997112
1
我知道这有点离题了。当然,对你的问题的回答取决于具体的通信方式,而这我不清楚。但至少缓冲区溢出可能表明在该通信中不需要存储多个数据包,因为我认为在这种情况下会发生溢出。 - Ingo Leonhardt
@IngoLeonhardt 不用担心,我并不是想听起来很粗鲁,我只是想引导回到问题本身:需要存储一个数据包还是多个数据包。 我认为只需要存储一个数据包(而操作系统缓冲区可能会存储任何累积的数据包)。 - user997112
1
我从未觉得你很粗鲁 :-) 如果我上次的评论给你留下了这样的印象,那只是因为我不是母语人士。希望你的问题能引起某个更懂的人的兴趣。 - Ingo Leonhardt
2个回答

2
与TCP将数据包合并成流不同,UDP尊重数据包边界,因此recvfrom一次只能获取一个数据包。
因此,MSGBUFSIZE只需要足够大以容纳单个数据包。如果您不使用巨型数据包,则大小应为1500,否则应为9000。

1

正如@Ingo所指出的,在这段代码中,您应该使用:

    char msgbuf[MSGBUFSIZE + 1];

+ 1是因为recvfrom可以将最多MSGBUFSIZE字节写入数组,然后您在末尾再写入一个NUL字节。

至于选择MSGBUFSIZE的值,这取决于协议规范。考虑到大多数物理网络无法在不分段的情况下发送超过1500字节的数据,2048之类的值可能是合理的。您还可以检查nbytes == MSGBUFSIZE(也可以使用MSG_TRUNC),并报告“数据包被截断”的警告,但这基本上不会发生在通过公共互联网路由的数据包上。

对于以下问题的回答:

我需要在处理第一个数据包时存储多个数据包吗?

通常应该让网络堆栈来处理这个问题。 recv维护数据包/报文边界,因此始终会从提供的缓冲区地址开始写入下一个数据包。同样,如何检测和处理错误(例如缺少或乱序的数据包以及超时)取决于协议。


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