// // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Official repository: https://github.com/boostorg/beast // //------------------------------------------------------------------------------ // // Example: WebSocket server, coroutine // //------------------------------------------------------------------------------ #include #include #include #include #include #include #include #include #include #include #include namespace beast = boost::beast; // from namespace http = beast::http; // from namespace websocket = beast::websocket; // from namespace net = boost::asio; // from using tcp = boost::asio::ip::tcp; // from //------------------------------------------------------------------------------ // Report a failure void fail(beast::error_code ec, char const* what) { std::cerr << what << ": " << ec.message() << "\n"; } // Echoes back all received WebSocket messages void do_session( websocket::stream& ws, net::yield_context yield) { beast::error_code ec; // Set suggested timeout settings for the websocket ws.set_option( websocket::stream_base::timeout::suggested( beast::role_type::server)); // Set a decorator to change the Server of the handshake ws.set_option(websocket::stream_base::decorator( [](websocket::response_type& res) { res.set(http::field::server, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-server-coro"); })); // Accept the websocket handshake ws.async_accept(yield[ec]); if(ec) return fail(ec, "accept"); for(;;) { // This buffer will hold the incoming message beast::flat_buffer buffer; // Read a message ws.async_read(buffer, yield[ec]); // This indicates that the session was closed if(ec == websocket::error::closed) break; if(ec) return fail(ec, "read"); // Echo the message back ws.text(ws.got_text()); ws.async_write(buffer.data(), yield[ec]); if(ec) return fail(ec, "write"); } } //------------------------------------------------------------------------------ // Accepts incoming connections and launches the sessions void do_listen( net::io_context& ioc, tcp::endpoint endpoint, net::yield_context yield) { beast::error_code ec; // Open the acceptor tcp::acceptor acceptor(ioc); acceptor.open(endpoint.protocol(), ec); if(ec) return fail(ec, "open"); // Allow address reuse acceptor.set_option(net::socket_base::reuse_address(true), ec); if(ec) return fail(ec, "set_option"); // Bind to the server address acceptor.bind(endpoint, ec); if(ec) return fail(ec, "bind"); // Start listening for connections acceptor.listen(net::socket_base::max_listen_connections, ec); if(ec) return fail(ec, "listen"); for(;;) { tcp::socket socket(ioc); acceptor.async_accept(socket, yield[ec]); if(ec) fail(ec, "accept"); else boost::asio::spawn( acceptor.get_executor(), std::bind( &do_session, websocket::stream< beast::tcp_stream>(std::move(socket)), std::placeholders::_1)); } } int main(int argc, char* argv[]) { // Check command line arguments. if (argc != 4) { std::cerr << "Usage: websocket-server-coro
\n" << "Example:\n" << " websocket-server-coro 0.0.0.0 8080 1\n"; return EXIT_FAILURE; } auto const address = net::ip::make_address(argv[1]); auto const port = static_cast(std::atoi(argv[2])); auto const threads = std::max(1, std::atoi(argv[3])); // The io_context is required for all I/O net::io_context ioc(threads); // Spawn a listening port boost::asio::spawn(ioc, std::bind( &do_listen, std::ref(ioc), tcp::endpoint{address, port}, std::placeholders::_1)); // Run the I/O service on the requested number of threads std::vector v; v.reserve(threads - 1); for(auto i = threads - 1; i > 0; --i) v.emplace_back( [&ioc] { ioc.run(); }); ioc.run(); return EXIT_SUCCESS; }