为什么boost :: asio :: async_write导致崩溃?

时间:2013-08-30 16:44:03

标签: c++ boost boost-asio

我的服务器运行正常,直到客户端连接然后它尝试向客户端发送消息。这是向客户端发送消息的功能。当此代码运行时,它会因错误

而崩溃

SERVER.exe中0x6351117C(msvcr110d.dll)的未处理异常:0xC0000005:访问冲突读取位置0x00000002。

    template <typename T, typename Handler>
    void AsyncWrite(const T& t, Handler handler)
    {
        std::ostringstream archiveStream;
        boost::archive::text_oarchive archive(archiveStream);
        archive << t;
        outboundData = archiveStream.str();

        std::ostringstream headerStream;
        headerStream << std::setw(headerLength) << std::hex << outboundData.size();
        if (!headerStream || headerStream.str().size() != headerLength)
        {
            boost::system::error_code error(boost::asio::error::invalid_argument);
            socket.get_io_service().post(boost::bind(handler, error));
            return;
        }
        outboundHeader = headerStream.str();

        std::vector<boost::asio::const_buffer> buffers;
        buffers.push_back(boost::asio::buffer(outboundHeader));
        buffers.push_back(boost::asio::buffer(outboundData));
        boost::asio::async_write(socket, buffers, handler);
    }

编辑:不知道这是否重要,但是我遵循这个例子

http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/example/cpp03/serialization/connection.hpp

2 个答案:

答案 0 :(得分:2)

这充满了危险:

std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(outboundHeader));
buffers.push_back(boost::asio::buffer(outboundData));
boost::asio::async_write(socket, buffers, handler);

由于buffers是具有自动存储持续时间的本地对象,因此在async_write()完成之前它将超出范围。仅此一点不应成为问题(必要时会复制此对象)。

但是,您的实际数据(保存在outboundHeaderoutboundData)也来自具有自动存储持续时间的本地对象,因此在async_write()完成之前它们也会超出范围

您必须确保传递到async_*函数的对象的生命周期延伸到调用处理程序的位置。

这是一种可能的解决方法,但它要求更改Handler个对象以接受数据(在您的情况下已编写)作为参数。

// new format of the handler
void Handler(std::shared_ptr<OutboundData> written);

struct OutboundData
{
   std::string header;
   std::string data;
};

// guarantee the lifetime of the OutboundData block
auto outbound_data = std::make_shared<OutboundData>();

// copy the contents (to send) in...
outbound_data->header = outboundHeader;
outbound_data->data = outboundData;

std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(outbound_data->header));
buffers.push_back(boost::asio::buffer(outbound_data->header));

// send the data, handler(outbound_data) will be called upon 
// completion (success or failure)
boost::asio::async_write(socket, buffers, boost::bind(handler, outbound_data));

另请注意,为了保持稳健性,通常您的处理程序应接受可能的错误条件,因此在发生故障时可以采取适当的措施。这看起来像这样:

void Handler(
  const boost::system::error_code& err,    // The error code
  std::shared_ptr<OutboundData> written);   // the data written (or not)

// calling async_write, while binding the appropriate parameter(s)
boost::asio::async_write(
  socket,
  buffers,
  boost::bind(handler, outbound_data, boost::asio::placeholders::error);

答案 1 :(得分:1)

验证包含outboundDataoutboundHeader的对象的生命周期是否超过async_write操作的生命周期。

这是在关联的server.cpp示例中完成的,方法是通过connection管理shared_ptr,并将shared_ptr绑定到处理程序。以下是代码的相关摘录:

/// Constructor opens the acceptor and starts waiting for the first incoming
/// connection.
server(...)
  : acceptor_(...)
{
  // Start an accept operation for a new connection.
  connection_ptr new_conn(new connection(acceptor_.get_io_service()));
  acceptor_.async_accept(new_conn->socket(),
      boost::bind(&server::handle_accept, this,
        boost::asio::placeholders::error, new_conn));
}  

/// Handle completion of a accept operation.
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
  if (!e)
  {
    // Successfully accepted a new connection. Send the list of stocks to the
    // client. The connection::async_write() function will automatically
    // serialize the data structure for us.
    conn->async_write(...,
        boost::bind(&server::handle_write, this,
          boost::asio::placeholders::error, conn));
  }
  ...
}

/// Handle completion of a write operation.
void handle_write(const boost::system::error_code& e, connection_ptr conn)
{
  // Nothing to do. The socket will be closed automatically when the last
  // reference to the connection object goes away.
}

connection包含outboundDataoutboundHeader,由shared_ptr构造函数中的server创建和管理。然后shared_ptr绑定到server::handle_accept()async_accept的处理程序。在server::handle_accept()内,连接绑定到server::handle_write()connection::async_write()的处理程序。尽管server::handle_write()什么都不做,但它在链中是至关重要的,因为它通过绑定参数使connection对象保持活着。


有人可能会争辩说,如果connection保证其生命周期超过async_write操作而不对呼叫者施加要求,那么它就不那么具有侵入性了。一个常见的惯用解决方案是connection继承自enable_shared_from_this。当一个类继承自enable_shared_from_this时,它会提供一个shared_from_this()成员函数,该函数会将有效的shared_ptr实例返回给this

以下是基于Boost.Asio serialization示例中serverconnection个对象的完整示例。

#include <string>

#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/bind/protect.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>

class connection
  : public boost::enable_shared_from_this<connection>
{
public:

  /// @brief Constructor.
  connection(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
    std::cout << "connection(): " << this << std::endl;
  }

  ~connection()
  {
    std::cout << "~connection(): " << this << std::endl;
  }

  /// @brief Get the underlying socket. Used for making a connection 
  ///        or for accepting an incoming connection.
  boost::asio::ip::tcp::socket& socket()
  {
    return socket_;
  } 

  /// @brief Asynchronously write data to the connection, invoking
  ///        handler upon completion or failure.
  template <typename Handler>
  void async_write(std::string data, Handler handler)
  {
    // Perform processing on data and copy to member variables.
    using std::swap;
    swap(data_, data);

    // Create a buffer sequence.
    boost::array<boost::asio::const_buffer, 1> buffers = {{
      boost::asio::buffer(data_)
    }};

    std::cout << "connection::async_write() " << this << std::endl;

    // Write to the socket.
    boost::asio::async_write(
        socket_,
        buffers, // Buffer sequence copied, not the underlying buffers.
        boost::bind(&connection::handle_write<Handler>,
          shared_from_this(), // Keep connection alive throughout operation.
          boost::asio::placeholders::error,
          handler));
  }

private:

  /// @brief Invokes user provided handler.  This member function
  ///        allows for the connection object's lifespan to be
  ///        extended during the binding process.
  template <typename Handler>
  void handle_write(const boost::system::error_code& error,
                    Handler handler)
  {
    std::cout << "connection::handle_write() " << this << std::endl;
    handler(error);
  }

private:
  boost::asio::ip::tcp::socket socket_;
  std::string data_;
};

class server
{
public:
  /// @brief Constructor opens an acceptor, waiting for incoming connection.
  server(boost::asio::io_service& io_service,
         unsigned short port)
    : acceptor_(io_service,
        boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
  {
    start_accept();
  }

private:

  /// @brief Start an accept operation for a new connection.
  void start_accept()
  {
    boost::shared_ptr<connection> new_conn =
        boost::make_shared<connection>(
          boost::ref(acceptor_.get_io_service()));
    acceptor_.async_accept(new_conn->socket(),
        boost::bind(&server::handle_accept, this,
          boost::asio::placeholders::error, new_conn));
  }

  /// @brief Handle completion of a accept operation.
  void handle_accept(const boost::system::error_code& error,
                     boost::shared_ptr<connection> conn)
  {
    if (!error)
    {
      // Successfully accepted a new connection. Write data to it.
      conn->async_write("test data",
          boost::protect(
            boost::bind(&server::handle_write, this,
              boost::asio::placeholders::error)));
    }

    // Start accepting another connection.
    start_accept();
  }

  void handle_write(const boost::system::error_code& error)
  {
    std::cout << "server::handle_write()" << std::endl;
  }

private:
  /// The acceptor object used to accept incoming socket connections.
  boost::asio::ip::tcp::acceptor acceptor_;

};

int main(int argc, char* argv[])
{
  try
  {
    // Check command line arguments.
    if (argc != 2)
    {
      std::cerr << "Usage: server <port>" << std::endl;
      return 1;
    }
    unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);

    boost::asio::io_service io_service;
    server server(io_service, port);
    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }
}

运行程序,并从另一个终端连接导致以下输出:

connection(): 0x8cac18c
connection::async_write() 0x8cac18c
connection(): 0x8cac1e4
connection::handle_write() 0x8cac18c
server::handle_write()
~connection(): 0x8cac18c

注意connection对象的生命周期至少延伸到async_write操作的生命周期。修改后的API允许server无需管理connection,因为对象将自行管理。请注意,由于嵌套boost::protect,因此需要boost::bind。除此之外,还有一些替代方法不会给调用者带来负担,例如将绑定的处理程序打包在元组中,就像在Boost.Asio示例中的connection::async_read()中所做的那样。