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

18
我正在学习 Boost::asio 和异步编程。如何使用类型为 std::string 的变量 user_ 进行异步读取?Boost::asio::buffer(user_) 只能与 async_write() 一起使用,而不能与 async_read() 一起使用。它可以与 vector 一起使用,那么它不能与字符串一起使用的原因是什么?除了声明 char user_[max_len] 并使用 Boost::asio::buffer(user_, max_len) 外,是否还有其他方法实现异步读取?
此外,在 async_read() 和 async_write() 中继承 boost::enable_shared_from_this<Connection> 并使用 shared_from_this() 而不是 this 的目的是什么?我在很多示例中看到过这种用法。
这是我的一部分代码:
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个回答

33
Boost.Asio文档说明:
缓冲区对象表示内存的连续区域,是一个由指针和字节大小组成的2元组。形如{void*,size_t}的元组指定了可变(可修改)的内存区域。
这意味着,为了使async_read调用将数据写入缓冲区,它必须是(在底层缓冲区对象中)一个连续的内存块。此外,缓冲区对象必须能够写入该内存块。
std :: string不允许对其缓冲区进行任意写入,因此async_read无法将内存块写入字符串的缓冲区(请注意,std :: string通过data()方法向调用者提供对底层缓冲区的只读访问权限,该方法保证返回的指针将在下一次调用非const成员函数之前有效。因此,Asio可以轻松创建包装std :: string的const_buffer,并可与async_write一起使用)。
Asio文档中有一个简单的“聊天”程序示例代码(参见http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat),它有一个很好的方法来解决这个问题。基本上,您需要让发送TCP首先发送消息的大小,在某种程度上是一个“头部”,您的读取处理程序必须解释头部以分配适合读取实际数据的固定大小的缓冲区。
至于在async_readasync_write中使用shared_from_this()的原因是它保证由boost::bind包装的方法始终引用活动对象。考虑以下情况:
  1. 您的handle_accept方法调用async_read并将处理程序“发送到反应堆” - 基本上,您已要求io_service在完成从套接字读取数据后调用Connection :: handle_user_readio_service存储此函数对象并继续其循环,等待异步读取操作完成。
  2. 在调用async_read之后,由于某种原因(程序终止,错误条件等),Connection对象被释放。
  3. 假设io_service现在确定异步读取已完成,在Connection对象被释放但io_service被销毁之前发生(例如,如果io_service :: run在单独的线程中运行,则可能会发生此情况)。 现在,io_service尝试调用处理程序,并且它具有对Connection对象的无效引用。

解决方案是通过shared_ptr分配Connection并在将处理程序“发送到反应堆”时使用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(...));

希望这能帮到你!

12

这并不是一篇答案,只是一个很长的评论:从ASIO缓冲区转换为字符串的非常简单的方法是从它上面流式传输:

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

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

当然,这只是数据副本,但如果你想要将其转换为字符串,这就是需要做的。


2
这是正确的答案。Boost.Asio可以从许多缓冲区类型中读取,并写入许多固定大小的缓冲区,但asio::streambuf是它唯一能够使用的可变大小缓冲区。正如MikeC所指出的那样,这是一种数据复制,但是当std::string增长时,它也会进行内部复制。请注意,提取方法operator>>是iostream中通常的空格解析方法。您还可以在istream上调用getline - MSalters

5
您可以像这样使用async\_read()std:string相关联:
async_read(socket_, boost::asio::buffer(&user_[0], user_.size()),
           boost::bind(&Connection::handle_user_read, this,
           placeholders::error, placeholders::bytes_transferred));

然而,在调用async\_read()之前,你最好确保std::string足够大,能够接受你所期望的数据包,并且在调用之前填充零。

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


2
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代码的情况下使其更加高效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接