Как асинхронно читать в 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_;
        ...
};

person SpyBot    schedule 07.05.2010    source источник


Ответы (4)


В документации Boost.Asio указано:

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

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

std::string не разрешает произвольную запись в свой буфер, поэтому async_read не может записывать фрагменты памяти в строковый буфер (обратите внимание, что std::string предоставляет вызывающей стороне доступ только для чтения к базовому буферу через метод data(), который гарантирует, что возвращенный указатель будет действителен до следующего вызова неконстантной функции-члена. По этой причине 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, всегда будет ссылаться на живой объект. Рассмотрим следующую ситуацию:

  1. Ваш handle_accept метод вызывает async_read и отправляет обработчик «в реактор» - в основном вы просили io_service вызвать Connection::handle_user_read, когда он закончит чтение данных из сокета. io_service сохраняет этот функтор и продолжает цикл, ожидая завершения операции асинхронного чтения.
  2. После вашего вызова async_read объект Connection по какой-то причине освобождается (завершение программы, состояние ошибки и т. Д.)
  3. Предположим, что 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(...));

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

person bjlaub    schedule 08.05.2010

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

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

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

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

person Mike C    schedule 16.08.2012
comment
Это правильный ответ. Boost.Asio может читать из многих типов буферов и записывать во многие буферы фиксированного размера, но asio::streambuf - единственный буфер переменного размера, с которым он будет работать. Как правильно отмечает MikeC, это копия данных, но у std::string есть внутренние копии, когда она все равно увеличивается. Обратите внимание, что метод извлечения operator>> - это обычный метод анализа пробелов из iostream. Вы также можете позвонить getline на istream. - person MSalters; 12.08.2019

Вы можете использовать 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, если объект можно удалить, более полное описание и более надежный метод можно найти здесь: Увеличьте функции async_ * и shared_ptr.

person kenba    schedule 27.10.2013

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.

person MSalters    schedule 28.11.2017