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/detached.hpp>
19#include <boost/asio/spawn.hpp>
20#include <algorithm>
21#include <cstdlib>
22#include <functional>
23#include <iostream>
24#include <memory>
25#include <string>
26#include <thread>
27#include <vector>
28
29namespace beast = boost::beast; // from <boost/beast.hpp>
30namespace http = beast::http; // from <boost/beast/http.hpp>
31namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
32namespace net = boost::asio; // from <boost/asio.hpp>
33using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
34
35//------------------------------------------------------------------------------
36
37// Report a failure
38void
39fail(beast::error_code ec, char const* what)
40{
41 std::cerr << what << ": " << ec.message() << "\n";
42}
43
44// Echoes back all received WebSocket messages
45void
46do_session(
47 websocket::stream<beast::tcp_stream>& ws,
48 net::yield_context yield)
49{
50 beast::error_code ec;
51
52 // Set suggested timeout settings for the websocket
53 ws.set_option(
54 websocket::stream_base::timeout::suggested(
55 role: beast::role_type::server));
56
57 // Set a decorator to change the Server of the handshake
58 ws.set_option(websocket::stream_base::decorator(
59 [](websocket::response_type& res)
60 {
61 res.set(name: http::field::server,
62 value: std::string(BOOST_BEAST_VERSION_STRING) +
63 " websocket-server-coro");
64 }));
65
66 // Accept the websocket handshake
67 ws.async_accept(handler: yield[ec]);
68 if(ec)
69 return fail(ec, what: "accept");
70
71 for(;;)
72 {
73 // This buffer will hold the incoming message
74 beast::flat_buffer buffer;
75
76 // Read a message
77 ws.async_read(buffer, handler: yield[ec]);
78
79 // This indicates that the session was closed
80 if(ec == websocket::error::closed)
81 break;
82
83 if(ec)
84 return fail(ec, what: "read");
85
86 // Echo the message back
87 ws.text(value: ws.got_text());
88 ws.async_write(bs: buffer.data(), handler: yield[ec]);
89 if(ec)
90 return fail(ec, what: "write");
91 }
92}
93
94//------------------------------------------------------------------------------
95
96// Accepts incoming connections and launches the sessions
97void
98do_listen(
99 net::io_context& ioc,
100 tcp::endpoint endpoint,
101 net::yield_context yield)
102{
103 beast::error_code ec;
104
105 // Open the acceptor
106 tcp::acceptor acceptor(ioc);
107 acceptor.open(protocol: endpoint.protocol(), ec);
108 if(ec)
109 return fail(ec, what: "open");
110
111 // Allow address reuse
112 acceptor.set_option(option: net::socket_base::reuse_address(true), ec);
113 if(ec)
114 return fail(ec, what: "set_option");
115
116 // Bind to the server address
117 acceptor.bind(endpoint, ec);
118 if(ec)
119 return fail(ec, what: "bind");
120
121 // Start listening for connections
122 acceptor.listen(backlog: net::socket_base::max_listen_connections, ec);
123 if(ec)
124 return fail(ec, what: "listen");
125
126 for(;;)
127 {
128 tcp::socket socket(ioc);
129 acceptor.async_accept(peer&: socket, token: yield[ec]);
130 if(ec)
131 fail(ec, what: "accept");
132 else
133 boost::asio::spawn(
134 ex: acceptor.get_executor(),
135 function: std::bind(
136 f: &do_session,
137 args: websocket::stream<
138 beast::tcp_stream>(std::move(socket)),
139 args: std::placeholders::_1),
140 // we ignore the result of the session,
141 // most errors are handled with error_code
142 token: boost::asio::detached);
143 }
144}
145
146int main(int argc, char* argv[])
147{
148 // Check command line arguments.
149 if (argc != 4)
150 {
151 std::cerr <<
152 "Usage: websocket-server-coro <address> <port> <threads>\n" <<
153 "Example:\n" <<
154 " websocket-server-coro 0.0.0.0 8080 1\n";
155 return EXIT_FAILURE;
156 }
157 auto const address = net::ip::make_address(str: argv[1]);
158 auto const port = static_cast<unsigned short>(std::atoi(nptr: argv[2]));
159 auto const threads = std::max<int>(a: 1, b: std::atoi(nptr: argv[3]));
160
161 // The io_context is required for all I/O
162 net::io_context ioc(threads);
163
164 // Spawn a listening port
165 boost::asio::spawn(ctx&: ioc,
166 function: std::bind(
167 f: &do_listen,
168 args: std::ref(t&: ioc),
169 args: tcp::endpoint{address, port},
170 args: std::placeholders::_1),
171 // on completion, spawn will call this function
172 token: [](std::exception_ptr ex)
173 {
174 // if an exception occurred in the coroutine,
175 // it's something critical, e.g. out of memory
176 // we capture normal errors in the ec
177 // so we just rethrow the exception here,
178 // which will cause `ioc.run()` to throw
179 if (ex)
180 std::rethrow_exception(ex);
181 });
182
183 // Run the I/O service on the requested number of threads
184 std::vector<std::thread> v;
185 v.reserve(n: threads - 1);
186 for(auto i = threads - 1; i > 0; --i)
187 v.emplace_back(
188 args: [&ioc]
189 {
190 ioc.run();
191 });
192 ioc.run();
193
194 return EXIT_SUCCESS;
195}
196

source code of boost/libs/beast/example/websocket/server/coro/websocket_server_coro.cpp