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

如何使用Boost::asio异步读取std::string?,string,boost,asynchronous,boost-asio,String,Boost,Asynchronous,Boost Asio,我正在学习Boost::asio和所有异步的东西。如何异步读取std::string类型的变量user\uuuBoost::asio::buffer(user)仅适用于async\u write(),而不适用于async\u read()。它与向量一起工作,那么它不与字符串一起工作的原因是什么?除了声明char user\uu[max\u len]和使用Boost::asio::buffer(user\uu,max\u len)之外,还有其他方法可以做到这一点吗 另外,从boost::从\u t

我正在学习Boost::asio和所有异步的东西。如何异步读取std::string类型的变量
user\uuu
Boost::asio::buffer(user)
仅适用于
async\u write()
,而不适用于
async\u read()
。它与向量一起工作,那么它不与字符串一起工作的原因是什么?除了声明
char user\uu[max\u len]
和使用
Boost::asio::buffer(user\uu,max\u len)
之外,还有其他方法可以做到这一点吗

另外,从
boost::从\u this
中启用\u shared\u,并在
async\u read()
async\u write()
中使用
shared\u from\u 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_;
        ...
};

Boost.Asio文档说明:

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

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

std::string
不允许对其缓冲区进行任意写入,因此
async_read
无法将内存块写入字符串的缓冲区(请注意,
std::string
确实允许调用方通过
data()对底层缓冲区进行只读访问)
方法,该方法确保返回的指针在下次调用非常量成员函数之前有效。因此,Asio可以轻松创建一个
const\u缓冲区
包装
std::string
,您可以将其与
async\u write
)一起使用

Asio文档中有一个简单的“聊天”程序的示例代码(请参阅),该程序有一个很好的解决此问题的方法。基本上,您首先需要让发送TCP沿着消息的大小发送,以“头”的形式进行排序,并且您的读取处理程序必须解释头以分配适合读取实际数据的固定大小的缓冲区

至于需要在
async\u read
async\u write
中使用
shared\u from\u this()
,原因是它保证了由
boost::bind
包装的方法将始终引用活动对象。考虑以下情况:

  • 您的
    handle\u accept
    方法调用
    async\u read
    并将一个处理程序“发送到reactor”-基本上,您已经要求
    io\u服务在完成从套接字读取数据时调用
    Connection::handle\u user\u read
    io_服务
    存储此函子并继续其循环,等待异步读取操作完成
  • 调用
    async\u read
    后,
    连接
    对象由于某种原因(程序终止、错误条件等)被解除分配
  • 假设
    io_服务
    现在确定异步读取已完成,在
    连接
    对象被解除分配之后,但
    io_服务
    被销毁之前(这可能发生,例如,如果
    io_服务::run
    在单独的线程中运行,这是典型的情况)。现在,
    io_服务
    尝试调用处理程序,它对
    连接
    对象的引用无效
  • 解决方案是通过
    shared_ptr
    分配
    Connection
    ,并使用
    shared_from_this()
    而不是
    this
    将处理程序发送到“reactor”中,这允许
    io_服务
    存储对对象的共享引用,而且
    shared\u 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));
            }
        }
        //...
    };
    

    希望这有帮助

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

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

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

    您可以将
    std:string
    async\\u read()
    一起使用,如下所示:

    async_read(socket_, boost::asio::buffer(&user_[0], user_.size()),
               boost::bind(&Connection::handle_user_read, this,
               placeholders::error, placeholders::bytes_transferred));
    
    但是,在调用
    async\\u read()
    之前,最好确保
    std::string
    足够大,可以接受您期望的数据包并用零填充


    至于为什么如果可以删除对象,就不应该将成员函数回调绑定到
    这个
    指针,可以在这里找到更完整的描述和更健壮的方法:。

    Boost Asio有两种类型的缓冲区。有
    boost::asio::buffer(您的\u数据\u结构)
    ,它不能增长,因此通常对未知输入无效,还有
    boost::asio::streambuf
    可以增长

    给定一个
    boost::asio::streambuf buf
    ,使用
    std::string(std::istreambuf_迭代器(&buf),{})将其转换为字符串


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

    这是正确答案。Asio可以读取许多缓冲区类型,也可以写入许多固定大小的缓冲区,但它将使用的唯一可变大小的缓冲区是
    Asio::streambuf
    。正如MikeC正确指出的那样,这是一个数据副本,但是
    std::string
    在增长时具有内部副本。请注意,提取方法
    操作符>>
    是iostream中常用的空白解析方法。您还可以在
    istream上调用
    getline
    async_read(socket_, boost::asio::buffer(&user_[0], user_.size()),
               boost::bind(&Connection::handle_user_read, this,
               placeholders::error, placeholders::bytes_transferred));