Boost WebSocket Example: A Comprehensive Guide

WebSocket is a protocol that enables two-way communication between a server and a client over a single, long-lived connection. It is a popular choice for real-time web applications that require low latency and high throughput. Boost is a set of C++ libraries that provides a wide range of functionalities for network programming. In this article, we will explore how to use Boost to implement a WebSocket server and client example.

What is Boost?

Boost is a set of high-quality, peer-reviewed C++ libraries that cover a wide range of functionalities, from data structures and algorithms to network programming and concurrency. It is an open-source project that has been developed and maintained by a community of C++ enthusiasts since 1998. Boost aims to provide a set of libraries that are portable, efficient, and easy to use. It has become a de facto standard for C++ development and is widely used in both open-source and commercial projects.

What is WebSocket?

WebSocket is a protocol that enables two-way communication between a server and a client over a single, long-lived connection. It is designed to be a low-latency, high-throughput alternative to traditional HTTP-based communication. WebSocket allows real-time data to be sent from the server to the client and vice versa without the need for the client to initiate a request. This makes it ideal for applications that require real-time updates, such as online games, chat applications, and financial trading platforms.

Why Boost for WebSocket?

Boost provides a set of libraries that are well-suited for network programming. It provides a consistent and intuitive API that abstracts away the low-level details of network programming, making it easier to develop robust and efficient network applications. Boost.Asio, in particular, is a powerful library that provides a range of functionalities for network programming, including support for TCP, UDP, SSL, and WebSocket protocols. It also provides a flexible and extensible framework for handling asynchronous I/O operations, which is essential for developing high-performance network applications.

Boost WebSocket Example

In this section, we will explore how to use Boost to implement a WebSocket server and client example. We will start by implementing a simple echo server that echoes back any message sent by the client. We will then modify the server to broadcast messages to all connected clients. Finally, we will implement a WebSocket client that connects to the server and sends messages to it.

Implementing a Simple Echo Server

Our first example will be a simple echo server that echoes back any message sent by the client. We will start by creating a new Boost.Asio-based TCP server that listens for incoming connections. We will then modify the server to handle WebSocket connections and implement the echo functionality.

  1. Include the necessary headers:
  2. #include <boost/asio.hpp>#include <boost/asio/ssl.hpp>#include <boost/asio/steady_timer.hpp>#include <boost/beast.hpp>#include <boost/beast/websocket.hpp>#include <cstdlib>#include <iostream>#include <memory>#include <string>
  3. Define the necessary namespaces:
  4. namespace asio = boost::asio;namespace ssl = boost::asio::ssl;namespace beast = boost::beast;namespace http = beast::http;namespace websocket = beast::websocket;using tcp = boost::asio::ip::tcp;
  5. Create a new TCP server:
  6. class TCPServer { public:TCPServer(asio::io_context& ioc, tcp::endpoint endpoint) :acceptor_(ioc, endpoint) {doAccept();}

    private:void doAccept() {acceptor_.async_accept([this](std::error_code ec, tcp::socket socket) {if (!ec) {// Handle the new connectionstd::make_shared<TCPConnection>(std::move(socket))->start();}

    // Accept the next connectiondoAccept();});}

    tcp::acceptor acceptor_;};

  7. Create a new TCP connection:
  8. class TCPConnection : public std::enable_shared_from_this<TCPConnection> { public:TCPConnection(tcp::socket socket) :socket_(std::move(socket)) {}

    void start() {doRead();}

    private:void doRead() {auto self = shared_from_this();socket_.async_read_some(asio::buffer(buffer_),[this, self](std::error_code ec, std::size_t bytes_transferred) {if (!ec) {// Echo back the messagedoWrite(bytes_transferred);}});}

    void doWrite(std::size_t length) {auto self = shared_from_this();asio::async_write(socket_,asio::buffer(buffer_, length),[this, self](std::error_code ec, std::size_t /*bytes_transferred*/) {if (!ec) {// Continue reading from the socketdoRead();}});}

    tcp::socket socket_;std::array<char, 1024> buffer_;};

  9. Create a new main function:
  10. int main() {try {asio::io_context ioc;tcp::endpoint endpoint(tcp::v4(), 12345);TCPServer server(ioc, endpoint);ioc.run();} catch (std::exception& e) {std::cerr << "Exception: " << e.what() << "\n";}

    return 0;}

With the above code, we have created a simple TCP server that listens for incoming connections and echoes back any message sent by the client. We can test this server by connecting to it using a Telnet client and sending some messages.

Handling WebSocket Connections

Next, we will modify our TCP server to handle WebSocket connections. We will use the Boost.Beast library to handle the WebSocket protocol. Beast provides a set of high-level abstractions that make it easy to implement WebSocket servers and clients.

  1. Include the necessary headers:
  2. #include <boost/asio/ssl.hpp>#include <boost/beast/core.hpp>#include <boost/beast/http.hpp>#include <boost/beast/websocket.hpp>#include <boost/beast/websocket/ssl.hpp>#include <boost/asio/steady_timer.hpp>#include <cstdlib>#include <iostream>#include <memory>#include <string>
  3. Create a new WebSocket server:
  4. class WebSocketServer { public:WebSocketServer(asio::io_context& ioc, tcp::endpoint endpoint) :acceptor_(ioc, endpoint),context_(ssl::context::tlsv12) {context_.set_default_verify_paths();context_.set_verify_mode(ssl::verify_none);doAccept();}

    private:void doAccept() {acceptor_.async_accept([this](std::error_code ec, tcp::socket socket) {if (!ec) {// Upgrade the connection to WebSocketstd::make_shared<WebSocketSession>(std::move(socket), context_)->start();}

    // Accept the next connectiondoAccept();});}

    tcp::acceptor acceptor_;ssl::context context_;};

  5. Create a new WebSocket session:
  6. class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> { public:WebSocketSession(tcp::socket socket, ssl::context& context) :stream_(std::move(socket), context) {}

    void start() {// Perform the WebSocket handshakeauto self = shared_from_this();stream_.async_accept([this, self](std::error_code ec) {if (!ec) {doRead();}});}

    private:void doRead() {auto self = shared_from_this();stream_.async_read(buffer_,[this, self](std::error_code ec, std::size_t bytes_transferred) {if (!ec) {// Echo back the messagedoWrite(bytes_transferred);}});}

    void doWrite(std::size_t length) {auto self = shared_from_this();stream_.async_write(buffer_.data(),[this, self](std::error_code ec, std::size_t /*bytes_transferred*/) {if (!ec) {// Continue reading from the WebSocketdoRead();}});}

    websocket::stream<beast::ssl_stream<tcp::socket>> stream_;beast::flat_buffer buffer_;};

  7. Create a new main function:
  8. int main() {try {asio::io_context ioc;tcp::endpoint endpoint(tcp::v4(), 12345);WebSocketServer server(ioc, endpoint);ioc.run();} catch (std::exception& e) {std::cerr << "Exception: " << e.what() << "\n";}

    return 0;}

With the above code, we have created a WebSocket server that listens for incoming connections and echoes back any message sent by the client. We can test this server by connecting to it using a WebSocket client and sending some messages.

Broadcasting Messages to All Connected Clients

Next, we will modify our WebSocket server to broadcast messages to all connected clients. We will maintain a list of all active WebSocket sessions and broadcast messages to all of them.

  1. Add a list of active WebSocket sessions to the WebSocket server:
  2. class WebSocketServer { public:WebSocketServer(asio::io_context& ioc, tcp::endpoint endpoint) :acceptor_(ioc, endpoint),context_(ssl::context::tlsv12) {context_.set_default_verify_paths();context_.set_verify_mode(ssl::verify_none);doAccept();}

    private:void doAccept() {acceptor_.async_accept([this](std::error_code ec, tcp::socket socket) {if (!ec) {// Upgrade the connection to WebSocketstd::make_shared<WebSocketSession>(std::move(socket), context_, sessions_)->start();}

    // Accept the next connectiondoAccept();});}

    tcp::acceptor acceptor_;ssl::context context_;std::vector<std::shared_ptr<WebSocketSession>> sessions_;};

  3. Modify the WebSocket session to add itself to the list of active sessions:
  4. class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> { public:WebSocketSession(tcp::socket socket, ssl::context& context, std::vector<std::shared_ptr<WebSocketSession>>& sessions) :stream_(std::move(socket), context),sessions_(sessions) {sessions_.push_back(shared_from_this());}

    ~WebSocketSession() {sessions_.erase(std::remove(sessions_.begin(), sessions_.end(), shared_from_this()), sessions_.end());}

    void start() {// Perform the WebSocket handshakeauto self = shared_from_this();stream_.async_accept([this, self](std::error_code ec) {if (!ec) {doRead();}});}

    private:void doRead() {auto self = shared_from_this();stream_.async_read(buffer_,[this, self](std::error_code ec, std::size_t bytes_transferred) {if (!ec) {// Broadcast the message to all connected clientsfor (auto session : sessions_) {session->doWrite(bytes_transferred);}

    // Continue reading from the WebSocketdoRead();} else if (ec == websocket::error::closed) {// Remove the session from the list of active sessionssessions_.erase(std::remove(sessions_.begin(), sessions_.end(), shared_from_this()), sessions_.end());}});}

    void doWrite(std::size_t length) {auto self = shared_from_this();stream_.async_write(buffer_.data(),[this, self](std::error_code ec, std::size_t /*bytes_transferred*/) {});}

    websocket::stream<beast::ssl_stream<tcp::socket>> stream_;beast::flat_buffer buffer_;std::vector<std::shared_ptr<WebSocketSession>>& sessions_;};

With the above code, we have modified our WebSocket server to maintain a list of all active WebSocket sessions and broadcast messages to all of them. We can test this server by connecting to it using multiple WebSocket clients and sending messages.

Implementing a WebSocket Client

Finally, we will implement a WebSocket client that connects to the server and sends messages to it. We will use the same Boost.Beast library to handle the WebSocket protocol on the client side.

  1. Create a new WebSocket client:
  2. class WebSocketClient { public:WebSocketClient(asio::io_context& ioc, const tcp::resolver::results_type& results) :resolver_(ioc),stream_(ioc) {doConnect(results);}

    void send(const std::string& message) {auto self = shared_from_this();asio::post(stream_.get_executor(),[this, self, message]() {// Send the message to the serverbuffer_ = message;doWrite();});}

    private:void doConnect(const tcp::resolver::results_type& results) {auto self = shared_from_this();asio::async_connect(stream_.next_layer(),results.begin(),results.end(),[this, self](std::error_code ec, const tcp::endpoint&) {if (!ec) {// Perform the WebSocket handshakedoHandshake();}});}

    void doHandshake() {auto self = shared_from_this();stream_.async_handshake("localhost","/",[this, self](std::error_code ec) {if (!ec) {doRead();}});}

    void doRead() {auto self = shared_from_this();stream_.async_read(buffer_,[this, self](std::error_code ec, std::size_t bytes_transferred) {if (!ec) {// Print the received messagestd::cout << "Received: " << beast::make_printable(buffer_.data()) << "\n";}

    // Continue reading from the WebSocketdoRead();});}

    void doWrite() {auto self = shared_from_this();stream_.async_write(asio::buffer(buffer_),[this, self](std::error_code ec, std::size_t /*bytes_transferred*/) {});}

    tcp::resolver resolver_;websocket::stream<asio::ip::tcp::socket> stream_;beast::flat_buffer buffer_;};

  3. Create a new main function:
  4. int main() {try {asio::io_context ioc;tcp::resolver resolver(ioc);auto results = resolver.resolve("localhost", "12345");WebSocketClient client(ioc, results);client.send