Server architecture: websocket multicast server?

2019-08-10 04:21发布

问题:

What would be the simplest way to build a server that receives incoming connections via a websocket, and streams the data flowing in that socket out to n subscribers on other websockets. Think for example of a streaming application, where one person is broadcasting to n consumers.

Neglecting things like authentication, what would be the simplest way to build a server that can achieve this? I'm a little confused about what would happen when a chunk of data hits the server. It would go into a buffer in memory, then how would it be distributed to the n consumers waiting for it? Some sort of circular buffer? Are websockets an appropriate protocol for this? Thanks.

回答1:

Here's one using the Ruby Plezi framework (I'm the author, so I'm biased):

require 'plezi'

class Client
   # Plezi recognizes websocket handlers by the presence of the
   # `on_message` callback.
   def on_message data
      true
   end
   protected
   # this will be out event.
   def publish data
      write data
   end
end

class Streamer
   def on_message data
      Client.broadcast :publish, data
   end
end

# the streamer will connect to the /streamer path
route '/streamer', Streamer

# the client will connect to the /streamer path
route '/', Client

# on irb, we start the server by exiting the `irb` terminal
exit

You can test it with the Ruby terminal (irb) - it's that simple.

I tested the connections using the Websocket.org echo test with two browser windows, one "streaming" and the other listening.

  • use ws://localhost:3000/streamer for the streamer websocket connection

  • use ws://localhost:3000/ for the client's connection.

EDIT (relating to your comment regarding the Library and architecture)

The magic happens in the IO core, which I placed in a separate Ruby gem (Ruby libraries are referred to as 'gems') called Iodine.

Iodine leverages Ruby's Object Oriented approach (in Ruby, everything is an object) to handle broadcasting.

A good entry point for digging through that piece of the code is here. When you encounter the method each, note that it's inherited from the core Protocol and uses an Array derived from the IO map.

Iodine's websocket implementation iterates through the array of IO handlers (the value half of a key=>value map), and if the IO handler is a Websocket it will "broadcast" the message to that IO handler by invoking the on_broadcst callback. The callback is invoked asynchronously and it locks the IO handler while being executed, to avoid conflicts.

Plezi leverages Iodine's broadcast method and uses the same concept so that the on_broadcast callback will filter out irrelevant messages.

Unicasting works a little bit differently, for performance reasons, but it's mostly similar.

I'm sorry for using a lot of shorthand in my code... pre-Ruby habits I guess. I use the condition ? when_true : when_false shorthand a lot and tend to squish stuff into single lines... but it should be mostly readable.

Good luck!