如何使用 Boost::asio 异步读取 std::string?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2790383/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 00:44:35  来源:igfitidea点击:

How to asynchronously read to std::string using Boost::asio?

stringboostasynchronousboost-asio

提问by SpyBot

I'm learning Boost::asio and all that async stuff. How can I asynchronously read to variable user_of type std::string? Boost::asio::buffer(user_)works only with async_write(), but not with async_read(). It works with vector, so what is the reason for it not to work with string? Is there another way to do that besides declaring char user_[max_len]and using Boost::asio::buffer(user_, max_len)?

我正在学习 Boost::asio 和所有异步的东西。如何异步读取user_std::string 类型的变量?Boost::asio::buffer(user_)仅适用于async_write(),但不适用于async_read()。它适用于向量,那么它不适用于字符串的原因是什么?除了声明char user_[max_len]和使用之外,还有其他方法可以做到这一点Boost::asio::buffer(user_, max_len)吗?

Also, what's the point of inheriting from boost::enable_shared_from_this<Connection>and using shared_from_this()instead of thisin async_read()and async_write()? I've seen that a lot in the examples.

另外,继承自boost::enable_shared_from_this<Connection>和使用shared_from_this()而不是thisin async_read()and 有async_write()什么意义?我在例子中看到了很多。

Here is a part of my code:

这是我的代码的一部分:

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_;
        ...
};

回答by bjlaub

The Boost.Asio documentation states:

Boost.Asio 文档指出:

A buffer object represents a contiguous region of memory as a 2-tuple consisting of a pointer and size in bytes. A tuple of the form {void*, size_t} specifies a mutable (modifiable) region of memory.

缓冲区对象将内存的连续区域表示为由指针和大小(以字节为单位)组成的 2 元组。{void*, size_t} 形式的元组指定可变(可修改)内存区域。

This means that in order for a call to async_readto write data to a buffer, it must be (in the underlying buffer object) a contiguous block of memory. Additionally, the buffer object must be able to write to that block of memory.

这意味着为了调用async_read将数据写入缓冲区,它必须(在底层缓冲区对象中)是一个连续的内存块。此外,缓冲区对象必须能够写入该内存块。

std::stringdoes not allow arbitrary writes into its buffer, so async_readcannot write chunks of memory into a string's buffer (note that std::stringdoesgive the caller read-only access to the underlying buffer via the data()method, which guarantees that the returned pointer will be valid until the next call to a non-const member function. For this reason, Asio can easily create a const_bufferwrapping an std::string, and you can use it with async_write).

std::string不允许任意写入其缓冲区,因此async_read不能将内存块写入字符串的缓冲区(请注意,这std::string确实通过该data()方法为调用者提供了对底层缓冲区的只读访问权限,这保证了返回的指针将在下一次之前有效调用非常量成员函数。因此,Asio 可以轻松创建一个const_buffer包装std::string,并且您可以将其与async_write)一起使用。

The Asio documentation has example code for a simple "chat" program (see http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat) that has a good method of overcoming this problem. Basically, you need to have the sending TCP send along the size of a message first, in a "header" of sorts, and your read handler must interpret the header to allocate a buffer of a fixed size suitable for reading the actual data.

Asio 文档有一个简单的“聊天”程序的示例代码(参见http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat),它有一个克服这个问题的好方法。基本上,您需要首先让发送 TCP 沿着消息的大小发送,在各种“标头”中,并且您的读取处理程序必须解释标头以分配适合读取实际数据的固定大小的缓冲区。

As far as the need for using shared_from_this()in async_readand async_write, the reason is that it guarantees that the method wrapped by boost::bindwill always refer to a live object. Consider the following situation:

至于需要使用shared_from_this()inasync_readasync_write,原因是它保证了包装的方法boost::bind将始终引用一个活动对象。考虑以下情况:

  1. Your handle_acceptmethod calls async_readand sends a handler "into the reactor" - basically you've asked the io_serviceto invoke Connection::handle_user_readwhen it finishes reading data from the socket. The io_servicestores this functor and continues its loop, waiting for the asynchronous read operation to complete.
  2. After your call to async_read, the Connectionobject is deallocated for some reason (program termination, an error condition, etc.)
  3. Suppose the io_servicenow determines that the asynchronous read is complete, afterthe Connectionobject has been deallocated but beforethe io_serviceis destroyed (this can occur, for example, if io_service::runis running in a separate thread, as is typical). Now, the io_serviceattempts to invoke the handler, and it has an invalid reference to a Connectionobject.
  1. 您的handle_accept方法调用async_read并将处理程序发送到“反应器”中 - 基本上您已经要求在它完成从套接字读取数据时io_service调用Connection::handle_user_read。该io_service存储这个函子,并继续其循环,等待异步读取操作完成。
  2. 在您调用 之后async_readConnection对象由于某种原因(程序终止、错误条件等)被释放
  3. 假设io_service现在确定异步读取完成时,之后Connection对象已被解除分配,但之前io_service被破坏(这可能发生,例如,如果io_service::run在一个单独的线程运行时,由于是典型值)。现在,io_service尝试调用处理程序,并且它具有对Connection对象的无效引用。

The solution is to allocate Connectionvia a shared_ptrand use shared_from_this()instead of thiswhen sending a handler "into the reactor" - this allows io_serviceto store a shared reference to the object, and shared_ptrguarantees that it won't be deallocated until the last reference expires.

解决方案是Connection通过 ashared_ptr和 useshared_from_this()而不是this在将处理程序发送到“反应器”时进行分配- 这允许io_service存储对对象的共享引用,并shared_ptr保证在最后一个引用到期之前它不会被释放。

So, your code should probably look something like:

所以,你的代码应该看起来像:

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));
        }
    }
    //...
};

Note that you now must make sure that each Connectionobject is allocated via a shared_ptr, e.g.:

请注意,您现在必须确保每个Connection对象都是通过 a 分配的shared_ptr,例如:

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

Hope this helps!

希望这可以帮助!

回答by Mike C

This isn't intended to be an answer per se, but just a lengthy comment: a very simple way to convert from an ASIO buffer to a string is to stream from it:

这本身并不是一个答案,而只是一个冗长的评论:从 ASIO 缓冲区转换为字符串的一种非常简单的方法是从中流式传输:

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

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

This is a data copy, of course, but that's what you need to do if you want it in a string.

当然,这是一个数据副本,但如果您希望将其放入字符串,则需要这样做。

回答by kenba

You can use a std:stringwith async\_read()like this:

您可以像这样使用std:stringwith async\_read()

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

However, you'd better make sure that the std::stringis big enough to accept the packet that you're expecting and padded with zeros before calling async\_read().

但是,您最好确保std::string在调用async\_read().

And as for why you should NEVERbind a member function callback to a thispointer if the object can be deleted, a more complete description and a more robust method can be found here: Boost async_* functions and shared_ptr's.

至于为什么如果可以删除对象,则永远不要将成员函数回调绑定到this指针,可以在此处找到更完整的描述和更健壮的方法:Boost async_* functions and shared_ptr's

回答by MSalters

Boost Asio has two styles of buffers. There's boost::asio::buffer(your_data_structure), which cannotgrow, and is therefore generally useless for unknown input, and there's boost::asio::streambufwhich cangrow.

Boost Asio 有两种风格的缓冲区。还有boost::asio::buffer(your_data_structure),它不能生长,因此一般是无用的未知输入,并有boost::asio::streambuf可以成长。

Given a boost::asio::streambuf buf, you turn it into a string with std::string(std::istreambuf_iterator<char>(&buf), {});.

给定 a boost::asio::streambuf buf,用 将其转换为字符串std::string(std::istreambuf_iterator<char>(&buf), {});

This is not efficient as you end up copying data once more, but that would require making boost::asio::bufferaware of growable containers, i.e. containers that have a .resize(N)method. You can't make it efficient without touching Boost code.

这不是有效的,因为您最终会再次复制数据,但这需要boost::asio::buffer了解可增长的容器,即具有.resize(N)方法的容器。不接触 Boost 代码就无法提高效率。