为什么这个程序没有收到预期的UDP数据包?

时间:2015-10-03 00:54:13

标签: c++ udp boost-asio

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

我正在尝试从C6655 TI DSP接收类似BOOTP的UDP数据包,这些数据包以3秒的间隔传输。我有Wireshark看着我的程序正在监听的相同接口,它可以看到数据包到达(参见下面的确切数据包数据,从Wireshark导出)。数据包不是真正来自DSP,我使用tcpdump捕获了一个,我正在使用packeth从Raspberry Pi模拟它。

但是,我的程序没有收到数据包。它有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                                ......

我有一个可以接收此数据包的Berkeley套接字实现(我已经删除了错误处理和其他错误代码):

{
    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);
}

1 个答案:

答案 0 :(得分:3)

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

您尚未提及超时设置的内容。基于代码的示例(来自Boost文档的示例)将超时设置为10秒,但您可以将其配置为可配置。如果超时太紧,这将导致旋转(后&gt;取消前一个&gt;调用前一个处理程序 - &gt; post-&gt;等)并且可能在接收数据包时调用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选项被破坏了(你可以看看为什么你用上面的选项划分nc.bsd)。

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

PackEth无法发送广播或p-t-p流量,因此我不会将其用作衡量您是否可以发送广播流量的指标。不可否认,我对它没有多少经验,所以我不知道它是否已损坏,或者我是否配置不正确。