【问题标题】:Why does this program not receive the expected UDP packets?为什么这个程序没有收到预期的 UDP 数据包?
【发布时间】:2015-10-03 00:54:13
【问题描述】:

我正在尝试使用 Boost asio 接收 UDP 数据包。我的代码基于this blocking UDP client example from the asio documentation

我正在尝试从 C6655 TI DSP 接收类似 BOOTP 的 UDP 数据包,该数据包以 3 秒的间隔传输。我让 Wireshark 监视我的程序正在侦听的同一接口,它可以看到数据包到达(请参阅下面的确切数据包数据,从 Wireshark 导出)。这些数据包真的来自 DSP,我用tcpdump 捕获了一个数据包,我正在用packeth 从树莓派模拟它。

但是,我的程序没有收到数据包。它有 4 秒的超时时间(因为 DSP 每 3 秒广播一次)。如果它达到超时,它会打印一条消息,否则它应该打印接收到的字节数。程序的完整(可编译)源代码如下(大约 100 行)。

正在使用参数192.168.5.122 67 4000 调用该命令,这意味着在 192.168.5.122:67 上侦听,超时时间为 4000 毫秒。

编辑:除了下面的代码之外,我还尝试将其作为我的端点:udp::endpoint listen_endpoint(boost::asio::ip::address_v4::any(), atoi(argv[2])); 以及某处搜索结果建议的 IP 地址 0.0.0.0

我还添加了以下内容,但无济于事:

boost::asio::socket_base::broadcast option(true);
socket_.set_option(option);

我确实有一个能够正确接收此数据包的程序,它是使用 Berkeley 套接字编写的。除了绑定到 INADDR_ANY 之外,它并没有做任何我可以看到的特殊操作。

这是完整的程序:

//
// blocking_udp_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~
//
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <iostream>

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

class listener
{
public:
    listener(const udp::endpoint& listen_endpoint)
        : socket_(io_service_, listen_endpoint)
        , deadline_(io_service_)
    {
        deadline_.expires_at(boost::posix_time::pos_infin);
        check_deadline();
    }

    std::size_t receive(const boost::asio::mutable_buffer& buffer, boost::posix_time::time_duration timeout, boost::system::error_code& ec)
    {
        deadline_.expires_from_now(timeout);
        ec = boost::asio::error::would_block;
        std::size_t length = 0;
        socket_.async_receive(boost::asio::buffer(buffer), boost::bind(&listener::handle_receive, _1, _2, &ec, &length));

        // TODO: The following do/while is hinky. Does run_one() need to happen before the comparison?
        do io_service_.run_one();
        while (ec == boost::asio::error::would_block);

        return length;
    }

private:
    void check_deadline()
    {
        if (deadline_.expires_at() <= deadline_timer::traits_type::now())
        {
            // cancel() won't work on XP. Something about using close() instead... Look it up. I'm doing this on Win10.
            socket_.cancel();
            deadline_.expires_at(boost::posix_time::pos_infin);
        }
        deadline_.async_wait(boost::bind(&listener::check_deadline, this));
    }

    static void handle_receive(const boost::system::error_code& ec, std::size_t length, boost::system::error_code* out_ec, std::size_t* out_length)
    {
        *out_ec = ec;
        *out_length = length;
    }

private:
    boost::asio::io_service io_service_;
    udp::socket socket_;
    deadline_timer deadline_;
};

int main(int argc, char* argv[])
{
    try
    {
        if (argc != 4)
        {
            std::cerr << "Usage: blocking_udp_timeout <listen_addr> <listen_port> <timeout_ms>\n";
            return 1;
        }

        udp::endpoint listen_endpoint(boost::asio::ip::address::from_string("0.0.0.0"), atoi(argv[2]));
        std::cout << "Endpoint: " << listen_endpoint << std::endl;

        auto timeout = atoi(argv[3]);
        std::cout << "Timeout : " << timeout << std::endl;

        listener c(listen_endpoint);

        for (;;)
        {
            char data[1024];
            boost::system::error_code ec;
            auto n = c.receive(boost::asio::buffer(data), boost::posix_time::milliseconds{timeout}, ec);

            if (ec)
            {
                std::cout << "Receive error: " << ec.message() << "\n";
            }
            else
            {
                std::cout << "Received " << n << " bytes." << std::endl;
            }
        }
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

这是我要接收的数据包。这包括以太网帧:

0000   ff ff ff ff ff ff c4 ed ba aa 28 35 08 00 45 00  ..........(5..E.
0010   01 48 00 01 00 00 10 11 a9 a5 00 00 00 00 00 00  .H..............
0020   00 00 00 44 00 43 01 34 00 00 01 01 06 00 12 34  ...D.C.4.......4
0030   56 78 00 01 00 00 00 00 00 00 00 00 00 00 00 00  Vx..............
0040   00 00 00 00 00 00 c4 ed ba aa 28 35 00 00 00 00  ..........(5....
0050   00 00 00 00 00 00 74 69 2d 62 6f 6f 74 2d 74 61  ......ti-boot-ta
0060   62 6c 65 2d 73 76 72 00 00 00 00 00 00 00 00 00  ble-svr.........
0070   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0080   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0090   00 00 00 00 00 00 74 69 2d 62 6f 6f 74 2d 74 61  ......ti-boot-ta
00a0   62 6c 65 2d 30 30 30 37 00 00 00 00 00 00 00 00  ble-0007........
00b0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00c0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00d0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00e0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00f0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0100   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0110   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0120   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0130   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0140   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0150   00 00 00 00 00 00                                ......

我确实有一个可以接收此数据包的伯克利套接字实现(我已删除错误处理和其他杂项代码):

{
    struct sockaddr_in servaddr;
    socklen_t len;
    char mesg[RECV_BUFFER_LENGTH];

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(67);
    bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    n = recvfrom(sockfd, mesg, RECV_BUFFER_LENGTH, 0, NULL, &len);
}

【问题讨论】:

  • BOOTP 通常是广播,需要特殊标志才能接收
  • 客户端代码看起来不错:socket_base::broadcast 仅在发送 UDP 广播消息时才需要,并且绑定到预期数据的接口地址或address_v4::any()(即0.0.0.0)就可以了。这是一个简单的demo。粗略一看,发布的以太网帧中0.0.0.0 的IPv4 目标地址很可疑,因为0.0.0.0 在路由中通常具有特殊含义,看起来不像是广播地址。
  • @TannerSansbury 来自 TI 的文档指出“以太网就绪公告帧以 BOOTP 请求的形式制作,因此它可以使用标准格式。没有处理此消息的响应,它是构建以便大多数(如果不是全部)BOOTP 和 DHCP 服务器将丢弃它。”这可能就是您所指的(它们不是那么具体)。但是,当 BOOTP 客户端没有 IP 地址(或不知道从哪里获取)时,您希望 BOOTP 客户端在该字段中输入什么?
  • 我将尝试编辑数据包并更改该 IP。不过,那将是不幸的,因为数据包来自我无法控制的引导 ROM。但是,我们有一个在 ARM 上运行的 Linux 上编写的程序,而 ARM 是带有该 DSP 的微型板载以太网网络上唯一的东西。它能够使用常规的 Berkeley 套接字接口接收此数据包(并且该源似乎没有做任何比绑定到 INADDR_ANY 更特别的事情)。当我在同一台设备上运行我的 asio 程序时,它没有收到数据包。这样就排除了底层问题,不是吗?
  • @Steve 除非内核文档声明它支持0.0.0.0旧式广播地址,否则我不会将其作为保证(尽管您可以别无选择)。在使用非连接读取时,您是否观察到相同的行为,例如 socket.async_receive_from() (demo)?将允许连接读取操作过滤掉 invalid 0.0.0.0 源地址。

标签: c++ udp boost-asio


【解决方案1】:

考虑在socket_.cancel() 之后但在下一次调用socket_.async_receive() 之前会发生什么。如果有任何数据到达,则不会为套接字分配“接收处理程序”。当计时器到期时,它会导致 run_once() 被调用 3 次(因为每个 async_wait()expires_at() 和其他一些会导致取消已分配的处理程序并导致已分配的处理程序被发布到运行使用错误代码 operation_aborted 排队。

你没有提到你的超时设置。您基于代码的示例(来自 Boost 文档的示例)将超时设置为 10 秒,但您可以将其设置为可配置的。如果超时太紧,这将导致旋转(post->cancel previous->call previous handler->post->etc.)并可能在接收数据包时调用socket_.cancel()。如果是这种情况,您就不必用广播来测试它了。您也可以通过点对点连接看到它。

编辑:在使用长超时(4000 毫秒)和您的确切代码时,我能够接收到发送的广播。我不得不安装“传统的”netcat,因为 BSD netcat 坏了。但是下面这行有效

echo "hello world" | nc.traditional -b -u 192.168.XXX.255 1500

XXX.255 并不是字面意义上的“XXX.255”。这是我的本地广播地址。 nc.bsd 中的 -b 选项已损坏(如果您使用上述选项 strace nc.bsd,您可以了解原因)。

unix stackexchange nc.traditional 有一个很好的例子,说明其他人如何弄清楚为什么 nc.bsd 不能进行 UDP 广播(-b 选项什么都不做)。

PackEth 无法发送广播或 p-t-p 流量,所以我不会用它来衡量您是否可以发送广播流量。诚然,我对它没有太多经验,所以我不知道它是坏了还是我没有正确配置它。

【讨论】:

  • 我认为这种类型的循环是不可能的,即使存在紧张或即时超时。如果数据到达并且没有挂起的读取操作,那么数据报仍将在内核中排队。如果数据可用,则在启动 async_receive() 操作中将发生读取,导致完成处理程序成功发布,此时读取操作不再可取消。
  • @Tanner Sansbury,你是对的。我已经尝试了许多方案,它们都点对点地工作,并且由于广播地址而失败。我尝试在绑定之前和之后设置广播选项。似乎没有什么可以开始工作。顺便说一句,我在 Linux Mint 上使用 boost 49 执行此操作。我想我会提到这一点,因为每个人似乎都同意的唯一一件事是广播路由在 Linux 和 Windows 上的工作方式不同。哦,我把它切换到 async_receive_from()。没有帮助。虽然这可能是 PackETH 的问题。在 nc -u 工作时发送 p-t-p 时,我看不到数据包到达。
猜你喜欢
  • 1970-01-01
  • 2019-02-27
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多