Tim Mudford
Tim Mudford

Reputation: 21

How do you send websocket data to connected clients using C++ Seasocks?

I am using the Seasocks C++ library in order to add websocket functionality to an existing project. How do I initiate the sending of data to connected clients from the C++ code?

My code so far is as follows:

// .....
#include "seasocks/PrintfLogger.h"
#include "seasocks/Server.h"
#include "seasocks/WebSocket.h"
#include <thread>

using namespace seasocks;

namespace {
    struct Handler : WebSocket::Handler {
        std::set<WebSocket*> _cons;
        void onConnect(WebSocket* con) override {
            _cons.insert(con);
            send("Connected");
        }
        void onDisconnect(WebSocket* con) override {
            _cons.erase(con);
            send("Disconnected");
        }
        void onData(WebSocket* con, const char* data) override {
            send(data);
        }
        void send(const std::string& msg) {
            for (auto* con : _cons) {
                con->send(msg);
            }
        }
    };
}
SmartConfig::SmartConfig(StorageContainer container, QObject *parent) : 
        QObject(parent),
        _storage(container),
        _webserver(NULL),
        _server(std::make_shared<PrintfLogger>())
{

    std::thread seasocksThread([&] {
        _server.addWebSocketHandler("/Socket", std::make_shared<Handler>(), true);
        _server.startListening(9090);
        _server.loop();
    });
    seasocksThread.detach();

    // .....
}

At this stage I can connect a client to the server and send data to it. It responds with whatever was sent, which is fine for now, but how do I initiate the sending of data to connected clients in various parts of the code or in other classes? I believe that I should be using something like:

seasocks::Server().execute({
    send("Some data to send.");
});

This doesn't work however. I get the following error:

no instance of constructor "seasocks::Server::Server" matches the argument list

Upvotes: 0

Views: 1197

Answers (1)

Matt Godbolt
Matt Godbolt

Reputation: 1512

Most of this is me restating the quick tutorial, but hopefully in a clearer way.

The function execute() is a method on the server object, and it accepts a "function to call" on the correct thread. Your example is trying to construct a new Server, then call its execute method with an initializer list (I think!).

To do useful work, though, you need to know which clients to send messages to. The server doesn't keep track of that: it delegates that responsibility to the individual Handlers. Think of the Handler as like a single web page handler: a website doesn't have "clients" but each page does (the clients that are viewing it)

You'll need to make a Handler for the page you want (e.g. the ChatHandler in the tutorial). That handler will remember the list of clients attached to it:

struct ChatHandler : WebSocket::Handler {
  set<WebSocket *> connections;
  void onConnect(WebSocket *socket) override 
    { connections.insert(socket); }
  void onData(WebSocket *, const char *data) override 
    { for (auto c : connections) c->send(data); }
  void onDisconnect(WebSocket *socket) override 
    { connections.erase(socket); }
};

// I'd never use globals normally but for simplicity:
std::shared_ptr<ChatHandler> chatHandler;
Server server(make_shared<PrintfLogger>());

void chat() {
  chatHandler = make_shared<ChatHandler>();
  server.addWebSocketHandler("/chat", chatHandler);
  server.serve("web", 9090);
}

In this example the chat client simply echoes the data it receives to all connected clients. As the onData method is called from the Seasocks thread, no execute() shenanigans is needed.

If one wanted to send a message to all connected clients from another thread, one might use code like this:

void sendToAll(const char *data) {
  server.execute([] {
    for (auto c : chatHandler->connections) c->send(data);
  });
}

This creates an anonymous function (a lambda) which will reach into the chat handler and call "send" on each of its clients. It then passes this lambda to server.execute() -- this will ensure the lambda is executed on the correct thread.

Specifically in your case I'd suggest:

  • Keeping hold of the Handler shared_ptr you create in the SmartConfig structure. Make shared_ptr<Handler> member variable (_handler), construct it and then also pass it into addWebSocketHandler.
  • When you want to send to all connections, call _server.execute([this, msg]{ _handler.send(msg);});

I hope that helps a little: it's not that clear, and C++ syntax isn't the easiest.

Upvotes: 1

Related Questions