“Cannot dereference string iterator” with Boost

  Kiến thức lập trình

I have a system with multiple TCP clients shared between two TCP servers. These two TCP servers also need to communicate with each other. I defined two classes : TcpServer and TcpClient. All my clients inherit from TcpClient, and the two servers inherit from both classes. I don’t know if this design is correct, since my main goal is for the servers to exchange messages, so each server is a client to the other server. Also, clients and servers need to communicate asynchronously.

However, I’m encountering an error when attempting to send multiple consecutive messages (of different types) from one server to another : “Debug assertion failed ! Expression: Cannot dereference string iterator because the iterator was invalidated”.

I read online that this error happens with asynchronous operations when the buffer is no longer valid. But my buffer is a member of a helper class TcpHelper that manages reading and writing operations.

Here are the defined classes.

class TcpHelper
{
private:
    /** Socket for TCP connection. */
    boost::asio::ip::tcp::socket m_socket;
    /** Outgoing message header. */
    std::string m_out_header;
    /** Serialized outgoing data. */
    std::string m_out_data;
    /** Buffer for incoming message header. */
    char m_in_header[HEADER_LENGTH];
    /** Buffer for incoming serialized data. */
    std::vector<char> m_in_data;

public:
    /**
     * Initialize a TCP socket.
     * @param io_context: Reference to Boost I/O context.
     */
    explicit TcpHelper(boost::asio::io_context& io_context) : m_socket(io_context)
    {
    }

    /**
     * Getter method for TCP socket.
     * @return A reference to the TCP socket.
     */
    boost::asio::ip::tcp::socket& getSocket()
    {
        return m_socket;
    }

    /**
     * Asynchronously write serialized data to the TCP socket and handle completion with a user-defined handler.
     * @tparam T: Type of data to serialize.
     * @tparam Handler: Type of handler to manage completion of the write operation.
     * @param data_type: Reference to the data to serialize and send.
     * @param handler: Callback function to handle the completion of the write operation.
     */
    template<typename T, typename Handler>
    void write(const T& data_type, Handler handler)
    {
        BOOST_LOG_TRIVIAL(debug) << "TCP Helper : Write operation";
        std::ostringstream archive_stream;
        boost::archive::text_oarchive archive(archive_stream);
        archive << data_type;
        m_out_data = archive_stream.str();

        std::ostringstream header_stream;
        header_stream << std::setw(HEADER_LENGTH) << std::hex << m_out_data.size();
        BOOST_LOG_TRIVIAL(debug) << "TCP Helper : Write " << m_out_data.size() << " bytes";
        if (!header_stream || header_stream.str().size() != HEADER_LENGTH)
        {
            BOOST_LOG_TRIVIAL(error) << "TCP Helper : Error in header size (Invalid argument)";
            boost::system::error_code error(boost::asio::error::invalid_argument);
            post(m_socket.get_executor(), boost::bind(handler, error));
            return;
        }
        m_out_header = header_stream.str();

        std::vector<boost::asio::const_buffer> buffers;
        buffers.emplace_back(boost::asio::buffer(m_out_header));
        buffers.emplace_back(boost::asio::buffer(m_out_data));
        boost::asio::async_write(m_socket, buffers, handler);
    }

    /**
     * Asynchronously read data from the TCP socket, deserialize it into a specified data type and handle completion
     * with a user-defined handler.
     * @tparam T: Type of data to deserialize into.
     * @tparam Handler: Type of handler to manage completion of the read operation.
     * @param data_type: Reference to store the deserialized data.
     * @param handler: Callback function to handle the completion of the read operation.
     */
    template<typename T, typename Handler>
    void read(T& data_type, Handler handler)
    {
        BOOST_LOG_TRIVIAL(debug) << "TCP Helper : Read operation";
        void (TcpHelper::*function)(const boost::system::error_code&, T&, boost::tuple<Handler>) = &TcpHelper::handleReadHeader<T, Handler>;
        boost::asio::async_read(m_socket, boost::asio::buffer(m_in_header), boost::bind(function, this, boost::asio::placeholders::error, boost::ref(data_type), boost::make_tuple(handler)));
    }

    /**
     * Internal handler to manage reading headers during TCP communications.
     * @tparam T: Type of data expected to be received.
     * @tparam Handler: Type of handler to manage completion of the read operation.
     * @param error_code: Error information passed by Boost.
     * @param data_type:  Reference to store the deserialized data.
     * @param handler: Callback function to handle the completion of the read operation.
     */
    template<typename T, typename Handler>
    void handleReadHeader(const boost::system::error_code& error_code, T& data_type, boost::tuple<Handler> handler)
    {
        if (error_code)
        {
            BOOST_LOG_TRIVIAL(error) << "TCP Helper : Error in incoming data length (" << m_in_header << " bytes)";
            boost::get<0>(handler)(error_code);
        }
        else
        {
            std::istringstream header_stream(std::string(m_in_header, HEADER_LENGTH));
            std::size_t m_inbound_data_size = 0;
            if (!(header_stream >> std::hex >> m_inbound_data_size))
            {
                BOOST_LOG_TRIVIAL(error) << "TCP Helper : Could not retrieve data size (Invalid argument)";
                boost::system::error_code error(boost::asio::error::invalid_argument);
                boost::get<0>(handler)(error);
                return;
            }

            BOOST_LOG_TRIVIAL(info) << "TCP Helper : Size = " << m_inbound_data_size;
            m_in_data.resize(m_inbound_data_size);
            void (TcpHelper::*function)(const boost::system::error_code&, T&, boost::tuple<Handler>) = &TcpHelper::handleReadData<T, Handler>;

            boost::asio::async_read(m_socket, boost::asio::buffer(m_in_data), boost::bind(function, this, boost::asio::placeholders::error, boost::ref(data_type), handler));
        }
    }

    /**
     * Internal handler to manage reading data during TCP communications.
     * @tparam T: Type of data expected to be received.
     * @tparam Handler: Type of handler to manage completion of the read operation.
     * @param error_code: Error information passed by Boost.
     * @param data_type:  Reference to store the deserialized data.
     * @param handler: Callback function to handle the completion of the read operation.
     */
    template<typename T, typename Handler>
    void handleReadData(const boost::system::error_code& error_code, T& data_type, boost::tuple<Handler> handler)
    {
        if (error_code)
        {
            boost::get<0>(handler)(error_code);
        }
        else
        {
            try
            {
                std::string archive_data(&m_in_data[0], m_in_data.size());
                std::istringstream archive_stream(archive_data);
                boost::archive::text_iarchive archive(archive_stream);
                archive >> data_type;
                BOOST_LOG_TRIVIAL(debug) << "TCP Helper : Read " << m_in_data.size() << " bytes";
            }
            catch (std::exception& exception)
            {
                BOOST_LOG_TRIVIAL(error) << "TCP Helper : Error in deserialization (Invalid argument)";
                boost::system::error_code error(boost::asio::error::invalid_argument);
                boost::get<0>(handler)(error);
                return;
            }

            boost::get<0>(handler)(error_code);
        }
    }
};

/** Type alias for a shared pointer to TcpHelper to simplify handling and memory management. */
typedef boost::shared_ptr<TcpHelper> p_tcp_connection;
class TcpClient
{
protected:
    /** Client ID. */
    std::string m_id;
    /** Server TCP/IP ID. */
    TcpIpId& m_server_id;
    /** Retry attempts counter for connecting. */
    int m_number_retry;
    /** Last message received from the server. */
    Message m_message_received;
    /** Reference to the I/O context for asynchronous operations. */
    boost::asio::io_context& m_io_context;
    /** Pointer to the TCP connection. */
    p_tcp_connection m_connection;

    void handleConnect(const boost::system::error_code& error);

    void handleRead(const boost::system::error_code& error);

    void handleWrite(const boost::system::error_code& error);

    void handleDisconnect();

public:
    /** Retry attempts constant for connecting. */
    static constexpr int MAX_NUMBER_RETRY = 5;
    /** Wait time before retry attempt in seconds. */
    static constexpr int WAIT_TIME = 5;

    TcpClient(const std::string& id, TcpIpId& server_id, boost::asio::io_context& io_context);

    virtual ~TcpClient();

    void connect();

    virtual void read();

    void write(const Message& message);

    void disconnect();
};

/**
 * Initialize a TCP client.
 * @param id: Client ID.
 * @param server_id: Server TCP/IP ID.
 * @param io_context: Reference to Boost I/O context.
 */
TcpClient::TcpClient(const std::string& id, TcpIpId& server_id, boost::asio::io_context& io_context) : m_id(id), m_server_id(server_id), m_number_retry(0), m_io_context(io_context), m_connection(new TcpHelper(m_io_context))
{
}

/**
 * Virtual destructor that disconnects a client.
 */
TcpClient::~TcpClient()
{
    disconnect();
}

/**
 * Initiate a connection to the server.
 */
void TcpClient::connect()
{
    boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(m_server_id.getIp()), m_server_id.getPort());
    boost::asio::ip::tcp::socket& socket = m_connection->getSocket();
    socket.async_connect(endpoint, boost::bind(&TcpClient::handleConnect, this, boost::asio::placeholders::error));
}

/**
 * Read the received message.
 */
void TcpClient::read()
{
    m_connection->read(m_message_received, boost::bind(&TcpClient::handleRead, this, boost::asio::placeholders::error));
}

/**
 * Write a message.
 * @param message: Message to be sent to the server.
 */
void TcpClient::write(const Message& message)
{
    m_connection->write(message, boost::bind(&TcpClient::handleWrite, this, boost::asio::placeholders::error));
}

/**
 * Close TCP socket.
 */
void TcpClient::disconnect()
{
    m_io_context.post(boost::bind(&TcpClient::handleDisconnect, this));
}

/**
 * Handle the completion of an asynchronous connect request.
 * @param error: Error information passed by Boost.
 */
void TcpClient::handleConnect(const boost::system::error_code& error)
{
    if (error)
    {
        BOOST_LOG_TRIVIAL(error) << "TCP Client : Connection handler failed (" << error.to_string() << ")";
        if (m_number_retry < MAX_NUMBER_RETRY)
        {
            BOOST_LOG_TRIVIAL(info) << "TCP Client : Retry connection";

            boost::asio::steady_timer timer(m_io_context);
            timer.expires_after(std::chrono::seconds(WAIT_TIME));
            timer.wait();
            connect();
            m_number_retry += 1;
        }
    }
}

/**
 * Handle the completion of an asynchronous read request.
 * @param error: Error information passed by Boost.
 */
void TcpClient::handleRead(const boost::system::error_code& error)
{
    if (!error)
    {
        BOOST_LOG_TRIVIAL(debug) << "TCP Client : Read handler successful";
        m_connection->read(m_message_received, boost::bind(&TcpClient::handleRead, this, boost::asio::placeholders::error));
        BOOST_LOG_TRIVIAL(info) << "TCP Client : Message received (" << m_message_received << ")";
    }
    else
    {
        BOOST_LOG_TRIVIAL(error) << "TCP Client : Read handler failed";
    }
}

/**
 * Handle the completion of an asynchronous write request.
 * @param error: Error information passed by Boost.
 */
void TcpClient::handleWrite(const boost::system::error_code& error)
{
    if (error)
    {
        BOOST_LOG_TRIVIAL(error) << "TCP Client : Write handler failed";
    }
}

/**
 * Handle a disconnect operation.
 */
void TcpClient::handleDisconnect()
{
    m_connection->getSocket().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
    m_connection->getSocket().close();
}
class TcpServer
{
protected:
    /** Server TCP/IP ID. */
    TcpIpId& m_id;
    /** Reference to the I/O context for asynchronous operations. */
    boost::asio::io_context& m_io_context;
    /** Acceptor object to accept incoming socket connections. */
    boost::asio::ip::tcp::acceptor m_acceptor;
    /** Set of active sessions managed by the server. */
    std::set<p_tcp_session> m_sessions;

    void listen();

    void handleAccept(p_tcp_connection connection, const boost::system::error_code& error);

    void handleRead(p_tcp_connection connection, const boost::system::error_code& error);

public:
    TcpServer(TcpIpId& id, boost::asio::io_context& io_context);

    TcpIpId getId() const;

    virtual void decodeMessage(const Message& message, p_tcp_session session) = 0;
};

/**
 * Initialize a TCP server and start listening for incoming connections.
 * @param id: Server TCP/IP ID.
 * @param io_context: Reference to Boost I/O context.
 */
TcpServer::TcpServer(TcpIpId& id, boost::asio::io_context& io_context) : m_id(id), m_io_context(io_context), m_acceptor(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(m_id.getIp()), m_id.getPort()))
{
    listen();
}

/**
 * Getter method for server's TCP/IP ID.
 * @return Server TCP/IP ID.
 */
TcpIpId TcpServer::getId() const
{
    return m_id;
}

/**
 * Start listening for incoming connections.
 */
 void TcpServer::listen()
{
    p_tcp_connection connection(new TcpHelper(m_io_context));
    m_acceptor.async_accept(connection->getSocket(), boost::bind(&TcpServer::handleAccept, this, connection, boost::asio::placeholders::error));
}

/**
 * Handle the acceptance of a new connection by creating a new session.
 * @param connection: Newly accepted TCP connection.
 * @param error: Error information passed by Boost.
 */
void TcpServer::handleAccept(p_tcp_connection connection, const boost::system::error_code& error)
{
    if (!error)
    {
        p_tcp_session session = TcpSession::create(connection, *this);
        m_sessions.insert(session);
        listen();
    }
    else
    {
        BOOST_LOG_TRIVIAL(error) << "TCP Server : Accept handler failed (" << error.message() << ")";
    }
}

/**
 * Handle the completion of a read operation.
 * @param connection: TCP connection.
 * @param error: Error information passed by Boost.
 */
void TcpServer::handleRead(p_tcp_connection connection, const boost::system::error_code& error)
{
    if (error)
    {
        BOOST_LOG_TRIVIAL(error) << "TCP Server : Read handler failed (" << error.message() << ")";
    }
}

So the problem appears when I call TcpClient::write(message) multiple times. I also tried putting a mutex before writing to the socket but the problem remains.

LEAVE A COMMENT