I’ve been playing a little bit with zmq. It really makes communication between components really easy, and the docs are plain great. I have also been experimenting with websockets on the client and was wondering if there was a way to bridge the two.
Now there are zmq libraries available for JavaScript - but this works if you’re also using JavaScript on the backend - which we won’t be. Instead we’ll use tornado as our gateway between the clients and the backend ‘engines’ (or engine in our case - but with zmq it’s really easy to scale). As a bonus, it allows us to segregate the backend logic - as tornado is an event loop, it’s critical we offload any processing to another thread/process so it can keep handling clients.
Rolling the dice
The backend will do nothing more than parse a string like ‘3d6’ and roll 3 6-sided dice. The method itself is straight-forward.
You’ll notice we’re returning actual objects. That’s because we’ll convert the return value to JSON before passing it back to the client.
The engine
This is nothing more than a zmq.REP socket on which we recv forever - the message is will be of the <client uuid>:<roll> format. You might notice the recv_string and send_string methods being a bit out of place. This is because in Python 3, strings are no longer equivalent to bytes - so the zmq API has those conveninence methods built-in.
The gateway
This was the most interesting part. When accepting new websocket connections, we generate a uuid to identify the client’s connection (as a client might have more than one). We then store it in PubWebSocket.client, a simple dictionary. The reason for this is that we’re doing everything async - so when the engine sends data back, we need to know which client the response belongs to.
Another cool thing is because tornado works on an event loop, our callback will always be on the main thread - so no locking of clients is required. But it does also mean that whatever our callback does must be quick and non-blocking. Here we just push the updates back to the clients.
You’ll note we don’t have a callback (publish_updates) registered for when the zmq_socket receives data - this will be done separately later.
The glue
Now that we have our gateway and our engine, let’s start them up together:
The proof
To get this live, we need clients. You can fire up a js console in Chrome or Firefox or alternatively just use nodejs (note that if you’re using the latter, you’ll need npm install ws - just one of the many websockets implementations available).
And the server logs:
Taking this further
I’d like to see how this works with a number of engines all sharing the same socket. zmq has a number of features available like using round-robin to dispatch work to various workers.