Подтвердить что ты не робот

Как асинхронно читать std::string с помощью Boost:: asio?

Я изучаю Boost:: asio и все эти асинхронные вещи. Как я могу асинхронно читать переменную user_ типа std::string? Boost::asio::buffer(user_) работает только с async_write(), но не с async_read(). Он работает с вектором, поэтому в чем причина, почему он не работает со строкой? Есть ли другой способ сделать это, помимо объявления char user_[max_len] и использования Boost::asio::buffer(user_, max_len)?

Кроме того, какая точка наследования от boost::enable_shared_from_this<Connection> и используя shared_from_this() вместо this в async_read() и async_write()? Я видел это в примерах.

Вот часть моего кода:

class Connection
{
    public:

        Connection(tcp::acceptor &acceptor) :
            acceptor_(acceptor), 
            socket_(acceptor.get_io_service(), tcp::v4())
        { }

        void start()
        {
            acceptor_.get_io_service().post(
                boost::bind(&Connection::start_accept, this));
        }

    private:

        void start_accept()
        {
            acceptor_.async_accept(socket_, 
                boost::bind(&Connection::handle_accept, this, 
                placeholders::error));
        }

        void handle_accept(const boost::system::error_code& err)
        {
            if (err)
            {
                disconnect();
            }
            else
            {
                async_read(socket_, boost::asio::buffer(user_),
                    boost::bind(&Connection::handle_user_read, this,
                    placeholders::error, placeholders::bytes_transferred));
            }
        }

        void handle_user_read(const boost::system::error_code& err,
            std::size_t bytes_transferred)
        {
            if (err)
            {
                disconnect();
            }
            else
            {
                ...
            }
        }

        ...

        void disconnect()
        {
            socket_.shutdown(tcp::socket::shutdown_both);
            socket_.close();
            socket_.open(tcp::v4());
            start_accept();
        }

        tcp::acceptor &acceptor_;
        tcp::socket socket_;
        std::string user_;
        std::string pass_;
        ...
};
4b9b3361

Ответ 1

Документация Boost.Asio гласит:

Буферный объект представляет собой смежную область памяти в виде 2-кортежей, состоящую из указателя и размера в байтах. Кортеж формы {void *, size_t} указывает изменяемую (изменяемую) область памяти.

Это означает, что для того, чтобы вызов async_read записывал данные в буфер, он должен быть (в базовом буфере) непрерывным блоком памяти. Кроме того, буферный объект должен иметь возможность записывать в этот блок памяти.

std::string не позволяет произвольные записи в свой буфер, поэтому async_read не может записывать фрагменты памяти в строковый буфер (обратите внимание, что std::string предоставляет доступ только для чтения вызывающего пользователя к базовому буферу через data(), который гарантирует, что возвращаемый указатель будет действителен до следующего вызова функции не-const-члена. По этой причине Asio может легко создать обертку const_buffer std::string, и вы можете использовать ее с помощью async_write).

Документация Asio содержит примерный код для простой программы чата (см. http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat), который имеет хороший метод преодоления этой проблемы, В принципе, вам нужно, чтобы отправляемый TCP отправлялся вместе с размером сообщения сначала в "заголовке" сортировки, и ваш обработчик чтения должен интерпретировать заголовок, чтобы выделить буфер фиксированного размера, подходящий для чтения фактических данных.

Что касается использования shared_from_this() в async_read и async_write, причина в том, что он гарантирует, что метод, обернутый boost::bind, всегда будет ссылаться на живой объект. Рассмотрим следующую ситуацию:

  • Ваш метод handle_accept вызывает async_read и отправляет обработчик "в реактор" - в основном вы попросили io_service вызывать Connection::handle_user_read, когда он заканчивает чтение данных из сокета. io_service хранит этот функтор и продолжает цикл, ожидая завершения операции асинхронного чтения.
  • После вашего вызова async_read объект Connection по какой-либо причине освобождается (завершение программы, условие ошибки и т.д.).
  • Предположим, что io_service теперь определяет, что асинхронное чтение завершено, после того как объект Connection был освобожден, но до того, как io_service будет уничтожен (это может произойти, например, если io_service::run работает в отдельная резьба, как это типично). Теперь io_service пытается вызвать обработчик и имеет недопустимую ссылку на объект Connection.

Решение состоит в том, чтобы выделить Connection через shared_ptr и использовать shared_from_this() вместо this при отправке обработчика "в реактор" - это позволяет io_service хранить общую ссылку на объект, и shared_ptr гарантирует, что он не будет освобожден до истечения последней ссылки.

Итак, ваш код должен выглядеть примерно так:

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

    Connection(tcp::acceptor &acceptor) :
        acceptor_(acceptor), 
        socket_(acceptor.get_io_service(), tcp::v4())
    { }

    void start()
    {
        acceptor_.get_io_service().post(
            boost::bind(&Connection::start_accept, shared_from_this()));
    }

private:

    void start_accept()
    {
        acceptor_.async_accept(socket_, 
            boost::bind(&Connection::handle_accept, shared_from_this(), 
            placeholders::error));
    }

    void handle_accept(const boost::system::error_code& err)
    {
        if (err)
        {
            disconnect();
        }
        else
        {
            async_read(socket_, boost::asio::buffer(user_),
                boost::bind(&Connection::handle_user_read, shared_from_this(),
                placeholders::error, placeholders::bytes_transferred));
        }
    }
    //...
};

Обратите внимание, что теперь вы должны убедиться, что каждый Connection объект распределяется через shared_ptr, например:

boost::shared_ptr<Connection> new_conn(new Connection(...));

Надеюсь, это поможет!

Ответ 2

Это не является ответом как таковым, а просто длинным комментарием: очень простой способ конвертировать из буфера ASIO в строку - это поток из него:

asio::streambuf buff;
asio::read_until(source, buff, '\r');  // for example

istream is(&buff);
is >> targetstring;

Это копия данных, конечно, но это то, что вам нужно сделать, если вы хотите, чтобы она была в строке.

Ответ 3

Вы можете использовать std:string с async\_read() следующим образом:

async_read(socket_, boost::asio::buffer(&user_[0], user_.size()),
           boost::bind(&Connection::handle_user_read, this,
           placeholders::error, placeholders::bytes_transferred));

Однако вам лучше убедиться, что std::string достаточно велик, чтобы принять пакет, который вы ожидаете, и заполнить нулями перед вызовом async\_read().

И почему вы должны НИКОГДА связывать обратный вызов функции-члена с указателем this, если объект можно удалить, более подробное описание и более надежный метод можно найти здесь: Boost async_ * функции и shared_ptr.

Ответ 4

Boost Asio имеет два стиля буферов. Там boost::asio::buffer(your_data_structure), который не может расти, и поэтому обычно бесполезен для неизвестного ввода, а там boost::asio::streambuf, который может расти.

Учитывая boost::asio::streambuf buf, вы превращаете его в строку с std::string(std::istreambuf_iterator<char>(&buf), {});.

Это неэффективно, поскольку вы в конечном итоге копируете данные еще раз, но для этого требуется boost::asio::buffer знать растущие контейнеры, т.е. контейнеры, которые имеют метод .resize(N). Вы не можете сделать это эффективно, не касаясь кода Boost.