0

我围绕 Boostless asio 编写了一个包装器来处理所有网络通信。大多数交换涉及在客户端和服务器之间来回发送多个数据包。我现在有一个问题,如果我嵌套对 asio 方法的调用,我最终会出现在方法中__throw_bad_function_call();

这是我的听众:

.hpp

/// \brief Socket object used to manage connections to the remote server.
    struct listener : public std::enable_shared_from_this<listener> {
      /// \brief Creates an instance of the object and opens a connection to the remote host.
      /// \param[in] params Connection parameters.
      /// \param[out] ec Errors returned by the OS.
      explicit listener(const parameters &params, std::error_code &ec) noexcept;

      /// \brief Executes all queued handlers and resets the io_context for another run.
      auto execute_handlers() noexcept -> void;

      /// \brief Writes data to the socket.
      /// \details Writes data to the socket and passes any errors to the callback.
      /// \param[in] barray Byte array to send to the server.
      /// \param[in] callback Method called when data has been written to the server.
      auto write(packet barray, std::function<void(std::error_code ec, std::size_t length)> callback) noexcept -> void;

      /// \brief Reads data from the socket.
      /// \details Reads that present in the socket and passes the data as a single byte array to the callback.
      /// \param[in] callback Method called when data has been written to the server.
      auto read(std::function<void(std::error_code ec, packet barray)> callback) noexcept -> void;

     private:
      /// \brief Underlying OS socket.
      std::unique_ptr<asio::generic::stream_protocol::socket> m_socket;

      asio::io_context m_context;
      asio::local::stream_protocol::endpoint m_endpoint;

      /// \brief Connection parameters
      parameters m_params;

      /// \brief DNS resolver
      asio::ip::tcp::resolver m_resolver;

      /// \brief Buffer that holds data to be received from the socket. Reset and resized before each call to read().
      packet m_buffer;
    };

.cpp

    listener::listener(const parameters &params, std::error_code &ec) noexcept
        : m_endpoint{params.domain_socket}, m_params{params}, m_resolver{m_context} {
#if !defined(_WIN32) && !defined(_WIN64)
      if (!m_params.domain_socket.empty()) {
        m_socket.reset(new asio::generic::stream_protocol::socket(m_context));
        m_socket->connect(m_endpoint, ec);
        if (ec) {
          return;
        }
        m_socket->non_blocking(true);
        return;
      }
#endif

      m_resolver.async_resolve(m_params.host, std::to_string(m_params.port),
                               [&](const std::error_code &error, asio::ip::tcp::resolver::results_type results) {
                                 if (error) {
                                   ec = error;
                                   return;
                                 }
                                 //! \todo Add timeout to async_connect
                                 for (const auto &endpoint : results) {
                                   m_socket.reset(new asio::generic::stream_protocol::socket(m_context));
                                   m_socket->async_connect(endpoint.endpoint(), [&](const std::error_code &err_c) {
                                     if (err_c) {
                                       ec = err_c;
                                       return;
                                     }
                                   });
                                 }
                               });
    }

    auto listener::execute_handlers() noexcept -> void {
      const auto handlers = m_context.run();
      m_context.restart();
    }

    auto listener::write(packet barray, std::function<void(std::error_code ec, std::size_t length)> callback) noexcept
        -> void {
      asio::async_write(*m_socket, asio::buffer(barray), callback);
    }

    auto listener::read(std::function<void(std::error_code ec, packet barray)> callback) noexcept -> void {
      packet tmp;
      tmp.resize(1);
      asio::async_read(*m_socket, asio::buffer(tmp), asio::transfer_exactly(1),
                       [&](std::error_code ec, std::size_t size) {
                         if (!ec) {
                           const auto available = m_socket->available();
                           m_buffer.resize(available);
                           m_buffer.shrink_to_fit();
                           asio::async_read(*m_socket, asio::buffer(m_buffer), asio::transfer_all());
                         }
                         callback(ec, std::move(m_buffer));
                       });
    }

然后我开始与远程服务器进行交换,但 lldb 告诉我我们失败了:

auto connection::do_connect(std::function<void(std::error_code ec, sql_state state)> callback) noexcept -> void {
m_listener = std::make_shared<listener>(m_params, ec);
      m_listener->execute_handlers();
      if (ec) {
        callback(ec, state);
      }
        sql_state state;
        const auto startup = write::startup(m_params);
        m_listener->write(startup, [&](std::error_code ec, std::size_t length) {
          if (ec) {
            callback(ec, state);
          }
          m_listener->read([&](std::error_code ec, packet packet){});
        });
        m_listener->execute_handlers();
      }

在 lldb 中运行此命令失败并显示以下跟踪:

Process 82736 launched: '/Users/ruihpacheco/Desktop/databaseclient/build_ninja/tests/integration/integration' (x86_64)
Process 82736 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x0000000100178118 integration`std::__1::function<void (std::__1::error_code, std::__1::vector<nonstd::byte, std::__1::allocator<nonstd::byte> >)>::operator(this=0x00007ffeefbfd780, __arg=(__val_ = 0, __cat_ = 0x00007fffa6cd6cd8), __arg=size=329)(std::__1::error_code, std::__1::vector<nonstd::byte, std::__1::allocator<nonstd::byte> >) const at functional:1913
   1910 {
   1911     if (__f_ == 0)
   1912         __throw_bad_function_call();
-> 1913     return (*__f_)(_VSTD::forward<_ArgTypes>(__arg)...);
   1914 }
   1915 
   1916 #ifndef _LIBCPP_NO_RTTI
Target 0: (integration) stopped.

感觉应该可以了。。。

4

1 回答 1

1

看来您的问题是启动异步操作,该操作将缓冲区带到局部变量。

auto listener::write(packet barray, std::function<void(std::error_code ec, std::size_t length)> callback) noexcept
        -> void {
      asio::async_write(*m_socket, asio::buffer(barray), callback);
    }

barray是内部的局部变量writeasync_write立即返回,但asio::buffer()仅将包装器返回到传递的数据(指向数据及其大小的指针),不复制数据。write结束,barray被销毁并async_write获得已删除数据的缓冲区..

auto listener::read(std::function<void(std::error_code ec, packet barray)> callback) noexcept -> void {
      packet tmp;
      tmp.resize(1);
      asio::async_read(*m_socket, asio::buffer(tmp), asio::transfer_exactly(1),
                       [&](std::error_code ec, std::size_t size) {
                         if (!ec) {
                           const auto available = m_socket->available();
                           m_buffer.resize(available);
                           m_buffer.shrink_to_fit();
                           asio::async_read(*m_socket, asio::buffer(m_buffer), asio::transfer_all());
                         }
                         callback(ec, std::move(m_buffer));
                       });

async_read需要buffer(tmp),在哪里tmp是本地的。async_read也立即返回,所以当处理程序被调用时,tmp不存在。

编辑

listener::read关于方法的一些话:

auto listener::read(
   std::function<void(std::error_code ec, packet barray)> callback) noexcept -> void  // [1]
{
  packet tmp;
  tmp.resize(1);
  asio::async_read(*m_socket, asio::buffer(tmp), asio::transfer_exactly(1),
           [&](std::error_code ec, std::size_t size) { // [2]
              if (!ec) {
                 const auto available = m_socket->available();
                 m_buffer.resize(available);
                 m_buffer.shrink_to_fit();
                 asio::async_read(*m_socket, asio::buffer(m_buffer), asio::transfer_all()); // [3]
               }
               callback(ec, std::move(m_buffer)); // [4]
         });
}

[1] 回调按值传递

[2] lambda通过引用捕获所有内容,因此不复制callback,闭包保持对局部变量的引用,在 [4] 中,您调用callback(ec,std::move(m_buffer))的是悬空的闭包,因为async_read立即返回并listener::read结束

[3] 和 [4] 很奇怪,立即返回,你在调用时async_read传递buffer给,返回(正在执行读取异步操作)并调用回调移动(它改变了异步操作使用的对象!),你应该等到处理程序被调用,然后你可以进入回调m_bufferayns_readasync_readm_bufferasync_readm_buffer

主要问题:在我看来,问题在于方法中callback 的引用传递listener::read。当处理程序被调用时,它会callback(..)在悬空引用上调用。尝试callback按值传递:

               [&,callback /*pass by value*/](std::error_code ec, std::size_t size) 
              {
                 if (!ec) 
                 {
                   const auto available = m_socket->available();
                   m_buffer.resize(available);
                   m_buffer.shrink_to_fit();
                   asio::async_read(*m_socket, asio::buffer(m_buffer), asio::transfer_all());
                 }
                 callback(ec, std::move(m_buffer));
               });
于 2019-02-02T17:13:53.883 回答