Storing Connection Specific/Session Information

WebSocket++ provides several methods of keeping track of connection specific information.

Associative Storage

The simplest method is to use the unique connection_hdl associated with each connection as a key for an associative container. This method is recommended unless there are specific circumstances that make it undesirable, such as large numbers of connections or to avoid lock contention. This method is particularly useful when the associated data needs to be accessible to multiple connections or needs to iterated through (for example to broadcast a message to all clients or check if a name has been used already).

This example demonstrates the use of an associative container with connection_hdl to store a sessionid (assigned uniquely to all new connections) and a name (assigned by the first message received on the connection). The name and sessionid data are subsequently available for all future handlers.

#include <iostream>
#include <map>
#include <exception>
#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;

struct connection_data {
    int sessionid;
    std::string name;
};

class print_server {
public:
    print_server() : m_next_sessionid(1) {
        m_server.init_asio();
                
        m_server.set_open_handler(bind(&print_server::on_open,this,::_1));
        m_server.set_close_handler(bind(&print_server::on_close,this,::_1));
        m_server.set_message_handler(bind(&print_server::on_message,this,::_1,::_2));
    }
    
    void on_open(connection_hdl hdl) {
        connection_data data;
        
        data.sessionid = m_next_sessionid++;
        data.name = "";
        
        m_connections[hdl] = data;
    }
    
    void on_close(connection_hdl hdl) {
        connection_data& data = get_data_from_hdl(hdl);
        
        std::cout << "Closing connection " << data.name 
                  << " with sessionid " << data.sessionid << std::endl;
        
        m_connections.erase(hdl);
    }
    
    void on_message(connection_hdl hdl, server::message_ptr msg) {
        connection_data& data = get_data_from_hdl(hdl);
        
        if (data.name == "") {
            data.name = msg->get_payload();
            std::cout << "Setting name of connection with sessionid " 
                      << data.sessionid << " to " << data.name << std::endl;
        } else {
            std::cout << "Got a message from connection " << data.name 
                      << " with sessionid " << data.sessionid << std::endl;
        }
    }
    
    connection_data& get_data_from_hdl(connection_hdl hdl) {
        auto it = m_connections.find(hdl);
        
        if (it == m_connections.end()) {
            // this connection is not in the list. This really shouldn't happen
            // and probably means something else is wrong.
            throw std::invalid_argument("No data avaliable for session");
        }
        
        return it->second;
    }
    
    void run(uint16_t port) {
        m_server.listen(port);
        m_server.start_accept();
        m_server.run();
    }
private:
    typedef std::map<connection_hdl,connection_data,std::owner_less<connection_hdl>> con_list;

    int m_next_sessionid;
    server m_server;
    con_list m_connections;
};

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

Associative Storage with Multiple Containers

A second method is similar to that demonstrated above, but rather than a single object that all connections are associated with, multiple objects are created and connection handlers are assigned to the appropriate object. See Changing handlers after a connection is established for information about setting connection specific handlers. The advantages are that the log(n) map lookup will have smaller and more relevant groups to look through. You can also associate one object per connection for constant time lookups and private data. This method is especially useful if the logic for each connection or groups of connections is different, as the handler code for each object need not be the same.

Extended Connection Storage

A third method is to set a custom connection_base config policy. This method extends the connection object by deriving from a custom base class that you specify in the config. This allows you to access all data elements and methods from your extended class using a connection_ptr. These data elements are allocated and stored with the connection by the library itself.

This method works well when connection logic is identical and connections share no data. It eliminates the overhead of lookups and switching handlers but requires building and maintaining your own custom WebSocket++ server config. Also keep in mind that connection objects are not thread safe except within one of their own handler callbacks. If other threads or other connections need to access connection specific information an associative container should be used instead or in addition to this method. See the Thread Safety section of the manual for more details.

The example below is identical to the associative storage example, but uses the extended connection_base method. Note, this example has no ability to enumerate all connections.

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

struct connection_data {
    int sessionid;
    std::string name;
};

struct custom_config : public websocketpp::config::asio {
    // pull default settings from our core config
    typedef websocketpp::config::asio core;
    
    typedef core::concurrency_type concurrency_type;
    typedef core::request_type request_type;
    typedef core::response_type response_type;
    typedef core::message_type message_type;
    typedef core::con_msg_manager_type con_msg_manager_type;
    typedef core::endpoint_msg_manager_type endpoint_msg_manager_type;
    typedef core::alog_type alog_type;
    typedef core::elog_type elog_type;
    typedef core::rng_type rng_type;
    typedef core::transport_type transport_type;
    typedef core::endpoint_base endpoint_base;
    
    // Set a custom connection_base class
    typedef connection_data connection_base;
};

typedef websocketpp::server<custom_config> server;
typedef server::connection_ptr connection_ptr;

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

class print_server {
public:
    print_server() : m_next_sessionid(1) {
        m_server.init_asio();
                
        m_server.set_open_handler(bind(&print_server::on_open,this,::_1));
        m_server.set_close_handler(bind(&print_server::on_close,this,::_1));
        m_server.set_message_handler(bind(&print_server::on_message,this,::_1,::_2));
    }
    
    void on_open(connection_hdl hdl) {
        connection_ptr con = m_server.get_con_from_hdl(hdl);
        
        con->sessionid = m_next_sessionid++;
    }
    
    void on_close(connection_hdl hdl) {
        connection_ptr con = m_server.get_con_from_hdl(hdl);
        
        std::cout << "Closing connection " << con->name 
                  << " with sessionid " << con->sessionid << std::endl;
    }
    
    void on_message(connection_hdl hdl, server::message_ptr msg) {
        connection_ptr con = m_server.get_con_from_hdl(hdl);
        
        if (con->name == "") {
            con->name = msg->get_payload();
            std::cout << "Setting name of connection with sessionid " 
                      << con->sessionid << " to " << con->name << std::endl;
        } else {
            std::cout << "Got a message from connection " << con->name 
                      << " with sessionid " << con->sessionid << std::endl;
        }
    }
    
    void run(uint16_t port) {
        m_server.listen(port);
        m_server.start_accept();
        m_server.run();
    }
private:
    int m_next_sessionid;
    server m_server;
};

int main() {
    print_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.