【问题标题】:How do you correctly close sockets in boost::asio?如何正确关闭 boost::asio 中的套接字?
【发布时间】:2016-05-25 09:33:04
【问题描述】:

我正在尝试在 boost::asio 中进行资源管理。我看到在相应的套接字已经被销毁后调用的回调。 boost::asio 官方示例就是一个很好的例子:http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp

我特别关心close方法:

void close()
{
   io_service_.post([this]() { socket_.close(); });
}

如果你调用了这个函数,然后破坏了持有socket_的chat_client实例,socket_将在调用close方法之前被破坏。在 chat_client 被销毁后,任何挂起的 async_* 回调都可以被调用。

你将如何正确处理这个问题?

【问题讨论】:

  • 致你的now-deleted comment “关闭套接字绝对不会导致所有挂起的回调都被错误调用。”:那你会怎么说呢?

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


【解决方案1】:

您几乎可以在任何时候使用socket_.close();,但您应该记住一些时刻:

  • 如果你有线程,这个调用应该用 strand 包裹,否则你可能会崩溃。见boost strand documentation
  • 无论何时close 请记住 io_service 已经可以有排队处理程序。无论如何都会使用旧的状态/错误代码调用它们。
  • close 可以抛出异常。
  • close 不包括 ip::tcp::socket 破坏。它 只是关闭系统套接字。
  • 您必须管理对象生命周期 自己确保只有在没有对象的情况下才会销毁对象 更多的处理程序。通常这是通过 enable_shared_from_this 完成的 在您的 Connectionsocket 对象上。

【讨论】:

  • 太复杂了,普通普通程序员应该避免关闭boost asio socket,最好不要打开。
【解决方案2】:

调用socket.close() 不会破坏套接字。但是,应用程序可能需要管理操作和完成处理程序所依赖的对象的生命周期,但这不一定是套接字对象本身。例如,考虑一个client 类,它拥有一个缓冲区、一个套接字,并且有一个未完成的读取操作,完成处理程序为client::handle_read()。可以close() 并显式销毁套接字,但缓冲区和client 实例必须保持有效,直到至少调用处理程序:

class client
{
  ...

  void read()
  {
    // Post handler that will start a read operation.
    io_service_.post([this]() {
      async_read(*socket, boost::asio::buffer(buffer_);
        boost::bind(&client::handle_read, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
    });
  }

  void handle_read(
    const boost::system::error_code& error,
    std::size_t bytes_transferred
  )
  {
    // make use of data members...if socket_ is not used, then it
    // is safe for socket to have already been destroyed.
  }

  void close()
  {
    io_service_.post([this]() {
      socket_->close();
      // As long as outstanding completion handlers do not
      // invoke operations on socket_, then socket_ can be 
      // destroyed.
      socket_.release(nullptr);
    });
  }

private:
  boost::asio::io_service& io_service_;

  // Not a typical pattern, but used to exemplify that outstanding
  // operations on `socket_` are not explicitly dependent on the 
  // lifetime of `socket_`.
  std::unique_ptr<boost::asio::socket> socket_;
  std::array<char, 512> buffer_;
  ...
}

应用程序负责管理操作和处理程序所依赖的对象的生命周期。 chat client example 通过等待 io_service.run() 在线程 join() 中返回,保证 chat_client 实例在其不再使用之后被销毁,从而实现了这一点:

int main(...)
{
  try
  {
    ...

    boost::asio::io_service io_service;
    chat_client c(...);

    std::thread t([&io_service](){ io_service.run(); });

    ...

    c.close();
    t.join(); // Wait for `io_service.run` to return, guaranteeing
              // that `chat_client` is no longer in use.
  }           // The `chat_client` instance is destroyed.
  catch (std::exception& e)
  {
    ...
  }
}

管理对象生命周期的一个常见习惯是让 I/O 对象由继承自 enable_shared_from_this&lt;&gt; 的单个类管理。当一个类从enable_shared_from_this 继承时,它提供一个shared_from_this() 成员函数,该函数返回一个有效的shared_ptr 实例来管理thisshared_ptr 的副本被传递给完成处理程序,例如 lambdas 中的捕获列表或作为实例句柄传递给 bind(),导致 I/O 对象的生命周期至少延长到处理程序。有关使用此方法的示例,请参阅 Boost.Asio asynchronous TCP daytime server 教程。

【讨论】:

  • enable_shred_from_this&lt;&gt; example 他们没有显式调用socket_.close() 方法。假设套接字关闭是自动处理的是否安全?
猜你喜欢
  • 2016-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 2011-12-05
  • 2019-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多