[boost asio] Ошибка при выполнении

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by cupper, 28 Dec 2010.

  1. cupper

    cupper Elder - Старейшина

    Joined:
    6 Jun 2007
    Messages:
    369
    Likes Received:
    92
    Reputations:
    5
    Пытаюсь реализовать следующее. Есть класс, он умеет сериализоваться. Нужно передать его с клиента на сервер, на сервере с ним что то сделать, и вернуть назад.

    Логика работы следующая.
    есть класс Connection, с реализованными шаблонными функция для отправки и принятия данных.
    Code:
    #ifndef __CONNECTION_HPP__
    #define __CONNECTION_HPP__
    
    #include <boost/asio.hpp>
    #include <boost/archive/xml_iarchive.hpp>
    #include <boost/archive/xml_oarchive.hpp>
    #include <boost/bind.hpp>
    #include <boost/shared_ptr.hpp>
    #include <boost/tuple/tuple.hpp>
    
    #include <iomanip>
    #include <string>
    #include <sstream>
    #include <vector>
    
    
    class Connection{
    public:
        Connection(boost::asio::io_service& io_service)
            : socket_(io_service)
        {
        }
        
        boost::asio::ip::tcp::socket& socket()
        {
            return socket_;
        }
        
        template<typename T, typename Handler>
        void async_write(const T* data, Handler handler)
        {
            // сериализуем данные
            DataToString(outbound_data_, data);
                    
            // формируем хедер, заданой длины, содержащий размер отправляемых данных
            std::ostringstream header_stream;
            header_stream << std::setw(header_length_)
                << std::hex << outbound_data_.size();
                
            if(!header_stream || header_stream.str().size() != header_length_){
            // header_stream пустой или его длина не равняется
            // обязательной длине заголовка
                boost::system::error_code error(boost::asio::error::invalid_argument);
                socket_.io_service().post(boost::bind(handler, error));
                return;
            }
            // Сформировываем строку для заголовка
            outbound_header_ = header_stream.str();
            
            std::vector<boost::asio::const_buffer> buffers;
            buffers.push_back(boost::asio::buffer(outbound_header_));
            buffers.push_back(boost::asio::buffer(outbound_data_));
            boost::asio::async_write(socket_, buffers, handler);    
        }
        
        template<typename T, typename Handler>
        void async_read(T* data, Handler handler)
        {
            void (Connection::*f)(
                const boost::system::error_code&,
                T*, boost::tuple<Handler>) 
                = &Connection::handle_read_header<T, Handler>;
            
            boost::asio::async_read(socket_, boost::asio::buffer(inbound_header_),
                boost::bind(f, 
                    this, boost::asio::placeholders::error, data,
                    boost::make_tuple(handler)));
        }
        
        template<typename T, typename Handler>
        void handle_read_header(const boost::system::error_code& e,
            T* data, boost::tuple<Handler> handler)
        {
            if(e){
                boost::get<0>(handler)(e);
            }
            else{
                std::istringstream is(std::string(inbound_header_, header_length_));
                std::size_t inbound_data_size = 0;
                if(!(is >> std::hex >> inbound_data_size)){
                    boost::system::error_code error(
                        boost::asio::error::invalid_argument);
                    boost::get<0>(handler)(error);
                    return;
                }
                
                inbound_data_.resize(inbound_data_size);
                void (Connection::*f)(
                    const boost::system::error_code&,
                    T*, boost::tuple<Handler>)
                    = &Connection::handle_read_data<T, Handler>;
                
                boost::asio::async_read(socket_, boost::asio::buffer(inbound_data_),
                    boost::bind(f, this, 
                        boost::asio::placeholders::error, data, handler));
            }
        }
        
        template<typename T, typename Handler>
        void handle_read_data(const boost::system::error_code& e, 
            T* data, boost::tuple<Handler> handler)
        {
            if(e)
            {
                boost::get<0>(handler)(e);
            }
            else
            {
                try
                {
                    std::string archive_data(&inbound_data_[0], inbound_data_.size());
                    StringToData(archive_data, data);
                }
                catch(std::exception& e)
                {
                    boost::system::error_code error(
                        boost::asio::error::invalid_argument);
                    boost::get<0>(handler)(error);
                    return;
                }
                
                boost::get<0>(handler)(e);
            }
        }
                
    
    private:
        template<typename T>
        void DataToString(std::string& outstr, T* data)
        {
            std::ostringstream archive_stream;
            boost::archive::xml_oarchive archive(archive_stream);
            archive << boost::serialization::make_nvp("data",*data);
            // переводим в строку
            outstr = archive_stream.str();
        }
        template<typename T>
        void StringToData(const std::string& instr, T* data)
        {
            std::istringstream archive_stream(instr);
            boost::archive::xml_iarchive archive(archive_stream);
            archive >> boost::serialization::make_nvp("data",*data);
        }
        boost::asio::ip::tcp::socket socket_;
        
        // Длина заголовка, содержащий длину принимаемого сообщения
        const static size_t header_length_ = 8;
        
        std::string outbound_header_;
        std::string outbound_data_;
        
        char inbound_header_[header_length_];
        std::vector<char> inbound_data_;
    };
    
    typedef boost::shared_ptr<Connection> Connection_ptr;
    
    #endif
    
    класс взят из тутториала и допилен.

    Есть класс client который. Единственная особенность которую можно унего выделить это то что класс содержит два указателя (один обычный, указывающий на отправляемые данные, один умных для принимаемых данных, что бы не следить за их корректным удалением)
    Code:
    #ifndef __CLIENT_HPP__
    #define __CLIENT_HPP__
    
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/serialization/vector.hpp>
    #include <vector>
    
    #include "ListPerson.h"
    #include "connection.hpp" // Must come before boost/serialization headers.
    
    class Client
    {
    public:
      /// Constructor starts the asynchronous connect operation.
      Client(boost::asio::io_service& io_service,
          const std::string& host, const std::string& service, epam::ListPerson& data)
        : connection_(io_service)
      {
        sendData_ = &data;
        recvData_.reset(new epam::ListPerson());
        // Resolve the host name into an IP address.
        boost::asio::ip::tcp::resolver resolver(io_service);
        boost::asio::ip::tcp::resolver::query query(host, service);
        boost::asio::ip::tcp::resolver::iterator endpoint_iterator =
          resolver.resolve(query);
        boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
    
        // Начинаем процедуру подключения к серверу
        connection_.socket().async_connect(endpoint,
            boost::bind(&Client::handle_connect, this,
              boost::asio::placeholders::error, ++endpoint_iterator));
      }
    
      // Верунть принятые от сервера данные
      boost::shared_ptr<epam::ListPerson> GetRecvData() const
      {
        return recvData_;
      }
    
    private:
      void handle_connect(const boost::system::error_code& e,
          boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
      {
        if (!e)
        {
          // Соединение прошло успешно
          // Отправляем данные
            connection_.async_write(sendData_,
              boost::bind(&Client::handle_write, this, boost::asio::placeholders::error));
        }
        else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
        {
          // Иначе пробудем подлкючиться к следующей конечной точке
          connection_.socket().close();
          boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
          connection_.socket().async_connect(endpoint,
              boost::bind(&Client::handle_connect, this,
                boost::asio::placeholders::error, ++endpoint_iterator));
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
      void handle_write(const boost::system::error_code& e)
      {
        if (!e)
        {
          // Данные отправленны
          // Принимает ответ
          connection_.async_read(recvData_.get(),
              boost::bind(&Client::handle_read, this,
                boost::asio::placeholders::error));
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
      /// Handle completion of a read operation.
      void handle_read(const boost::system::error_code& e)
      {
        if (!e)
        {
          std::cout << recvData_.get();
        }
        else
        {
          // An error occurred.
          std::cerr << e.message() << std::endl;
        }
    
      }
    
    private:
      /// The connection to the server.
      Connection connection_;
    
      /// The data received from the server.
      epam::ListPerson* sendData_;
      boost::shared_ptr<epam::ListPerson> recvData_;
    };
    
    #endif // __CLIENT_HPP__
    
    есть серверный класс, который содержит просто один умный указатель (он указывает на пустой объект который моздается в конструкторе, в него производится десереализация данных, их модификация и отправка обратно клиенту.
    Code:
    #ifndef __SERVER_HPP__
    #define __SERVER_HPP__
    
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/lexical_cast.hpp>
    #include <boost/serialization/vector.hpp>
    
    #include <iostream>
    #include <vector>
    
    #include "connection.hpp"
    #include "ListPerson.h"
    
    class Server
    {
    public:
      Server(boost::asio::io_service& io_service, unsigned short port)
        : acceptor_(io_service,
            boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
      {
          data_.reset(new epam::ListPerson());
        // Start an accept operation for a new connection.
        Connection_ptr new_conn(new Connection(acceptor_.io_service()));
    
        acceptor_.async_accept(new_conn->socket(),
            boost::bind(&Server::handle_accept, this,
              boost::asio::placeholders::error, new_conn));
              
      }
    
      void handle_accept(const boost::system::error_code& e, Connection_ptr conn)
      {
        if (!e)
        {
          // Соединение активировано
          // Принимаем данные
            
            conn->async_read(data_.get(),
              boost::bind(&Server::handle_read, this,
                boost::asio::placeholders::error, conn));
                
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
      /// Handle completion of a read operation.
      void handle_read(const boost::system::error_code& e, Connection_ptr conn)
      {
        if (!e)
        {
            // чтото делаем сданными
            std::cout << data_.get() << std::endl;
            data_->Add(boost::shared_ptr<epam::Person>(new epam::Student("Server BSD'ec", epam::Gender::MAN, "1977.07.20", "XX-42", 3.56)));
          // Отправляем ответ
            conn->async_write(data_.get(),
              boost::bind(&Server::handle_write, this,
                boost::asio::placeholders::error));
    
            // Данные отправлены, запускаем новое ожидание подключения
            std::cout << "\nRestart ACCEPTRO\n";
            Connection_ptr new_conn(new Connection(acceptor_.io_service()));
            acceptor_.async_accept(new_conn->socket(),
                boost::bind(&Server::handle_accept, this,
                boost::asio::placeholders::error, new_conn));
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
      void handle_write(const boost::system::error_code& e)
      {
        if(!e)
        {
            // Вот СЮДА НЕДОХОДИТ
    
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
    private:
      boost::asio::ip::tcp::acceptor acceptor_;
      boost::shared_ptr<epam::ListPerson> data_;
    };
    
    #endif // __SERVER_HPP__
    
    это все собирается, запускается сервер, запускается клиент. Данные с клиента уходят, на сервере принимаються, к ним добавляется еще один елемент, они отправляются, перезапускается ожидание нового подключения (происходит в функции handle_read) И до функции handle_write на которой полный цикл работы должен завершиться, недоходит. Вылетает ошибка студии об ошибке отлабки, и несодержащая вообше не какой полезной информации. До клиента данные благополучно доходят. Ошибка возникает на стороне сервера. Найти или понять где я не в силах, вообще понятие не приложу в чем проблемма.

    [​IMG]

    Возможно ктото предложит более лучьшую реализацию задуманного, которая возможно поможет устранить данную ошибку. Буду рад любой помощи.
     
  2. Aag

    Aag Elder - Старейшина

    Joined:
    26 Jul 2005
    Messages:
    60
    Likes Received:
    19
    Reputations:
    8
    Поскольку фунции асинхронные, то ошибка происходит при обращении к data_

    1поток. Пока выполняется вызов conn->async_write(data_.get() ...

    2 поток. Одновременно выполняется вызов acceptor_.async_accept из которого handle_accept в котором conn->async_read(data_.get(),...

    Если приведенные выше предположения выше верны, можно закомментировать временно
    Code:
           // Данные отправлены, запускаем новое ожидание подключения
            std::cout << "\nRestart ACCEPTRO\n";
            Connection_ptr new_conn(new Connection(acceptor_.io_service()));
            acceptor_.async_accept(new_conn->socket(),
                boost::bind(&Server::handle_accept, this,
                boost::asio::placeholders::error, new_conn));
     
    Будет проявляться после этого ошибка?

    Если ошибка не проявится, значит, предположение верно. Как исправить? Приблизительно так, в качестве концепта:
    Code:
    class Server
    {
    public:
      Server(boost::asio::io_service& io_service, unsigned short port)
        : acceptor_(io_service,
            boost::asio::ip::tcp::endpoint(boost::asio::ip::tc  p::v4(), port))
      {
        // Start an accept operation for a new connection.
        Connection_ptr new_conn(new Connection(acceptor_.io_service()));
    
        acceptor_.async_accept(new_conn->socket(),
            boost::bind(&Server::handle_accept, this,
              boost::asio::placeholders::error, new_conn));
              
      }
    
      void handle_accept(const boost::system::error_code& e, Connection_ptr conn)
      {
        if (!e)
        {
          // Соединение активировано
          // Принимаем данные
          boost::shared_ptr<epam::ListPerson>  data_(new epam::ListPerson());
            
            conn->async_read(data_.get(),
              boost::bind(&Server::handle_read, this,
                boost::asio::placeholders::error, conn, data_));
                
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
      /// Handle completion of a read operation.
      void handle_read(const boost::system::error_code& e, Connection_ptr conn, boost::shared_ptr<epam::ListPerson> data_)
      {
        if (!e)
        {
            // чтото делаем сданными
            std::cout << data_.get() << std::endl;
            data_->Add(boost::shared_ptr<epam::Person>(new epam::Student("Server BSD'ec", epam::Gender::MAN, "1977.07.20", "XX-42", 3.56)));
          // Отправляем ответ
            conn->async_write(data_.get(),
              boost::bind(&Server::handle_write, this,
                boost::asio::placeholders::error));
    
            // Данные отправлены, запускаем новое ожидание подключения
            std::cout << "\nRestart ACCEPTRO\n";
            Connection_ptr new_conn(new Connection(acceptor_.io_service()));
            acceptor_.async_accept(new_conn->socket(),
                boost::bind(&Server::handle_accept, this,
                boost::asio::placeholders::error, new_conn));
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
      void handle_write(const boost::system::error_code& e)
      {
        if(!e)
        {
            // Вот СЮДА НЕДОХОДИТ
    
        }
        else
        {
          std::cerr << e.message() << std::endl;
        }
      }
    
    private:
      boost::asio::ip::tcp::acceptor acceptor_;
      
    };
    
    Или переложить вызов
    Code:
    // Данные отправлены, запускаем новое ожидание подключения
            std::cout << "\nRestart ACCEPTRO\n";
            Connection_ptr new_conn(new Connection(acceptor_.io_service()));
            acceptor_.async_accept(new_conn->socket(),
                boost::bind(&Server::handle_accept, this,
                boost::asio::placeholders::error, new_conn));
    в фунцию handle_write .
     
    1 person likes this.
  3. cupper

    cupper Elder - Старейшина

    Joined:
    6 Jun 2007
    Messages:
    369
    Likes Received:
    92
    Reputations:
    5
    изначально повторный вызов accept находился как раз в handle_write. Ошибка не в этом.

    НО ты предсказал критическую ошибку которая неминуемо наступила бы. Действиельно объек то у меня один в классе server, и рано или поздно случилась бы эта ошибка. Шас спазу поправлю чтобы объект при каждом accepte создавался свой новый объект. Спасибо :)

    Провел эксперимент. Выбросил из сервера часть с отправкой данных назад клиенту, ошибки нет. Значит ошибка точно в этот моменте. Осталось найти где именно))
     
    #3 cupper, 29 Dec 2010
    Last edited: 29 Dec 2010