• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/boostorg/beast
8 //
9 
10 //------------------------------------------------------------------------------
11 //
12 // Example: WebSocket server, coroutine
13 //
14 //------------------------------------------------------------------------------
15 
16 #include <boost/beast/core.hpp>
17 #include <boost/beast/websocket.hpp>
18 #include <boost/asio/spawn.hpp>
19 #include <algorithm>
20 #include <cstdlib>
21 #include <functional>
22 #include <iostream>
23 #include <memory>
24 #include <string>
25 #include <thread>
26 #include <vector>
27 
28 namespace beast = boost::beast;         // from <boost/beast.hpp>
29 namespace http = beast::http;           // from <boost/beast/http.hpp>
30 namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
31 namespace net = boost::asio;            // from <boost/asio.hpp>
32 using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
33 
34 //------------------------------------------------------------------------------
35 
36 // Report a failure
37 void
fail(beast::error_code ec,char const * what)38 fail(beast::error_code ec, char const* what)
39 {
40     std::cerr << what << ": " << ec.message() << "\n";
41 }
42 
43 // Echoes back all received WebSocket messages
44 void
do_session(websocket::stream<beast::tcp_stream> & ws,net::yield_context yield)45 do_session(
46     websocket::stream<beast::tcp_stream>& ws,
47     net::yield_context yield)
48 {
49     beast::error_code ec;
50 
51     // Set suggested timeout settings for the websocket
52     ws.set_option(
53         websocket::stream_base::timeout::suggested(
54             beast::role_type::server));
55 
56     // Set a decorator to change the Server of the handshake
57     ws.set_option(websocket::stream_base::decorator(
58         [](websocket::response_type& res)
59         {
60             res.set(http::field::server,
61                 std::string(BOOST_BEAST_VERSION_STRING) +
62                     " websocket-server-coro");
63         }));
64 
65     // Accept the websocket handshake
66     ws.async_accept(yield[ec]);
67     if(ec)
68         return fail(ec, "accept");
69 
70     for(;;)
71     {
72         // This buffer will hold the incoming message
73         beast::flat_buffer buffer;
74 
75         // Read a message
76         ws.async_read(buffer, yield[ec]);
77 
78         // This indicates that the session was closed
79         if(ec == websocket::error::closed)
80             break;
81 
82         if(ec)
83             return fail(ec, "read");
84 
85         // Echo the message back
86         ws.text(ws.got_text());
87         ws.async_write(buffer.data(), yield[ec]);
88         if(ec)
89             return fail(ec, "write");
90     }
91 }
92 
93 //------------------------------------------------------------------------------
94 
95 // Accepts incoming connections and launches the sessions
96 void
do_listen(net::io_context & ioc,tcp::endpoint endpoint,net::yield_context yield)97 do_listen(
98     net::io_context& ioc,
99     tcp::endpoint endpoint,
100     net::yield_context yield)
101 {
102     beast::error_code ec;
103 
104     // Open the acceptor
105     tcp::acceptor acceptor(ioc);
106     acceptor.open(endpoint.protocol(), ec);
107     if(ec)
108         return fail(ec, "open");
109 
110     // Allow address reuse
111     acceptor.set_option(net::socket_base::reuse_address(true), ec);
112     if(ec)
113         return fail(ec, "set_option");
114 
115     // Bind to the server address
116     acceptor.bind(endpoint, ec);
117     if(ec)
118         return fail(ec, "bind");
119 
120     // Start listening for connections
121     acceptor.listen(net::socket_base::max_listen_connections, ec);
122     if(ec)
123         return fail(ec, "listen");
124 
125     for(;;)
126     {
127         tcp::socket socket(ioc);
128         acceptor.async_accept(socket, yield[ec]);
129         if(ec)
130             fail(ec, "accept");
131         else
132             boost::asio::spawn(
133                 acceptor.get_executor(),
134                 std::bind(
135                     &do_session,
136                     websocket::stream<
137                         beast::tcp_stream>(std::move(socket)),
138                     std::placeholders::_1));
139     }
140 }
141 
main(int argc,char * argv[])142 int main(int argc, char* argv[])
143 {
144     // Check command line arguments.
145     if (argc != 4)
146     {
147         std::cerr <<
148             "Usage: websocket-server-coro <address> <port> <threads>\n" <<
149             "Example:\n" <<
150             "    websocket-server-coro 0.0.0.0 8080 1\n";
151         return EXIT_FAILURE;
152     }
153     auto const address = net::ip::make_address(argv[1]);
154     auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
155     auto const threads = std::max<int>(1, std::atoi(argv[3]));
156 
157     // The io_context is required for all I/O
158     net::io_context ioc(threads);
159 
160     // Spawn a listening port
161     boost::asio::spawn(ioc,
162         std::bind(
163             &do_listen,
164             std::ref(ioc),
165             tcp::endpoint{address, port},
166             std::placeholders::_1));
167 
168     // Run the I/O service on the requested number of threads
169     std::vector<std::thread> v;
170     v.reserve(threads - 1);
171     for(auto i = threads - 1; i > 0; --i)
172         v.emplace_back(
173         [&ioc]
174         {
175             ioc.run();
176         });
177     ioc.run();
178 
179     return EXIT_SUCCESS;
180 }
181