【问题标题】:set_option: Invalid argument when setting option boost::asio::ip::multicast::join_group inside lambdaset_option:在 lambda 中设置选项 boost::asio::ip::multicast::join_group 时参数无效
【发布时间】:2015-04-29 01:27:34
【问题描述】:

此代码旨在使用 Boost.Asio 接收 UDP 多播消息。当在接收器的构造函数中进行第二次 set_option() 调用(加入多播组)时,下面的代码会引发 Boost system_error 异常。投诉是“无效的论点”。这似乎与构造函数发生在 IO::doIO() 内定义的 lambda 内的事实有关,因为使用具有相同功能 (IO::threadFunc()) 的 std::thread 成员反而会导致预期行为(不抛出异常)。

为什么会这样,我该如何解决它以便我可以使用 lambda?

//g++ -std=c++11 doesntWork.cc -lboost_system -lpthread

#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

class IO
{
public:
   class receiver
   {
   public:
      receiver(
         boost::asio::io_service         &io_service,
         const boost::asio::ip::address  &multicast_address,
         const unsigned short             portNumber) : _socket(io_service)
      {
         const boost::asio::ip::udp::endpoint listen_endpoint(
            boost::asio::ip::address::from_string("0.0.0.0"), portNumber);

         _socket.open(listen_endpoint.protocol());
         _socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
         _socket.bind(listen_endpoint);
std::cerr << " About to set option join_group" << std::endl;
         _socket.set_option(boost::asio::ip::multicast::join_group(
            multicast_address));

         _socket.async_receive_from(
            boost::asio::buffer(_data),
            _sender_endpoint,
            boost::bind(&receiver::handle_receive_from, this,
               boost::asio::placeholders::error,
               boost::asio::placeholders::bytes_transferred));
      }

   private:
      void handle_receive_from(
         const boost::system::error_code &error,
         const size_t                     bytes_recvd)
      {
         if (!error)
         {
            for(const auto &c : _data)
               std::cout << c;
            std::cout << std::endl;
         }
      }

   private:
      boost::asio::ip::udp::socket   _socket;
      boost::asio::ip::udp::endpoint _sender_endpoint;
      std::vector<unsigned char> _data;
   }; // receiver class


   void doIO()
   {
      const boost::asio::ip::address multicast_address =
         boost::asio::ip::address::from_string("235.0.0.1");

      const unsigned short portNumber = 9999;

//       _io_service_thread = std::thread(
//          &IO::threadFunc, this, multicast_address, portNumber);

      _io_service_thread = std::thread([&, this]{
         try {
            // Construct an asynchronous receiver
            receiver r(_io_service, multicast_address, portNumber);

            // Now run the IO service
            _io_service.run();
         }
         catch(const boost::system::system_error &e)
         {
            std::cerr << e.what() << std::endl;
            throw e; //std::terminate()
         }
      });
   }

   void threadFunc(
      const boost::asio::ip::address &multicast_address,
      const unsigned short portNumber)
   {
      try {
         // Construct an asynchronous receiver
         receiver r(_io_service, multicast_address, portNumber);

         // Now run the IO service
         _io_service.run();
      }
      catch(const boost::system::system_error &e)
      {
         std::cerr << e.what() << std::endl;
         throw e; //std::terminate()
      }
   }

private:
   boost::asio::io_service _io_service;
   std::thread             _io_service_thread;
}; // IO class

int main()
{
   IO io;
   io.doIO();

   std::cout << "IO Service is running" << std::endl;

   sleep(9999);
}

【问题讨论】:

    标签: c++11 boost boost-asio multicast


    【解决方案1】:

    存在可能导致访问悬空引用、调用未定义行为的竞争条件。 lambda 捕获列表通过引用捕获自动变量multicast_addressportNumber。但是,这些对象的生命周期可能会在 _io_service_thread 中使用之前结束:

    void doIO()
    {
      const boost::asio::ip::address multicast_address = /* ... */;
      const unsigned short portNumber = /* ... */;
    
      _io_service_thread = std::thread([&, this] {
        // multicast_address and portNumber's lifetime may have already ended.
        receiver r(_io_service, multicast_address, portNumber);
        // ...
      });
    } // multicast_address and portNumber are destroyed.
    

    要解决此问题,请考虑按值捕获,以便线程操作其生命周期将保持有效直到线程结束的副本。变化:

    std::thread([&, this] { /* ... */ }
    

    到:

    std::thread([=] { /* ... */ }
    

    std::thread 使用函数及其所有参数构造时,此问题不会出现,因为std::thread 构造函数会将所有提供的参数复制/移动到线程可访问的存储中。

    另外,请注意_io_service_thread 对象的销毁将调用std::terminate(),如果它在IO 的析构函数中仍然是可连接的。为避免此行为,请考虑从主线程显式加入 _io_service_thread

    【讨论】:

    • 太棒了。谢谢你。实际代码确实在 IO 类的析构函数中包含 joinable() 检查和 join()。
    猜你喜欢
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 2016-05-11
    • 2014-07-11
    • 1970-01-01
    • 2014-06-01
    • 2018-06-08
    • 1970-01-01
    相关资源
    最近更新 更多