高频接收UDP数据包:会有数据包丢失吗?

3
我有一个 C++ 应用程序,使用 Boost.Asio 的 UDP 服务器从千兆本地网络设备接收高频率数据包(每秒 3500 个)。一些用户报告丢失了一些数据包。所以最后我选择同时运行 WireShark 和我的应用程序,检查 WireShark 能否接收但我的应用程序无法接收的数据包。

我发现 WireShark 并没有接收到每个数据包,似乎它会漏掉一些数据包。我的应用程序也会比 Wireshark 正确接收的数据包少一些。

我的问题:WireShark 是否可能接收到我的应用程序无法接收到的数据包?我认为也许 WireShark 对 IP 协议栈有低级别访问权限,即使这些数据包在 WireShark 中显示出来,操作系统也会将其丢弃?在 (1) 中的操作是否可能花费太长时间,以致下一个 async_receive_from 调用太晚? 我希望能得到关于此主题的意见。谢谢。

以下是我使用的代码(非常基础)。udp_server.h:

#pragma once

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <fstream>

const int MAX_BUFFER_SIZE = 65537;

using boost::asio::ip::udp;

class UDPServer
{
public:
    UDPServer(boost::asio::io_service& ios, udp::endpoint endpoint)
        :m_io_service(ios),
        m_udp_socket(m_io_service, endpoint)
    {
        // Resize the buffer to max size in the component property
        m_recv_buffer.resize(MAX_BUFFER_SIZE);

        m_output_file.open("out.bin", std::ios::out | std::ios::binary);

        StartReceive();
    }

public:
    void StartReceive()
    {
        m_udp_socket.async_receive_from(
            boost::asio::buffer(m_recv_buffer), m_remote_endpoint,
            boost::bind(&UDPServer::HandleReceive, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }

private:
    void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        if (!error || error == boost::asio::error::message_size)
        {
            // Write to output -- (1)
            m_output_file.sputn(&m_recv_buffer[0], bytes_transferred);

            // Start to receive again
            StartReceive();
        }
    }

    boost::asio::io_service&    m_io_service;
    udp::socket                 m_udp_socket;
    udp::endpoint               m_remote_endpoint;
    std::vector<char>           m_recv_buffer;
    std::filebuf                m_output_file;
};

main.cpp:

include <boost/asio.hpp>
#include "udp_server.h"

const unsigned short PORT_NUMBER = 44444;

int main()
{
    boost::asio::io_service ios;
    boost::asio::ip::udp::endpoint endpoint(udp::endpoint(udp::v4(), PORT_NUMBER));
    UDPServer server(ios, endpoint);
    ios.run();

    return 0;
}

“怎么可能”:数据包可以(将会)在物理卡上缓冲,如果卡由于数据包未能被足够快地处理而用尽了缓冲空间,则它们将被丢弃。应用程序的差异意味着不同的数据包会被错过。 - Richard Critten
5
UDP是不可靠的。你需要在其上添加一些东西来处理丢失的数据包等问题。 - pm100
还有,为什么要使用std::filebuf?它并不是用来这样使用的。只需要使用std::ofstream(可能关闭stdio同步)。 - edmz
2
UDP是一种无连接、不可靠的“发射即忘”协议。您将会丢失一些UDP数据报,通过使用UDP,您接受了这个事实。您要么需要处理数据丢失,要么需要使用可靠的协议。 - Ron Maupin
2
你的代码完全没有优化。例如,你真的需要每收到一个数据包就调用一次 bind 吗?每个数据包后面都需要调用 sputn 吗?在调用 async_receive_from 之前需要等待 sputn 返回吗?进行优化将有所帮助。但无论你做什么,有时候会丢失数据包、重复数据包或接收到乱序的数据包。如果你无法处理这些问题,就不要使用 UDP。 - David Schwartz
@black:我选择使用std::filebuf,因为在几年前的基准测试中,它是我尝试过的最快的。我比较了文本模式和二进制模式下的std::ofstreamstd::filebuf,而std::filebuf明显是最快的。无论如何,在我的实际代码中,我使用另一个处理代码来执行其他操作,我不是在写入二进制文件。这只是为了调试。 - poukill
3个回答

4
这是可能的,Wireshark可以获取一个数据包,但我的应用程序无法接收到它吗?
是的。特别地,每个套接字都有自己的固定大小的传入数据缓冲区,如果在内核尝试将新的传入数据包添加到该缓冲区的那一刻,缓冲区没有足够的可用空间来容纳该数据包,则该数据包不会被添加到缓冲区中,因此从该套接字读取传入数据的应用程序将无法接收到它。
鉴于此,Wireshark的缓冲区可能有足够的空间来接受传入的数据包,但您自己的应用程序的缓冲区却没有足够的空间。

增加接收套接字缓冲区的大小有什么风险吗?例如,m_udp_socket.set_option(boost::asio::socket_base::receive_buffer_size(1000000)) 将设置为1MB缓冲区。这只会有好处还是有不利因素? - poukill
据我所知,唯一的缺点是它会比原本使用更多的RAM(每个插座)。这是否是一个重大问题,取决于您想同时打开多少个大缓冲区套接字以及您的机器上有多少可用的空闲RAM :) - Jeremy Friesner

1
一个数据包有多少字节?你将缓冲区设置为64K,让大小为32K。然后3.5k * 32k表示112MB/s,我认为千兆网络承载不了它。当你将数据包写入文件时,sputn可能会阻止你接收更多的数据包,因此驱动程序的缓冲区溢出,数据包被丢弃。

每个数据包大小为1400字节,因此低于MTU大小。它提供了约5MB/s的速度,但频率非常高。在我的应用程序中没有任何缓冲,我想这相当苛刻。 - poukill

1

总之,是的。

如果你想要修复或改进这个问题,而不仅仅是询问它,使用比默认值更大得多的套接字接收缓冲区,尽可能大地利用平台,并确保你对文件的写入也进行了缓冲,使用尽可能大的缓冲区,以满足关于崩溃数据丢失的任何要求。


即使在这种情况下,您仍然会丢失数据包...稍后,但您将丢失它们。数据包的丢失是缓冲填充过程的副作用。缓冲区存在是为了吸收数据包节奏的波动,但不是填满缓冲区的稳定流量。而且这还没有考虑到线路水平的噪声错误或其他手段。扩大缓冲区大小并不是解决方案。 - Luis Colorado
@LuisColorado 这是解决方案的一部分。另外两个部分是通过将写入文件的缓冲区,尽可能快地进行读取过程,并接受始终会有一些数据包丢失的事实。 - user207421
那不是解决方案的一部分...扩大缓冲区大小只是为了延迟问题,看看它是否会消失(或者把头埋在地里以避免恐惧)。阅读一些队列理论,你就会明白这一点。如果传入的数据量大于你处理它的速率,队列的平均大小将无限增长(它是一个无界线性算子,信不信由你)。 - Luis Colorado

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