Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/155.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ boost::asio UDP广播客户端仅接收;“快速”;小包_C++_Boost_Udp_Broadcast_Asio - Fatal编程技术网

C++ boost::asio UDP广播客户端仅接收;“快速”;小包

C++ boost::asio UDP广播客户端仅接收;“快速”;小包,c++,boost,udp,broadcast,asio,C++,Boost,Udp,Broadcast,Asio,我已经使用boost::asio编写了一个UDP广播客户端。它是有效的,但有一个警告。如果我非常快地发送数据包(至少每100ms左右发送一个),它似乎会接收所有数据包。然而,如果我只发送一个数据包,它似乎无法捕获它。我使用的是异步接收,所以我无法想象它为什么不工作。数据本身相当小,并且总是小于分配的缓冲区大小。当它确实收到“快速”数据包时,它们是正确的,并且只包含来自单个“发送”的数据。在调试器中,每个发送的数据包都会正确地中断一次 标题: class BroadcastClient {

我已经使用boost::asio编写了一个UDP广播客户端。它是有效的,但有一个警告。如果我非常快地发送数据包(至少每100ms左右发送一个),它似乎会接收所有数据包。然而,如果我只发送一个数据包,它似乎无法捕获它。我使用的是异步接收,所以我无法想象它为什么不工作。数据本身相当小,并且总是小于分配的缓冲区大小。当它确实收到“快速”数据包时,它们是正确的,并且只包含来自单个“发送”的数据。在调试器中,每个发送的数据包都会正确地中断一次

标题:

class BroadcastClient
    {
    public:
        BroadcastClient();
        std::optional<std::string> poll();

    protected:
        void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred);

    private:
        std::future<void> ioFuture;
        std::vector<uint8_t> buffer;
        std::string result;
        boost::asio::io_service ioService;
        std::unique_ptr<boost::asio::ip::udp::socket> socket;
        uint16_t port{ 8888 };
        boost::asio::ip::udp::endpoint sender_endpoint;
    };
类广播客户端
{
公众:
广播客户端();
std::可选轮询();
受保护的:
无效句柄读取(const boost::system::error\u code&error,std::size\u t字节传输);
私人:
std:未来与未来;
向量缓冲区;
std::字符串结果;
boost::asio::io_服务ioService;
std::唯一的ptr插座;
uint16_t端口{8888};
boost::asio::ip::udp::endpoint sender\u endpoint;
};
实施:

BroadcastClient::BroadcastClient()
{
    this->socket = std::make_unique<boost::asio::ip::udp::socket>(
        this->ioService, boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), this->port));

    this->socket->set_option(boost::asio::socket_base::broadcast(true));
    this->socket->set_option(boost::asio::socket_base::reuse_address(true));

    this->ioFuture = std::async(std::launch::async, [this] { this->ioService.run(); });
    this->buffer.resize(4096);

    this->socket->async_receive_from(
        boost::asio::buffer(this->buffer, this->buffer.size()), sender_endpoint,
        boost::bind(&BroadcastClient::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}

void BroadcastClient::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    if(!error)
    {
        this->result += std::string(std::begin(buffer), std::begin(buffer) + buffer.size());
        std::fill(std::begin(buffer), std::end(buffer), 0);
        
        this->socket->async_receive_from(
            boost::asio::buffer(this->buffer, this->buffer.size()), sender_endpoint,
            boost::bind(&BroadcastClient::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
    }
}

std::optional<std::string> BroadcastClient::poll()
{
    if(this->result.empty() == false)
    {
        auto copy = this->result;
        this->result.clear();
        return copy;
    }

    return {};
}

BroadcastClient::BroadcastClient()
{
此->套接字=std::使_唯一(
this->ioService,boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(),this->port));
这个->套接字->设置_选项(boost::asio::socket_base::broadcast(true));
这个->套接字->设置_选项(boost::asio::socket_base::reuse_address(true));
this->ioFuture=std::async(std::launch::async,[this]{this->ioService.run();});
这->缓冲区大小调整(4096);
此->套接字->异步\u从接收\u(
boost::asio::buffer(this->buffer,this->buffer.size()),发送方\端点,
boost::bind(&BroadcastClient::handle_read,this,boost::asio::placeholders::error,boost::asio::placeholders::bytes_transfer));
}
void BroadcastClient::handle\u read(const boost::system::error\u code&error,std::size\u t bytes\u transfer)
{
如果(!错误)
{
这->结果+=std::string(std::begin(buffer),std::begin(buffer)+buffer.size());
std::fill(std::begin(缓冲区),std::end(缓冲区),0);
此->套接字->异步\u从接收\u(
boost::asio::buffer(this->buffer,this->buffer.size()),发送方\端点,
boost::bind(&BroadcastClient::handle_read,this,boost::asio::placeholders::error,boost::asio::placeholders::bytes_transfer));
}
}
std::可选广播客户端::轮询()
{
if(this->result.empty()==false)
{
自动复制=此->结果;
这->result.clear();
返回副本;
}
返回{};
}

我搜索了很长时间,因为广播UDP可能很挑剔。然后我发现了你的未来。我不仅不相信
std::async
会做你期望的事情(它几乎可以做任何事情),而且还有一个潜在的致命竞争,这99%肯定是你的问题:

  • 启动异步任务-它将在将来某个时间启动/

  • 然后添加
    async\u receive\u from
    操作。如果任务已经启动,队列将是空的,
    run()。事实上,当您:

     ioService.run();
     std::clog << "End of run " << std::boolalpha << ioService.stopped() << std::endl;
    
    我大部分时间都是这样。我建议使用线程:

    ioThread = std::thread([this] {
        ioService.run();
        std::clog << "End of run " << std::boolalpha << ioService.stopped() << std::endl;
    });
    
    完整地说,还要处理异常:或者使用
    thread\u pool(1)
    ,这很好,因为它还替换了您的
    io\u服务

    或者,使用工作防护装置(或)

    现在,我似乎不能让它在本地测试时丢失数据包

    更多评论
  • 一般来说,您希望更早地知道代码中何时出现错误情况,因此请在
    handle\u read
    中报告
    error
    ,因为这种情况会导致异步循环终止。更多固定的
    handle\u read

  • 结果
    缓冲区不是线程安全的,您可以从多个线程访问它。这引起了我的注意。添加同步,或使用原子交换等

    CharStyle为了确保
    轮询
    发生在服务线程上,您必须
    将轮询操作发布到io_服务。这是不可能的,因为服务是私有的

  • 在句柄读取中使用
    buffer.size()
    ,但这是硬编码的(4096)。您可能想要传输的
    字节

    result.append(std::begin(buffer), std::begin(buffer) + bytes_transferred);
    
    同时也避免了不必要的临时事件。此外,现在不必将缓冲区重置为零:

    void BroadcastClient::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
        if (!error) {
            std::lock_guard lk(result_mx);
            result.append(std::begin(buffer), std::begin(buffer) + bytes_transferred);
    
            start_read();
        } else {
            std::clog << "handle_read: " << error.message() << std::endl;
        }
    }
    
  • 有来自
  • 调用的
    async\u receive\u的重复。这需要一个
    start\u read
    或类似的方法。此外,考虑使用lambda来减少代码,而不依赖于旧式的<代码> Booost::绑定< /代码>:

    void BroadcastClient::start_read() {
        socket.async_receive_from(
            boost::asio::buffer(buffer), sender_endpoint,
            [this](auto ec, size_t xfr) { handle_read(ec, xfr); });
    }
    
  • 完整列表

    #include <boost/asio.hpp>
    #include <iostream>
    #include <iomanip>
    #include <thread>
    #include <mutex>
    using namespace std::chrono_literals;
    
    class BroadcastClient {
        using socket_base = boost::asio::socket_base;
        using udp = boost::asio::ip::udp;
      public:
        BroadcastClient();
    
        ~BroadcastClient() {
            std::clog << "~BroadcastClient()" << std::endl;
            socket.cancel();
            work.reset();
            ioThread.join();
        }
        std::optional<std::string> poll();
    
      protected:
        void start_read();
        void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred);
    
      private:
        uint16_t port{ 8888 };
        boost::asio::io_service ioService;
        boost::asio::executor_work_guard<
            boost::asio::io_service::executor_type> work { ioService.get_executor() };
        udp::socket socket { ioService, { {}, port } };
    
        std::thread ioThread;
        std::string buffer = std::string(4096, '\0');
        std::mutex result_mx;
        std::string result;
        udp::endpoint sender_endpoint;
    };
    
    BroadcastClient::BroadcastClient() {
        socket.set_option(socket_base::broadcast(true));
        socket.set_option(socket_base::reuse_address(true));
    
        ioThread = std::thread([this] {
            ioService.run();
            std::clog << "Service thread, stopped? " << std::boolalpha << ioService.stopped() << std::endl;
        });
    
        start_read(); // actually okay now because of `work` guard
    }
    
    void BroadcastClient::start_read() {
        socket.async_receive_from(
            boost::asio::buffer(buffer), sender_endpoint,
            [this](auto ec, size_t xfr) { handle_read(ec, xfr); });
    }
    
    void BroadcastClient::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
        if (!error) {
            std::lock_guard lk(result_mx);
            result.append(std::begin(buffer), std::begin(buffer) + bytes_transferred);
    
            start_read();
        } else {
            std::clog << "handle_read: " << error.message() << std::endl;
        }
    }
    
    std::optional<std::string> BroadcastClient::poll() {
        std::lock_guard lk(result_mx);
        if (result.empty())
            return std::nullopt;
        else 
            return std::move(result);
    }
    
    constexpr auto now = std::chrono::steady_clock::now;
    
    int main() {
        BroadcastClient bcc;
    
        for (auto start = now(); now() - start < 3s;) {
            if (auto r = bcc.poll())
                std::cout << std::quoted(r.value()) << std::endl;
    
            std::this_thread::sleep_for(100ms);
        }
    } // BroadcastClient destructor safely cancels the work
    
    印刷品

    "hello world 18422"
    "hello world 3810"
    "hello world 26191hello world 10419"
    "hello world 23666hello world 18552"
    "hello world 2076"
    "hello world 19871hello world 8978"
    "hello world 1836"
    "hello world 11134hello world 16603"
    "hello world 3748hello world 8089"
    "hello world 27946"
    "hello world 14834hello world 15274"
    "hello world 26555hello world 6695"
    "hello world 32419"
    "hello world 26996hello world 26796"
    "hello world 9882"
    "hello world 680hello world 29358"
    "hello world 9723hello world 31163"
    "hello world 3646"
    "hello world 10602hello world 22562"
    "hello world 18394hello world 17229"
    "hello world 20028"
    "hello world 14444hello world 3890"
    "hello world 16258"
    "hello world 28555hello world 21184"
    "hello world 31342hello world 30891"
    "hello world 3088"
    "hello world 1051hello world 5638"
    "hello world 24308hello world 7748"
    "hello world 18398"
    ~BroadcastClient()
    handle_read: Operation canceled
    Service thread, stopped? true
    

    可能/仍然/感兴趣的旧答案内容

    等等。我注意到这不是“常规的”点对点UDP

    据我所知,多播是由路由器提供的。他们必须维护复杂的“订阅”端点表,以便知道在哪里转发实际数据包

    许多路由器都在与这些问题作斗争,在可靠性方面存在着固有的缺陷,尤其是在WiFi等方面。如果您有一个路由器(或者更确切地说是一个包含路由器的拓扑结构)也在与这些问题作斗争,并且只是在某个时间间隔停止“记住”多播组中的参与端点,我将/不会/感到惊讶

    我认为这种类型的表必须保存在路由的每个跃点中(包括内核,它可能必须跟踪同一个多播组的多个进程)

    关于这方面的一些提示:

    一条经常听到的建议是:

    • 如果可以,使用多播进行dicscovery,然后切换到单播
    • 尝试具体说明绑定接口(在代码中,您可能希望用以下内容替换address_v4::any()<
      void BroadcastClient::start_read() {
          socket.async_receive_from(
              boost::asio::buffer(buffer), sender_endpoint,
              [this](auto ec, size_t xfr) { handle_read(ec, xfr); });
      }
      
      #include <boost/asio.hpp>
      #include <iostream>
      #include <iomanip>
      #include <thread>
      #include <mutex>
      using namespace std::chrono_literals;
      
      class BroadcastClient {
          using socket_base = boost::asio::socket_base;
          using udp = boost::asio::ip::udp;
        public:
          BroadcastClient();
      
          ~BroadcastClient() {
              std::clog << "~BroadcastClient()" << std::endl;
              socket.cancel();
              work.reset();
              ioThread.join();
          }
          std::optional<std::string> poll();
      
        protected:
          void start_read();
          void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred);
      
        private:
          uint16_t port{ 8888 };
          boost::asio::io_service ioService;
          boost::asio::executor_work_guard<
              boost::asio::io_service::executor_type> work { ioService.get_executor() };
          udp::socket socket { ioService, { {}, port } };
      
          std::thread ioThread;
          std::string buffer = std::string(4096, '\0');
          std::mutex result_mx;
          std::string result;
          udp::endpoint sender_endpoint;
      };
      
      BroadcastClient::BroadcastClient() {
          socket.set_option(socket_base::broadcast(true));
          socket.set_option(socket_base::reuse_address(true));
      
          ioThread = std::thread([this] {
              ioService.run();
              std::clog << "Service thread, stopped? " << std::boolalpha << ioService.stopped() << std::endl;
          });
      
          start_read(); // actually okay now because of `work` guard
      }
      
      void BroadcastClient::start_read() {
          socket.async_receive_from(
              boost::asio::buffer(buffer), sender_endpoint,
              [this](auto ec, size_t xfr) { handle_read(ec, xfr); });
      }
      
      void BroadcastClient::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
          if (!error) {
              std::lock_guard lk(result_mx);
              result.append(std::begin(buffer), std::begin(buffer) + bytes_transferred);
      
              start_read();
          } else {
              std::clog << "handle_read: " << error.message() << std::endl;
          }
      }
      
      std::optional<std::string> BroadcastClient::poll() {
          std::lock_guard lk(result_mx);
          if (result.empty())
              return std::nullopt;
          else 
              return std::move(result);
      }
      
      constexpr auto now = std::chrono::steady_clock::now;
      
      int main() {
          BroadcastClient bcc;
      
          for (auto start = now(); now() - start < 3s;) {
              if (auto r = bcc.poll())
                  std::cout << std::quoted(r.value()) << std::endl;
      
              std::this_thread::sleep_for(100ms);
          }
      } // BroadcastClient destructor safely cancels the work
      
      g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp
      while sleep .05; do echo -n "hello world $RANDOM" | netcat -w 0 -u 127.0.0.1 8888 ; done&
      ./a.out
      kill %1
      
      "hello world 18422"
      "hello world 3810"
      "hello world 26191hello world 10419"
      "hello world 23666hello world 18552"
      "hello world 2076"
      "hello world 19871hello world 8978"
      "hello world 1836"
      "hello world 11134hello world 16603"
      "hello world 3748hello world 8089"
      "hello world 27946"
      "hello world 14834hello world 15274"
      "hello world 26555hello world 6695"
      "hello world 32419"
      "hello world 26996hello world 26796"
      "hello world 9882"
      "hello world 680hello world 29358"
      "hello world 9723hello world 31163"
      "hello world 3646"
      "hello world 10602hello world 22562"
      "hello world 18394hello world 17229"
      "hello world 20028"
      "hello world 14444hello world 3890"
      "hello world 16258"
      "hello world 28555hello world 21184"
      "hello world 31342hello world 30891"
      "hello world 3088"
      "hello world 1051hello world 5638"
      "hello world 24308hello world 7748"
      "hello world 18398"
      ~BroadcastClient()
      handle_read: Operation canceled
      Service thread, stopped? true