可变大小的缓冲区用于接收UDP数据包

3

我有一个UDP套接字,会接收一些数据包,这些数据包大小可能不同,并且我是异步处理的:

socket.async_receive_from(boost::asio::buffer(buffer, 65536), senderEndpoint, handler);

这里的问题是为了处理不同的大小,我有一个大缓冲区,可以通过使用可变大小的缓冲区来解决这个问题。
据我所知,当使用async_receive_from时,处理程序每次只会收到一个数据包,因为UDP保留了数据包边界。那么,是否有一种方法可以将空缓冲区提供给async_receive_from,并使Asio自动调整缓冲区大小以适应数据包大小?
另外,请注意,我对数据包进行了包装,因此对于传输到此套接字的每个数据包,前4个字节是数据包的长度。

为什么?只需指定您需要的最大缓冲区即可。接收方法将告诉您实际接收到的字节数。 - user207421
该程序将在移动平台上运行,而不是PC - 因此其内存占用应该很轻。我认为可以有比分配一个大缓冲区更有效的方法。 - Synxis
1
一次性分配一个大缓冲区比分配许多小缓冲区更高效。 - user207421
2
你可以通过null_buffers来惰性地分配适当大小的缓冲区。此外,包含长度的前4个字节可能是不必要的,因为available()返回可供读取的数据报的大小,而receive()最多只会出队一个数据报。 - Tanner Sansbury
2个回答

12

为了得到更精确的答案,这里有一份详细解释的代码。

首先,我们需要调用接收处理程序而不填充缓冲区。这可以使用boost :: asio :: null_buffer()来完成(有关更多信息,请参见Tanner所述的反应器式操作)。

void UDPConnection::receive()
{
    socket.async_receive(boost::asio::null_buffers(), receive_handler);
}

现在,当收到数据包时,receive_handler函数将被调用,而无需填充任何缓冲区。

现在,对于这个处理程序:

void UDPConnection::handleReceive(const boost::system::error_code& error, unsigned int)
{
    // Standard check: avoid processing anything if operation was canceled
    // Usually happens when closing the socket
    if(error == boost::asio::error::operation_aborted)
        return;

通过socket.available(),我们可以获得要读取的字节数。重要提示:这个数字不一定是数据包的大小!对于我的测试来说,这个数字总是大于数据包的大小(即使是8 kB的数据包,这是我电脑能处理的最大值)。

    // Prepare buffer
    unsigned int available = socket.available();
    unsigned char* buffer = new unsigned char[available];

这里是魔法发生的地方:真正的接收调用在这里完成,一般会很快,因为它只会填充缓冲区。即使在调用时套接字中有多个数据包,此调用也仅会出队一个数据包。这里返回值很重要,因为可能只有缓冲区的一部分被填充。例如:available=50,packetSize=13

    // Fill it
    boost::asio::ip::udp::endpoint senderEndpoint;
    boost::system::error_code ec;
    unsigned int packetSize = socket.receive_from(boost::asio::buffer(buffer, available), senderEndpoint, 0, ec);

现在,只需要进行标准的错误检查/处理/等等...

    if(ec)
    {
        // ...
    }

    // Process packet
    // ...
    receive();
}

这难道不意味着你需要进行额外的数据复制,因为现在Asio需要将数据存储在内部缓冲区中,然后再将其复制到您的缓冲区中吗?此外,您为每个数据包动态分配缓冲区,而不是仅分配一次,即使它是8KB,它仍然不是很大。 - Andriy Tylychko
那很可能是这样的。一旦我有时间,我可能会改进这个答案(并且消除其中展示的内存泄漏!) - Synxis

1
你可以手动完成这个操作。 my_socket.available() 返回套接字中等待读取的下一个Udp数据包的大小。因此,您可以使用它来检查下一个数据包的大小,相应地增加缓冲区,然后接收它。但是,我同意评论者的意见,那很可能不如只使用最大可能的缓冲区大小高效。除非最大值比平均数据包要大得多,并且很少有可能出现,以至于您的应用程序通常不必接收它。

但这是优化问题。您问题的答案是 available() 和自己增加缓冲区。

编辑:我知道在异步情况下这不太理想,因为available() 只有在调用时已经等待下一个udp数据包时才返回其大小。


3
针对异步场景,反应器风格操作 提供了一种干净的解决方案来延迟分配适当大小的缓冲区。 - Tanner Sansbury

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