Boost::Asio Async write failed

2019-02-26 15:55发布

I am porting an application which uses Boost::Asio to an embedded system.

I have already cross-compiled boost 1.57.0 binaries for the board using its BSP. To test the libraries working, I ran two http server examples that use synchronized and asynchronized writing respectively.

The Sync version runs fine; while the Async one failed at writing. It returned error "Operation canceled".

Can anyone point out where I should look for? Thanks.

/*
 * Boost::Asio async example
 */
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/smart_ptr.hpp>

using namespace boost::asio;
using boost::system::error_code;
using ip::tcp;

struct CHelloWorld_Service
{
        CHelloWorld_Service(io_service &iosev)
                :m_iosev(iosev),m_acceptor(iosev, tcp::endpoint(tcp::v4(), 1000))
        {}

        void start()
        {
                boost::shared_ptr<tcp::socket> psocket(new tcp::socket(m_iosev));
                m_acceptor.async_accept(*psocket,
                        boost::bind(&CHelloWorld_Service::accept_handler, this, psocket, _1));
        }

        void accept_handler(boost::shared_ptr<tcp::socket> psocket, error_code ec)
        {
                if(ec) return;
                start();
                std::cout << psocket->remote_endpoint().address() << std::endl;
                boost::shared_ptr<std::string> pstr(new std::string("hello async world!"));
                psocket->async_write_some(buffer(*pstr),
                        boost::bind(&CHelloWorld_Service::write_handler, this, pstr, _1, _2));
        }

        void write_handler(boost::shared_ptr<std::string> pstr, error_code ec,
                size_t bytes_transferred)
        {
                if(ec)
                std::cout<< "Failed to send! " << boost::system::system_error(ec).what() << std::endl;
                else
                std::cout<< *pstr << " has been sent" << std::endl;
        }

        private:
                io_service &m_iosev;
                ip::tcp::acceptor m_acceptor;
};

int main(int argc, char* argv[])
{
        io_service iosev;
        CHelloWorld_Service sev(iosev);
        sev.start();
        iosev.run();

        return 0;
}

1条回答
▲ chillily
2楼-- · 2019-02-26 16:17

On your async_write_some call you forget to hold a reference to the socket instance.

This causes the socket object to be destructed, and as part of the destructor, all pending asynchronous operations are canceled. This explains that you receive ec operation_aborted.

Fix it either by adding the socket pointer to the bound arguments, or alternatively use the enable_shared_from_this idiom with your CSession type.

Using more shared_pointer magic:

Here's the "simplest" edit:

void write_handler(
        boost::shared_ptr<std::string> pstr, 
        boost::shared_ptr<tcp::socket> /*keepalive!*/, 
        error_code ec, size_t bytes_transferred) 
{
    if(ec)
        std::cout<< "Failed to send! " << boost::system::system_error(ec).what() << "\n";
    else
        std::cout<< *pstr << " has been sent (" << bytes_transferred << " bytes transferred)\n";
}

Which should be bound like:

    psocket->async_write_some(ba::buffer(*pstr),
            boost::bind(&CService::write_handler, this, pstr, psocket,
                ba::placeholders::error, ba::placeholders::bytes_transferred));

Live On Coliru

Several style improvements

  • not using namespace
  • using the asio placeholders (not _1, _2)

Prints:

g++ -std=c++11 -O2 -Wall -pedantic main.cpp -pthread -lboost_system -lboost_filesystem && ./a.out& while sleep .1; do nc 127.0.0.1 6767; done
127.0.0.1
hello async world!hello async world! has been sent (18 bytes transferred)
127.0.0.1
hello async world!hello async world! has been sent (18 bytes transferred)
127.0.0.1
hello async world!hello async world! has been sent (18 bytes transferred)
...

Using CSession (enable_shared_from_this)

This is the other idiom, and it avoid spelling out all the shared-pointers.

Instead of keeping spearate shared pointers to the socket and buffer, you make a class to contain both:

struct CSession : boost::enable_shared_from_this<CSession> {
    CSession(ba::io_service &iosev)
        :m_iosev(iosev), m_sock(m_iosev)
    {}

    void do_response();

  private:
    void write_handler(error_code ec, size_t bytes_transferred);

    ba::io_service &m_iosev;
    tcp::socket m_sock;
    std::string response;
};

And now the bind looks like:

boost::bind(&CSession::write_handler,
     shared_from_this(), /* keep-alive! */
     ba::placeholders::error, ba::placeholders::bytes_transferred)

Much simpler. Session management is the responsibility of the CService, like before:

void start()
{
    auto session = boost::make_shared<CSession>(m_iosev);
    m_acceptor.async_accept(session->m_sock,
            boost::bind(&CService::accept_handler, this, session, ba::placeholders::error));
}

void accept_handler(boost::shared_ptr<CSession> session, error_code ec) {
    if(ec) {
        std::cerr << "Accept failed: " << ec.message() << "\n";
    } else {
        session->do_response();
        start();
    }
}

Again Live On Coliru

#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>

namespace ba = boost::asio;
using boost::system::error_code;
using ba::ip::tcp;

namespace HelloWorld {

    struct CSession : boost::enable_shared_from_this<CSession> {
        CSession(ba::io_service &iosev)
            :m_iosev(iosev), m_sock(m_iosev)
        {}

        void do_response() {
            response = "hello async world!\n";
            std::cout << m_sock.remote_endpoint().address() << std::endl;

            m_sock.async_write_some(ba::buffer(response),
                    boost::bind(&CSession::write_handler,
                        shared_from_this(), /* keep-alive! */
                        ba::placeholders::error, ba::placeholders::bytes_transferred));
        }

      private:

        void write_handler(error_code ec, size_t bytes_transferred) 
        {
            if(ec)
                std::cout<< "Failed to send! " << boost::system::system_error(ec).what() << "\n";
            else
                std::cout<< response << " has been sent (" << bytes_transferred << " bytes transferred)\n";
        }

        ba::io_service &m_iosev;

        friend class CService;
        tcp::socket m_sock;

        std::string response;
    };

    struct CService
    {
        CService(ba::io_service &iosev)
            :m_iosev(iosev),m_acceptor(iosev, tcp::endpoint(tcp::v4(), 6767))
        {}

        void start() {
            auto session = boost::make_shared<CSession>(m_iosev);
            m_acceptor.async_accept(session->m_sock,
                    boost::bind(&CService::accept_handler, this, session, ba::placeholders::error));
        }

        void accept_handler(boost::shared_ptr<CSession> session, error_code ec) {
            if(ec) {
                std::cerr << "Accept failed: " << ec.message() << "\n";
            } else {
                session->do_response();
                start();
            }
        }

      private:
        ba::io_service &m_iosev;
        tcp::acceptor m_acceptor;
    };
}

int main() {
    ba::io_service iosev;

    using namespace HelloWorld;

    CService sev(iosev);
    sev.start();
    iosev.run();
}

With similar output.

查看更多
登录 后发表回答