One producer, many consumers

Websocket functionality was added to kdb+ in v3.0 and there are some decent examples online, e.g.

But I couldn't find any of a handler sending the same data to multiple clients simultaneously. Or indeed sequentially-but-as-fast-as-possible, since I'm only interested in single-threaded scenarios.

The .z.ws handler

Let's consider a simple websocket handler similar to the cookbook example:

.z.ws:{neg[.z.w] .Q.s rand 37}

On receipt of a message from a websocket client - this should send a random integer between 0 & 36, wrapped up in websocket protocol on the file descriptor numbered .z.w, which denotes the fd of the current connection. i.e. if 0 is stdin, 1 is stdout, 2 is stderr then your first client connected (assuming you haven't opened any other files) is likely to be on fd 3, the next 4, and so on. See /proc/<kdb_pid>/fd on Linux if you don't believe me, and also .z.W.

Streaming

Replying to .z.w's message is the polite thing to do and most applications do it in order to be useful. But what if you just want to stream random [0,36] integers to your clients and let .z.ws handle other requests like subscribing & unsubscibing from those steams (akin to kdb+tick functionality)?

Well fortunately, the websocket clients require no special treatment; that is you would broadcast to ws clients in exactly the same way as regular tcp/ip clients - by using the asynchronous IPC call against the list of connected file descriptors (e.g. from .z.W). Why asynchronous? This is a broadcast so you generally don't want to wait for a reply, but also sending a synchronous request over a websocket connection will raise a not-yet-implemented error.

Roulette

Firstly, let's give rand 37 a more interesting application - the spin of a (European) Roulette table:

q)spin:{x,$[x=0;`lose;(x<11)|((x>18)and(x<29));$[x mod 2;`red;`black];(x<19)|((x>28)&(x<37));$[x mod 2;`black;`red];0N]}

e.g.

q)spin[rand 37]
13
`black
q)spin each til 4
0 `lose 
1 `red  
2 `black
3 `red

Transmit

Now let's send the result of a spin to each of our "players":

q).z.W / two clients are connected:
5| 
8| 
q)send:{[msg;h]neg[h].Q.s .z.t,msg} / func to send msg on handle h asynchronously
q)send[spin[rand 37]] each (key .z.W) / send on all handles in .z.W

In the last line we use each to call our send function for every entry in .z.W. Since .z.W is a dictionary, we use key to extract the handle numbers. In fact, handle 5 is a websocket client (Google Chrome) and handle 8 is a kdb/q client, and what we see for both is below (note the timestamps are the same):

kdb/q client with browser client

13 Black again! Now that we've got the broadcast working okay, we can set the timer function to stream spin results. Let's spin every 2 seconds:

q).z.ts:{send[spin[rand 37]] each (key .z.W)}
q)\t 2000

With three browser clients connected we now see:

many spins in browser

Sending JSON

.Q.s is handy for examples, but not particularly useful in real applications. Fortunately the .j namespace (a part of the kdb release since v3.2) makes JSON serialization trivial. Our send function becomes:

q)send:{[msg;h]neg[h].j.j .z.t,msg}

and the javascript client can happily create a native object from this string:

Chrome console with json string from kdb

Distinguishing client types

You may want to send native kdb structures on the kdb/q handles and JSON on the websocket handles. You can't distinguish between the two using .z.W alone, and you'll notice that if you try to send a kdb object (other than a string) to a ws client it results in a 'type error. So what can you do? The .z.po "port open" callback doesn't distinguish between different types of handle either, so unfortunately it isn't much use in this case. I may be overlooking something, but the only option I can see is to use the .z.ws handler to maintain a list of known ws clients, and use to .z.pc to remove the client on-disconnect. Using DotZ provides some great ideas (with code) for caching client information and metadata.


––

So that's about it for this contrived example, but hopefully this post provides motivation for more purposeful use-cases, such as streaming market data ticks or adding browser clients to existing TCP/IP applications.

Comments

comments powered by Disqus