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

前端 未结 4 1678
野性不改
野性不改 2020-12-08 11:55

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_)

4条回答
  •  心在旅途
    2020-12-08 11:58

    The Boost.Asio documentation states:

    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.

    This means that in order for a call to async_read to 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.

    std::string does not allow arbitrary writes into its buffer, so async_read cannot write chunks of memory into a string's buffer (note that std::string does give 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_buffer wrapping an std::string, and you can use it with 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.

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

    1. Your handle_accept method calls async_read and sends a handler "into the reactor" - basically you've asked the io_service to invoke Connection::handle_user_read when it finishes reading data from the socket. The io_service stores this functor and continues its loop, waiting for the asynchronous read operation to complete.
    2. After your call to async_read, the Connection object is deallocated for some reason (program termination, an error condition, etc.)
    3. Suppose the io_service now determines that the asynchronous read is complete, after the Connection object has been deallocated but before the io_service is destroyed (this can occur, for example, if io_service::run is running in a separate thread, as is typical). Now, the io_service attempts to invoke the handler, and it has an invalid reference to a Connection object.

    The solution is to allocate Connection via a shared_ptr and use shared_from_this() instead of this when sending a handler "into the reactor" - this allows io_service to store a shared reference to the object, and shared_ptr guarantees that it won't be deallocated until the last reference expires.

    So, your code should probably look something like:

    class Connection : public boost::enable_shared_from_this
    {
    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 Connection object is allocated via a shared_ptr, e.g.:

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

    Hope this helps!

提交回复
热议问题