The Crystal Programming Language Forum

Understanding Websockets

I’m trying to understand how the websockets works. I’m building a basic chat with the least amount of setup code.

ws_handler = HTTP::WebSocketHandler.new do |ws, ctx|
  ws.send({text: "Connecting"}.to_json)
  ws.on_message do |message|
     ws.send(message)
  end
end
server = HTTP::Server.new [ws_handler]

When I open up 2 browser tabs, I send a message and I see they both hit the server, the server sends back to the browser, but only the tab that sent the message gets the message back. Is there something else I have to do to tell all connected clients to get the message?

Websockets are connections to single clients. So if you receive a message and send it back, it only arrives at the sender.
You need some kind of message distribution if you want to reach different clients. Typically a chat server would keep track of all the websockets connected to it and relay incoming messages to all of them.
A very basic implementation can be found in kemal-chat shard: https://github.com/sdogruyol/kemal-chat/blob/688f0388f461ab68b83dfe0d81a04065d4832b6a/src/kemal_chat.cr
Or a more elaborate version with different channels in bifrost shard: https://github.com/alternatelabs/bifrost/blob/cbad1701ad2fa8537b2b35d20fef98c809245fcb/src/bifrost.cr

1 Like

Ah! Ok. I was seeing something like that in other implementations and didn’t understand. That makes sense now. Thanks for the explanation!

Ok, one more just out of curiosity (and I know it’s a loaded question), but in that Kemal chat example, it’s shoving each websocket instance in to an Array. How would you handle that at scale?

Like 1 million connections. Just have an array (or maybe hash for some organization) with 1million instances in it? I guess you’d just throw more RAM at the machine at that point and try to cache and manage which connections are still alive and such at that level, right?

Each server in the cluster would have to maintain the list of clients in a chat, as in the example
and then you probably want to load balance between servers.

So you would use redis or a service mesh to distribute messages between servers so they can forward them to users connected to those servers.

Once you move to HA or scalable multi-server infrastructure the complexity of the application has to rise

1 Like

Yeah, it makes sense that you’d eventually move to Redis or some other messaging system. The glue part that I’m missing is when you have a websocket connect, and you need to store something so you can reference that websocket later, what is that “something” if it’s not the websocket instance object? Since you can only shove Strings in redis, what are you shoving in to redis so when you pull it back out you can say “hey, this is WebSocket 456”? Or is that answer a bit too complicated? :laughing:

I’m more just wondering if with Crystal, is there some identifier you can store to reference an open connection later, or does it become that you drop websockets, and start using a whole different system at that level?

I suppose you would just start assigning uids to every web socket. Then you can address them.

1 Like

This was actually very helpful. Thanks!