Server Initiated Messages

One of the key reasons to use a WebSocket over other sorts of HTTP requests is to allow your server to push content to the client at time when the client has not explicitly asked for something. Responding to a WebSocket message using WebSocket++ is straightforward, as demonstrated by the echo_server example. If, however, you want to send messages based on events on the server side you will need a bit more code. We will look at two examples. The first is a broadcast server, the second a telemetry server.

Broadcast Server

It is similar to the echo server in that incoming messages are echoed back, but different in that they are echoed back to all connected clients rather than only the sender. In order to send a message, we need a token that identifies the connection. That token in WebSocket++ is the connection_hdl. In the echo_server example the connection_hdl was supplied by the on_message callback. All handler callbacks include a connection_hdl that identifies the connection that initiated the handler.

For a broadcast server, on_message will need to call send for every connection, rather than just the sender. To accomplish this we will need to maintain a list of all of the outstanding connections. This can be done by storing a list of connection_hdls populated by the on_open handler and pruned by on_close. For each message received, that message is then copied in a loop to every connection_hdl in the list.

Note: This is a basic and simplified program. Error checking code paths have been removed to more clearly demonstrate the core logic. Not all such programs will need to check for all errors. You should check for and deal with the ones appropriate to your application.

Secondly, this simplified broadcast server will not scale well. WebSocket++ handlers block core networking functions. While this program is running its send loop in on_message, no new connections are being processed and no new messages are being received. This will work fine with small numbers of connections, but will break badly when scaled up. In general, to maintain responsiveness at high message rates or high levels of concurrency, handlers should pass processing requests off to other worker threads to actually perform the work.

The broadcast server example included in the library source corrects both of these problems. If you are interested in looking at a higher performance example with error checking please refer to that code.

Program Source

#include <set>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

typedef websocketpp::server<websocketpp::config::asio> server;

using websocketpp::connection_hdl;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;

class broadcast_server {
public:
    broadcast_server() {
        m_server.init_asio();
                
        m_server.set_open_handler(bind(&broadcast_server::on_open,this,::_1));
        m_server.set_close_handler(bind(&broadcast_server::on_close,this,::_1));
        m_server.set_message_handler(bind(&broadcast_server::on_message,this,::_1,::_2));
    }
    
    void on_open(connection_hdl hdl) {
        m_connections.insert(hdl);
    }
    
    void on_close(connection_hdl hdl) {
        m_connections.erase(hdl);
    }
    
    void on_message(connection_hdl hdl, server::message_ptr msg) {
        for (auto it : m_connections) {
            m_server.send(it,msg);
        }
    }

    void run(uint16_t port) {
        m_server.listen(port);
        m_server.start_accept();
        m_server.run();
    }
private:
    typedef std::set<connection_hdl,std::owner_less<connection_hdl>> con_list;

    server m_server;
    con_list m_connections;
};

int main() {
    broadcast_server server;
    server.run(9002);
}

Telemetry Server

While the broadcast server does push messages to connections unsolicited as data is ready, it all starts with a message from one connection. If you want to push data based on an event truly initiated by the server and not a response to any incoming input there are a few options.

count_server: Thread or external event loop

This method uses a separate thread (an external event loop in another thread will work as well). Count server uses another thread to sleep for a time and then increment a counter and sends the new value out to all connected clients.

Note: Like the broadcast server above, this is a simplified program. It includes the core of the program for clarity but omits detailed error checking code.

#include <functional>
#include <mutex>
#include <set>
#include <thread>

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

typedef websocketpp::server<websocketpp::config::asio> server;

using websocketpp::connection_hdl;

class count_server {
public:
    count_server() : m_count(0) {
        m_server.init_asio();
                
        m_server.set_open_handler(bind(&count_server::on_open,this,_1));
        m_server.set_close_handler(bind(&count_server::on_close,this,_1));
    }
    
    void on_open(connection_hdl hdl) {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_connections.insert(hdl);
    }
    
    void on_close(connection_hdl hdl) {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_connections.erase(hdl);
    }
    
    void count() {
        while (1) {
            sleep(1);
            m_count++;
            
            std::stringstream ss;
            ss << m_count;
            
            std::lock_guard<std::mutex> lock(m_mutex);    
            for (auto it : m_connections) {
                m_server.send(it,ss.str(),websocketpp::frame::opcode::text);
            }
        }
    }
    
    void run(uint16_t port) {
        m_server.listen(port);
        m_server.start_accept();
        m_server.run();
    }
private:
    typedef std::set<connection_hdl,std::owner_less<connection_hdl>> con_list;
    
    int m_count;
    server m_server;
    con_list m_connections;
    std::mutex m_mutex;
};

int main() {
    count_server server;
    std::thread t(std::bind(&count_server::count,&server));
    server.run(9002);
}

transport::asio on_timer

TODO: notes about this one

#include <functional>
#include <set>

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

typedef websocketpp::server<websocketpp::config::asio> server;

using websocketpp::connection_hdl;

class count_server {
public:
    count_server() : m_count(0) {
        m_server.init_asio();
                
        m_server.set_open_handler(bind(&count_server::on_open,this,_1));
        m_server.set_close_handler(bind(&count_server::on_close,this,_1));
        
        m_timer = m_server.set_timer(1000, bind(&count_server::count,this,_1));
    }
    
    void on_open(connection_hdl hdl) {
        m_connections.insert(hdl);
    }
    
    void on_close(connection_hdl hdl) {
        m_connections.erase(hdl);
    }
    
    void count(const websocketpp::lib::error_code & ec) {
        m_count++;
        
        std::stringstream ss;
        ss << m_count;
        
        for (auto it : m_connections) {
            m_server.send(it,ss.str(),websocketpp::frame::opcode::text);
        }
        
        m_timer = m_server.set_timer(1000, bind(&count_server::count,this,_1));
    }
    
    void run(uint16_t port) {
        m_server.listen(port);
        m_server.start_accept();
        m_server.run();
    }
private:
    typedef std::set<connection_hdl,std::owner_less<connection_hdl>> con_list;
    
    int m_count;
    server m_server;
    con_list m_connections;
    server::timer_ptr m_timer;
};

int main() {
    count_server server;
    server.run(9002);
}

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.