Skip to content

Seasocks quick tutorial

offa edited this page Jan 10, 2018 · 4 revisions

Seasocks is a tiny embeddable C++ server with WebSockets support.

It'll only likely work on Linux, and requires a somewhat modern C++ compiler with good C++14 support, so something like GCC 5 or a recent clang is needed.

Building Seasocks

SeaSocks builds using CMake, which can be installed with your favourite package manager pretty simply. Once you've got it installed:

$ mkdir build
$ cd build
$ cmake ..
$ make

And then run one of test apps (which need to run from the root of the project, not the build directory).

$ cd .. 
$ build/src/app/c/ws_test

Visit http://localhost:9090/ and see a somewhat uninspiring but very minimal web socket test application. You can control the number by clicking, and if you open several browsers you'll see the number ticks up in each when you click on it.

Using Seasocks

Everything in Seasocks is in the seasocks namespace. Unless there's a clash with your own classes, it's convenient to put a using namespace seasocks; at the top of your C++ files. The code below assumes you've done this. Additionally, we'll assume using namespace std; to make things shorter.

SeaSocks needs to log stuff from time to time. To control where the logging goes, you'll need an implementation of the Logger class (from <seasocks/Logger.h>). If stdout is good enough for you, just make a PrintfLogger (from <seasocks/PrintfLogger.h>).

Create a Server and give it the logger, then call serve("path", port); and you're done! serve blocks forever, or until another thread calls server.terminate();. For our simple example we'll let Ctrl-C be our killer. Our example then is:

void example() {
  auto logger = make_shared<PrintfLogger>();
  Server server(logger);
  server.serve("web", 9090);
}

To add web sockets support, we need to create a subclass of WebSocket::Handler (from <seasocks/WebSocket.h>). There are three methods to override: onConnect, onData and onDisconnect. onData is optional, if you don't care for replies from your clients. When you get called for onConnect, you're handed a WebSocket which you can stash away in a list of "connected clients" (until onDisconnect is called, anyway). This WebSocket has a method to let you send data to your clients.

A quick side-note on threading: SeaSocks is single-threaded - which makes it quite easy to reason about things. You can be certain your list of clients is up-to-date until onDisconnect is called. However, this has a drawback: if you need to send messages to clients you have to do it from the SeaSocks thread. This may be inconvenient if you are reacting to asynchronous results coming on other threads, so SeaSocks has a way to execute methods on its own thread: the Server class has an execute method which arranges for the supplied Server::Runnable has its run() method called on the server thread as soon as possible.

Anyway, armed with your own WebSocket::Handler, you can register it as a named handler before calling serve by passing it to server.addWebSocketHandler("/endpoint", handler);. Then, JavaScript connecting to /endpoint will talk to your Handler. Here's a really simple "chat" server:

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); }
};

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

There's a bunch of handy JSON formatters in <server/util/Json.h> of the form makeArray(...) or similar.

WebSockets are SeaSocks's main motivation, but static content's possible too. You can add a "page handler" with Server::addPageHandler, and that handler can serve up its own content (or authenticate, or whatever). There's some helper classes in server/util to make this a bit easier, have a look at RootPageHandler and its CrackedUriPageHandler (which conveniently break URIs up into manageable bits).

Websocket compression

As of this this commit, seasocks supports per-message deflate as described in RFC 7692. See the notes in that commit message for some current limitations.

In order to use this feature, it must be enabled. After creating a seasocks::Server object, call the setPerMessageDeflateEnabled method:

    server.setPerMessageDeflateEnabled(true);
Clone this wiki locally