|
| 1 | +Utility Server Example Application Tutorial |
| 2 | +=========================================== |
| 3 | + |
| 4 | +Introduction |
| 5 | +------------ |
| 6 | + |
| 7 | +This tutorial provides a step by step discussion of building a basic WebSocket++ server. The final product of this tutorial is the utility_server example application from the example section. This server demonstrates the following features: |
| 8 | + |
| 9 | +- Use Asio Transport for networking |
| 10 | +- Accept multiple WebSocket connections at once |
| 11 | +- Read incoming messages and perform a few basic actions (echo, broadcast, telemetry, server commands) based on the path |
| 12 | +- Use validate handler to reject connections to invalid paths |
| 13 | +- Serve basic HTTP responses with the http handler |
| 14 | +- Gracefully exit the server |
| 15 | +- Encrypt connections with TLS |
| 16 | + |
| 17 | +This tutorial is current as of the 0.6.x version of the library. |
| 18 | + |
| 19 | +Chapter 1: Initial Setup & Basics |
| 20 | +--------------------------------- |
| 21 | + |
| 22 | +### Step 1 |
| 23 | + |
| 24 | +_Add WebSocket++ includes and set up a a server endpoint type._ |
| 25 | + |
| 26 | +WebSocket++ includes two major object types. The endpoint and the connection. The |
| 27 | +endpoint creates and launches new connections and maintains default settings for |
| 28 | +those connections. Endpoints also manage any shared network resources. |
| 29 | + |
| 30 | +The connection stores information specific to each WebSocket session. |
| 31 | + |
| 32 | +> **Note:** Once a connection is launched, there is no link between the endpoint and the connection. All default settings are copied into the new connection by the endpoint. Changing default settings on an endpoint will only affect future connections. |
| 33 | +Connections do not maintain a link back to their associated endpoint. Endpoints do not maintain a list of outstanding connections. If your application needs to iterate over all connections it will need to maintain a list of them itself. |
| 34 | + |
| 35 | +WebSocket++ endpoints are built by combining an endpoint role with an endpoint config. There are two different types of endpoint roles, one each for the client and server roles in a WebSocket session. This is a server tutorial so we will use the server role `websocketpp::server` which is provided by the `<websocketpp/server.hpp>` header. |
| 36 | + |
| 37 | +> #### Terminology: Endpoint Config |
| 38 | +> WebSocket++ endpoints have a group of settings that may be configured at compile time via the `config` template parameter. A config is a struct that contains types and static constants that are used to produce an endpoint with specific properties. Depending on which config is being used the endpoint will have different methods available and may have additional third party dependencies. |
| 39 | +
|
| 40 | +The endpoint role takes a template parameter called `config` that is used to configure the behavior of endpoint at compile time. For this example we are going to use a default config provided by the library called `asio`, provided by `<websocketpp/config/asio_no_tls.hpp>`. This is a server config that uses the Asio library to provide network transport and does not support TLS based security. Later on we will discuss how to introduce TLS based security into a WebSocket++ application, more about the other stock configs, and how to build your own custom configs. |
| 41 | + |
| 42 | +Combine a config with an endpoint role to produce a fully configured endpoint. This type will be used frequently so I would recommend a typedef here. |
| 43 | + |
| 44 | +`typedef websocketpp::server<websocketpp::config::asio> server` |
| 45 | + |
| 46 | +#### `utility_server` constructor |
| 47 | + |
| 48 | +This endpoint type will be the base of the utility_server object that will keep track of the state of the server. Within the `utility_server` constructor several things happen: |
| 49 | + |
| 50 | +First, we adjust the endpoint logging behavior to include all error logging channels and all access logging channels except the frame payload, which is particularly noisy and generally useful only for debugging. [TODO: link to more information about logging] |
| 51 | + |
| 52 | +~~~{.cpp} |
| 53 | +m_endpoint.set_error_channels(websocketpp::log::elevel::all); |
| 54 | +m_endpoint.set_access_channels(websocketpp::log::alevel::all ^ websocketpp::log::alevel::frame_payload); |
| 55 | +~~~ |
| 56 | + |
| 57 | +Next, we initialize the transport system underlying the endpoint. This method is specific to the Asio transport not WebSocket++ core. It will not be necessary or present in endpoints that use a non-asio config. |
| 58 | + |
| 59 | +> **Note:** This example uses an internal Asio `io_service` that is managed by the endpoint itself. This is a simple arrangement suitable for programs where WebSocket++ is the only code using Asio. If you have an existing program that already manages an `io_service` object or want to build a new program where WebSocket++ handlers share an io_service with other handlers you can pass the `io_service` you want WebSocket++ to register its handlers on to the `init_asio()` method and it will use it instead of generating and managing its own. [TODO: FAQ link instead?] |
| 60 | +
|
| 61 | +~~~{.cpp} |
| 62 | +m_endpoint.init_asio(); |
| 63 | +~~~ |
| 64 | + |
| 65 | +#### `utility_server::run` method |
| 66 | + |
| 67 | +In addition to the constructor, we also add a run method that sets up the listening socket, begins accepting connections, starts the Asio io_service event loop. |
| 68 | + |
| 69 | +~~~{.cpp} |
| 70 | +// Listen on port 9002 |
| 71 | +m_endpoint.listen(9002); |
| 72 | +
|
| 73 | +// Queues a connection accept operation |
| 74 | +m_endpoint.start_accept(); |
| 75 | +
|
| 76 | +// Start the Asio io_service run loop |
| 77 | +m_endpoint.run(); |
| 78 | +~~~ |
| 79 | + |
| 80 | +The final line, `m_endpoint.run();`, will block until the endpoint is instructed to stop listening for new connections. While running it will listen for and process new connections as well as accept and process new data and control messages for existing connections. WebSocket++ uses Asio in an asyncronous mode where multiple connections can be similtaneously serviced efficiently within a single thread. |
| 81 | + |
| 82 | +#### Build |
| 83 | +Adding WebSocket++ has added a few dependencies to our program that must be addressed in the build system. Firstly, the WebSocket++ library headers need must be in the include search path of your build system. How exactly this is done depends on where you have the WebSocket++ headers installed what build system you are using. |
| 84 | + |
| 85 | +For the rest of this tutorial we are going to assume a C++11 build environment. WebSocket++ will work with pre-C++11 systems if your build system has access to a recent version of the Boost library headers. |
| 86 | + |
| 87 | +Finally, to use the Asio transport config we need to bring in the Asio library. There are two options here. If you have access to a C++11 build environment the standalone version from http://think-async.com is a good option. This header only library does not bring in any special dependencies and ensures you have the latest version of Asio. If you do not have a C++11 build environment or already have brought in the Boost libraries you can also use the version of Asio bundled with Boost. |
| 88 | + |
| 89 | +To use standalone Asio, make sure the Asio headers are in your include path and define ASIO_STANDALONE. To use Boost Asio, make sure the Boost headers are in your include path and that you are linking to the boost_system library. |
| 90 | + |
| 91 | +`c++ -std=c++11 step1.cpp` (Asio Standalone) |
| 92 | +OR |
| 93 | +`c++ -std=c++11 step1.cpp -lboost_system` (Boost Asio) |
| 94 | + |
| 95 | +#### Code so far |
| 96 | +```cpp |
| 97 | +// The ASIO_STANDALONE define is necessary to use the standalone version of Asio. |
| 98 | +// Remove if you are using Boost Asio. |
| 99 | +#define ASIO_STANDALONE |
| 100 | + |
| 101 | +#include <websocketpp/config/asio_no_tls.hpp> |
| 102 | +#include <websocketpp/server.hpp> |
| 103 | + |
| 104 | +#include <functional> |
| 105 | + |
| 106 | +typedef websocketpp::server<websocketpp::config::asio> server; |
| 107 | + |
| 108 | +class utility_server { |
| 109 | +public: |
| 110 | + utility_server() { |
| 111 | + // Set logging settings |
| 112 | + m_endpoint.set_error_channels(websocketpp::log::elevel::all); |
| 113 | + m_endpoint.set_access_channels(websocketpp::log::alevel::all ^ websocketpp::log::alevel::frame_payload); |
| 114 | + |
| 115 | + // Initialize Asio |
| 116 | + m_endpoint.init_asio(); |
| 117 | + } |
| 118 | + |
| 119 | + void run() { |
| 120 | + // Listen on port 9002 |
| 121 | + m_endpoint.listen(9002); |
| 122 | + |
| 123 | + // Queues a connection accept operation |
| 124 | + m_endpoint.start_accept(); |
| 125 | + |
| 126 | + // Start the Asio io_service run loop |
| 127 | + m_endpoint.run(); |
| 128 | + } |
| 129 | +private: |
| 130 | + server m_endpoint; |
| 131 | +}; |
| 132 | + |
| 133 | +int main() { |
| 134 | + utility_server s; |
| 135 | + s.run(); |
| 136 | + return 0; |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +### Step 2 |
| 141 | + |
| 142 | +_Set up a message handler to echo all replies back to the original user_ |
| 143 | + |
| 144 | +#### Setting a message handler |
| 145 | + |
| 146 | +> ###### Terminology: Registering handlers |
| 147 | +> WebSocket++ provides a number of execution points where you can register to have a handler run. Which of these points are available to your endpoint will depend on its config. TLS handlers will not exist on non-TLS endpoints for example. A complete list of handlers can be found at http://www.zaphoyd.com/websocketpp/manual/reference/handler-list. |
| 148 | +> |
| 149 | +> Handlers can be registered at the endpoint level and at the connection level. Endpoint handlers are copied into new connections as they are created. Changing an endpoint handler will affect only future connections. Handlers registered at the connection level will be bound to that specific connection only. |
| 150 | +> |
| 151 | +> The signature of handler binding methods is the same for endpoints and connections. The format is: `set_*_handler(...)`. Where * is the name of the handler. For example, `set_open_handler(...)` will set the handler to be called when a new connection is open. `set_fail_handler(...)` will set the handler to be called when a connection fails to connect. |
| 152 | +> |
| 153 | +> All handlers take one argument, a callable type that can be converted to a `std::function` with the correct count and type of arguments. You can pass free functions, functors, and Lambdas with matching argument lists as handlers. In addition, you can use `std::bind` (or `boost::bind`) to register functions with non-matching argument lists. This is useful for passing additional parameters not present in the handler signature or member functions that need to carry a 'this' pointer. |
| 154 | +> |
| 155 | +> The function signature of each handler can be looked up in the list above in the manual. In general, all handlers include the `connection_hdl` identifying which connection this even is associated with as the first parameter. Some handlers (such as the message handler) include additional parameters. Most handlers have a void return value but some (`validate`, `ping`, `tls_init`) do not. The specific meanings of the return values are documented in the handler list linked above. |
| 156 | +
|
| 157 | + |
| 158 | +### Step 3 |
| 159 | + |
| 160 | +_error handling_ |
| 161 | + |
| 162 | + |
| 163 | +### Step 4 |
| 164 | + |
| 165 | +_Set up open and close handlers and a connection data structure_ |
| 166 | + |
| 167 | +### Step 5 |
| 168 | + |
| 169 | +_Change the message handler for connections based on URI and add a validate handler to reject invalid URIs_ |
| 170 | + |
| 171 | +### Step 6 |
| 172 | + |
| 173 | +_Add some Admin commands (report total clients, cleanly shut down server)_ |
| 174 | + |
| 175 | +### Step 7 |
| 176 | + |
| 177 | +_Add some Broadcast commands_ |
| 178 | + |
| 179 | +### Step 8 |
| 180 | + |
| 181 | +_Add TLS_ |
0 commit comments