Как асинхронно читать в std :: string, используя Boost :: asio? - PullRequest
12 голосов
/ 07 мая 2010

Я изучаю 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_;
        ...
};

Ответы [ 4 ]

30 голосов
/ 08 мая 2010

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

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

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

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

В документации Asio приведен пример кода для простой программы "chat" (см. 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, например ::1010 *

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

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

9 голосов
/ 16 августа 2012

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

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

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

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

4 голосов
/ 28 октября 2013

Вы можете использовать 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 .

1 голос
/ 28 ноября 2017

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-кода.

...