boost :: asio :: strand无法使async_write线程安全

时间:2019-07-13 00:37:57

标签: c++11 boost-asio

我有一个Websocket服务器,该服务器使用以下命令将消息写入客户端

ws_.async_write(
    boost::asio::buffer( msg, msg.length()),
    boost::asio::bind_executor(
        strand_,
        std::bind(
            &cSession::on_write,
            shared_from_this(),
            std::placeholders::_1,
            std::placeholders::_2)));

strand_在CTOR strand_(make_strand(ioc))中初始化。asio文档指出,这应该使写入线程安全。

在其他两个线程中运行,我有消息生成器,每三秒钟发送一次消息。因此,websocket每三秒钟就会尝试几乎同时执行两个async_write调用。在几次尝试或有时是第一次尝试之后,断言失败

Assertion failed!
    Program: C:\Users\James\code\echoer\bin\WProcessServer.exe
File: C:\Users\James\code\boost\boost_1_70_0/boost/beast/websocket/detail/soft_mutex.hpp, Line 75

Expression: id_ == T::id

源代码说

    // If this assert goes off it means you are attempting to
    // simultaneously initiate more than one of same asynchronous
    // operation, which is not allowed. For example, you must wait
    // for an async_read to complete before performing another
    // async_read.

因此,我对strand的使用并未使服务器线程变得安全。为什么不呢?

以下是演示该问题的完整的最小程序的代码:

    #include <iostream>
    #include <algorithm>
    #include <thread>

    #include <boost/beast/core.hpp>
    #include <boost/beast/websocket.hpp>
    #include <boost/asio/ip/tcp.hpp>
    #include <boost/asio/strand.hpp>
    #include <boost/asio/bind_executor.hpp>

    using tcp = boost::asio::ip::tcp;               // from <boost/asio/ip/tcp.hpp>

    class cServer;

    // Report a failure
    void
    fail(boost::system::error_code ec, char const* what)
    {
        std::cerr << what << ": " << ec.message() << "\n";
    }

    /** Send messages at regular intervals to client from own thread

     Used to test the server is thread safe
    */

    class cMessenger
    {
    public:
        /** CTOR
            @param[in] io  io context
        */
        cMessenger(
            boost::asio::io_context& ioc,
            cServer& myServer,
            int id
        );

        /// Startup ( never returns - call in own thread )
        void Run();

        /// Schedule next message
        void Schedule();

        /// Send Message, then schedule next
        void onTimer();

    private:
        std::thread myThread;
        boost::asio::steady_timer myTimer;     /// timer controlling when massages are sent
        boost::asio::io_context& myIOC;
        cServer& myServer;
        int myID;
    };

    /// Websocket connection
    class cSession : public std::enable_shared_from_this<cSession>
    {
        /** The stream ( actually a TCP socket ) used to communicate with the client */
        boost::beast::websocket::stream<tcp::socket> ws_;

        /** The strand used to synchronize writes to the client
        Prevents a new write starting on the socket until previous write completes
        */
        boost::asio::strand<
        boost::asio::io_context::executor_type> strand_;

        /** Buffer storage for incoming messages from client */
        boost::beast::multi_buffer buffer_;

        cServer * myWebSocket;

    public:
        // Take ownership of the socket
        explicit
        cSession(
            boost::asio::io_context& ioc,
            tcp::socket socket,
            cServer * webSocket )
            : ws_(std::move(socket))
            , strand_(make_strand(ioc))
            , myWebSocket( webSocket )
        {
        }

        /** Start the asynchronous operation */
        void run();

        /** Handle websocket handshake completion */
        void on_accept(boost::system::error_code ec);

        /** Wait for next message from client */
        void do_read();

        /** Handle reception of message from client */
        void on_read(
            boost::system::error_code ec,
            std::size_t bytes_transferred);

        /** Write message to connection that came from elsewhere */
        void Write( const std::string& msg );

        /** Handle completion of write message from elsewhere */
        void on_write(
            boost::system::error_code ec,
            std::size_t bytes_transferred)
        {
            // Clear the buffer
            buffer_.consume(buffer_.size());
            //do_read();
        }
    };
    /// Accepts incoming connections and launches the sessions
    class cListener : public std::enable_shared_from_this<cListener>
    {
        boost::asio::io_context& ioc;        // io context
        boost::asio::ip::tcp::tcp::acceptor acceptor_;
        boost::asio::ip::tcp::tcp::socket socket_;
        cServer * myServer;

    public:
        cListener(
            boost::asio::io_context& ioc,
            boost::asio::ip::tcp::tcp::endpoint endpoint );

        void Set( cServer* server )
        {
            myServer = server;
        }

        /// Start accepting incoming connections
        void run()
        {
            if(! acceptor_.is_open())
                return;
            do_accept();
        }

        /// wait for client connection request
        void do_accept();

        /// handle a client connection request
        void on_accept(boost::system::error_code ec);
    };

    /** A process Server */
    class cServer
    {
    public:
        /** CTOR
            @param[in] port to listen for client connections

            Runs in its own thread
            Starts listening on port for client connections
            Starts boost asio io_context
        */
        cServer(
            boost::asio::io_context& ioc,
            const std::string& port );

        /** Returns when thread ends */
        void Join();

        /** New connection to client */
        void Set( cSession * session );

        /** Client connection lost */
        void SessionClosed();

        /** Receive message from the client
            @param[in] msg
        */
        void ClientMsg( const std::string& msg );

        /** Send message to client
            @param[in] msg
            @param[in] store true if message should be stored for client recconection, default true
        The message will be sent to client

        */
        void SendToClient(
            const std::string& msg,
            bool store = true );

        /// Get IO Context
        boost::asio::io_context& IOC()
        {
            return myIOC;
        }

    private:
        boost::asio::io_context& myIOC;
        unsigned short myPort;
        std::thread myThread;
        std::shared_ptr<cListener> myListener;
        cSession * mySession;

        void Run();
    };



    cListener::cListener(
        boost::asio::io_context& ioc_ref,
        tcp::endpoint endpoint )
        : ioc( ioc_ref )
        , acceptor_(ioc_ref)
        , socket_(ioc_ref)
    {
        boost::system::error_code ec;

        // Open the acceptor
        acceptor_.open(endpoint.protocol(), ec);
        if(ec)
        {
            fail(ec, "open");
            return;
        }

        // Allow address reuse
        acceptor_.set_option(boost::asio::socket_base::reuse_address(true));
        if(ec)
        {
            fail(ec, "set_option");
            return;
        }

        // Bind to the server address
        acceptor_.bind(endpoint, ec);
        if(ec)
        {
            fail(ec, "bind");
            return;
        }

        // Start listening for connections
        acceptor_.listen(
            boost::asio::socket_base::max_listen_connections, ec);
        if(ec)
        {
            fail(ec, "listen");
            return;
        }
    }

    void cListener::do_accept()
    {
        acceptor_.async_accept(
            socket_,
            std::bind(
                &cListener::on_accept,
                shared_from_this(),
                std::placeholders::_1));
    }

    void cListener::on_accept(boost::system::error_code ec)
    {
        if(ec)
        {
            fail(ec, "accept");
        }
        else
        {
            // Create the session
            auto s = std::make_shared<cSession>(
                         ioc,
                         std::move(socket_),
                         myServer );

            // run the session
            s->run();
        }
    }

    void cSession::run()
    {
        // Accept the websocket handshake
        ws_.async_accept(
            boost::asio::bind_executor(
                strand_,
                std::bind(
                    &cSession::on_accept,
                    shared_from_this(),
                    std::placeholders::_1)));
    }

    void cSession::on_accept(boost::system::error_code ec)
    {
        if(ec)
            return fail(ec, "accept");

        // let websocket know connection is up and running
        myWebSocket->Set( this );

        // Wait for first message from client
        //do_read();
    }

    void cSession::do_read()
    {
        // Read a message into our buffer
        ws_.async_read(
            buffer_,
            boost::asio::bind_executor(
                strand_,
                std::bind(
                    &cSession::on_read,
                    shared_from_this(),
                    std::placeholders::_1,
                    std::placeholders::_2)));
    }

    void cSession::on_read(
        boost::system::error_code ec,
        std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);

        // This indicates that the session was closed
        if(ec == boost::beast::websocket::error::closed)
        {
            myWebSocket->SessionClosed();
            return;
        }

        if(ec)
        {
            myWebSocket->SessionClosed();
            return;
        }


        std::cout << "rcvd from client " << boost::beast::buffers_to_string(buffer_.data()) << "\n";

        // ???
        ws_.text(ws_.got_text());

        // wait for next message
        do_read();

    }

    void cSession::Write(
        const std::string& msg )
    {
        ws_.async_write(
            boost::asio::buffer( msg, msg.length()),
            boost::asio::bind_executor(
                strand_,
                std::bind(
                    &cSession::on_write,
                    shared_from_this(),
                    std::placeholders::_1,
                    std::placeholders::_2)));
    }

    cServer::cServer(
        boost::asio::io_context& ioc,
        const std::string& port )
        : myIOC( ioc )
        , myPort( static_cast<unsigned short>(std::atoi(port.c_str())) )
        , mySession( 0 )
    {
        std::cout << "Server starting...";
        myThread = std::thread(
                       &cServer::Run,
                       this );
        std::cout << "Server started\n";
    }
    void cServer::Run()
    {
        // Create and launch a listening port
        myListener = std::make_shared<cListener>(
                         myIOC,
                         tcp::endpoint
        {
            boost::asio::ip::make_address("0.0.0.0"),
            myPort
        } );
        myListener->Set( this );
        myListener->run();

        // start event manager
        myIOC.run();
    }

    void cServer::Join()
    {
        myThread.join();
    }

    void cServer::Set( cSession * session )
    {
        std::cout << "New connection from client\n";

        mySession = session;

        mySession->do_read();
    }

    void cServer::SessionClosed()
    {
        std::cout << "Client connection lost\n";

        mySession = 0;

        // listen for a reconnection
        myListener->do_accept();
    }

    void cServer::ClientMsg( const std::string& msg )
    {
    }

    void cServer::SendToClient(
        const std::string& msg,
        bool store )
    {
        //std::cout << "SendToClient: " << msg << "\n";
        if( ! mySession )
        {

        }
        else
        {
            mySession->Write( msg );
        }
    }

    cMessenger::cMessenger(
        boost::asio::io_context& ioc,
        cServer& server,
        int id  )
        : myTimer( ioc )
        , myIOC( ioc )
        , myServer( server )
        , myID( id )
    {
        //std::cout << "Messenger starting ...";
        myThread = std::thread(
                       &cMessenger::Run,
                       this );
    }
    void cMessenger::Run()
    {
        std::cout << "Messenger is running in its own thread\n";
        Schedule();
        myIOC.run();
    }
    void cMessenger::Schedule()
    {
        myTimer.expires_after( std::chrono::seconds(3) );
        myTimer.async_wait(std::bind(&cMessenger::onTimer, this ));
    }

    void cMessenger::onTimer()
    {
        //std::cout << " cMessenger::onTimer\n";

        myServer.SendToClient(
            "Hello World from thread " + std::to_string( myID ),
            false );

        Schedule();
    }


    int main(int argc, char* argv[])
    {
        boost::asio::io_context ioc( 3 );

        cServer Server(
            ioc,
            "8080"
        );

        cMessenger Messenger1(
            ioc,
            Server,
            1 );

        cMessenger Messenger2(
            ioc,
            Server,
            2 );

        Server.Join();

    }


2 个答案:

答案 0 :(得分:3)

1e-10在单独的线程上运行,并调用minimize(无同步):

onTimer

SendToClient仅调用void cMessenger::onTimer() { // std::cout << " cMessenger::onTimer\n"; myServer.SendToClient("Hello World from thread " + std::to_string(myID), false); ,仍然没有同步:

SendToClient

Write实际上只是在不同步的情况下调用void cServer::SendToClient(const std::string &msg, bool store) { // std::cout << "SendToClient: " << msg << "\n"; if (!mySession) { } else { mySession->Write(msg); } }

Write

对子链的保护仅适用于对其执行的任何操作。

您永远不要显式地对链执行任何操作,而只能在其上包装完成处理程序。

这意味着您启动的其他未同步操作(非回调)仍然可以同时运行。

一种解决方法是async_write您忘记同步的其他操作。另一个办法是仅从已经同步的上下文中仔细执行它们。

答案 1 :(得分:0)

strand.dispatch()使用lambdas进行I / O调用,例如

// Dispatch the send handler
auto self(this->shared_from_this());
auto send_handler = [this, self]()
{
    // Try to send the main buffer
    TrySend();
};
_strand.dispatch(send_handler);




void TrySend()
{
///...        _socket.async_write_some(asio::buffer(_send_buffer_flush.data() + _send_buffer_flush_offset, _send_buffer_flush.size() - _send_buffer_flush_offset), bind_executor(_strand, async_write_handler));
     }