【问题标题】:How to asynchronously read to std::string using Boost::asio?如何使用 Boost::asio 异步读取 std::string?
【发布时间】:2011-02-16 22:29:21
【问题描述】:

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

另外,从boost::enable_shared_from_this<Connection> 继承并在async_read()async_write() 中使用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_;
        ...
};

【问题讨论】:

    标签: string boost asynchronous boost-asio


    【解决方案1】:

    Boost.Asio 文档指出:

    缓冲区对象将内存的连续区域表示为由指针和字节大小组成的 2 元组。 {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 以消息的大小以某种“标头”形式发送,并且您的读取处理程序必须解释标头以分配适合读取实际数据的固定大小的缓冲区。

    至于在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(...));
    

    希望这会有所帮助!

    【讨论】:

      【解决方案2】:

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

      asio::streambuf buff;
      asio::read_until(source, buff, '\r');  // for example
      
      istream is(&buff);
      is >> targetstring;
      

      当然,这是一个数据副本,但如果你想将它放在一个字符串中,那就是你需要做的。

      【讨论】:

      • 这是正确答案。 Boost.Asio 可以读取许多缓冲区类型,并写入许多固定大小的缓冲区,但asio::streambuf 是唯一可以使用的可变大小缓冲区。正如 MikeC 正确指出的那样,这是一个数据副本,但 std::string 无论如何都会在其增长时具有内部副本。请注意,提取方法operator&gt;&gt; 是通常的空白解析方法。来自 iostream。您也可以在istream 上拨打getline
      【解决方案3】:

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

      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 足够大以接受您期望的数据包并用零填充。

      以及为什么要NEVER在对象可以删除的情况下将成员函数回调绑定到this指针,更完整的描述和更健壮的方法可以在这里找到:@ 987654321@.

      【讨论】:

        【解决方案4】:

        Boost Asio 有两种类型的缓冲区。有boost::asio::buffer(your_data_structure),它不能增长,因此对于未知输入通常无用,还有boost::asio::streambuf 可以增长。

        给定一个boost::asio::streambuf buf,你可以用std::string(std::istreambuf_iterator&lt;char&gt;(&amp;buf), {});把它变成一个字符串。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多