2013-08-30 2 views
0

Мой сервер работает нормально до тех пор, пока клиент не подключится, а затем попытается отправить сообщение клиенту. Вот функция отправки сообщения клиенту. При запуске этого кода происходит сбой с ошибкойПочему boost :: asio :: async_write вызывает сбой?

Необработанное исключение в 0x6351117C (msvcr110d.dll) в файле SERVER.exe: 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); 
    } 

Edit: Не знаю, если это имеет значение, но им следуя этому примеру

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

ответ

1

Убедитесь, что продолжительность жизни объекта, содержащего outboundData и outboundHeader превышает объем 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, который содержит outboundData и outboundHeader, создается и управляется с помощью 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.

Вот пример, основанный на объектах server и connection в примере Boost.Asio serialization.

#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. Существуют альтернативы этому, которые не помещают нагрузку на вызывающего абонента, например, упаковывают обработчик привязки в кортеж, как это сделано в connection::async_read() в примере Boost.Asio.

+0

Хотя ваш полный пример, основанный на примере сериализации, отобрал сериализацию и отправил только строку «тестовые данные». Это помогло мне исправить мою проблему. Я думаю, что мой объект, содержащий Connection, удалялся раньше, и shared_ptr, похоже, исправил это – Shredder2500

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(). Это само по себе не должно быть проблемой (этот объект копируется по мере необходимости).

Однако фактические данные (проведенные в outboundHeader и outboundData) также от локальных объектов с автоматической продолжительностью хранения, поэтому они также выходят за рамки до 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); 
+0

Не должен ли outboundHeader и outboundData работать до тех пор, пока класс все еще существует? потому что они вне каких-либо методов? – Shredder2500

+0

Если они являются переменными-членами, да. Я пропустил эту деталь. Сам класс имеет достаточно продолжительный срок службы? – Chad

+0

хорошо класс соединения является переменной-членом класса Client. Когда клиент подключается, клиентский объект создается и сохраняется в списке клиентов std :: list следующим образом: Клиент клиента (conn, logger_); clientList.push_back (клиент); – Shredder2500

Смежные вопросы