SocketCluster / socketcluster

Highly scalable realtime pub/sub and RPC framework
https://socketcluster.io
MIT License
6.15k stars 314 forks source link

Question: multiple socket = socketCluster.connect #159

Closed stewartcelani closed 8 years ago

stewartcelani commented 8 years ago

I'm looking at switching my project which requires a front-end to trigger events on chrome extensions from socket.io (worked great, but scaling is an issue and Pusher has 10k socket limit which isn't enough) to socket cluster and I just wanted to check how SC handles multiple connects. Extension will be running on chromebooks that may be dropping in and out of wifi, closing/opening and the extension must force disconnect outside of certain hours. I need to keep the connection and multiple subscriptions per device up and running when device resumes network connectivity. I'll be using the socket state functionality to hopefully bypass ever calling connect on a socketcluster object that isn't actually connected, but if the scenario occurs where socket = socketCluster.connect({ [options] }) occurs more than once, what happens? The chrome extension could be running on hundreds of devices in a private network and I'd like to minimize trips to the socketcluster server. In this scenario does SC just pick up an already connected object? Or will setting this again force a reconnection and reauth etc? Thanks for your time!

jondubois commented 8 years ago

SC recovers from lost connections automatically and it automatically re-establishes channel subscriptions when they are lost so it sounds good for your use case. (You can optionally disable this behaviour of course).

Regarding your second question; if you call socketCluster.connect(options) multiple times (to the same host, port, etc...), by default it will reuse the connection. You can optionally turn this off by adding a multiplex: false option when you call socketCluster.connect(options). See http://socketcluster.io/#!/docs/api-socketcluster-client

stewartcelani commented 8 years ago

Thanks Jon! It took me many, many hours to make sure socket.io survived via stress testing (closing/opening chromebook, turning off/on wifi router and having to use forceNew connections) and socketcluster has been a champ. It.. just works so far O.O. =D

BTW: Aussie?! Where do I send your Patreon-beer? Link me.

Before I had to change from socket.io I was nearly ready to launch the alpha of my app/extension for the largest Chromebook school in SA (470 atm). Next step is forcing disconnects/reconnects based on certain params (school hours + public IP) and reconnecting when opposite is true (within school hours + from school public IP). Just planning on using the same object for all of those (i.e. disconnect might be called a bunch of times and connect would be called on a forcibly disconnected socketcluster variable when the device is on the school network within defined times again)... how would that effect SC? Recommendations? With socket.io if connect was called multiple times it had no idea of previous connections so it would spawn a LOT of different connections so it took a lot of code to try guard against making new connections (had to use {forceNew: true}) and then chrome would buffer the requests when I didn't want it to and come back online and spit 100s of requests at my socket.io server (endless streams of "user connected" =D)..

Any advice or recommendations for having 470 chromebooks connected on same private network to the server via DECD proxy at a time on how to minimize socket chatter?

I'm using a publish pattern from teachers (client = meteor) to the students (chrome extensions) and then extensions can reply via private channel to teachers (and vice versa) all via the publish method on the global socket object (passing {event: blah, from: blah@blah.com, to: channel@lah.com, message: optional} in order to send acks back to the from channel (i.e. teacher sends a 'close tab' event' to a students Chromebook which then handles the event and sends back 'tab closed!' ack to teachers browser which pops up a ui confirmation for the teacher).

Using socket.io I used emit messages in from the server but not sure the cleanest way to do things with SC. I don't want to have to use eval in the extension to declare variables for each channel to then watch (performing all SC functionality from global object is VERY important if I can specify channel) [just sad I can't specify event there also, seems to only be supported by emit -> server -> other client pattern] because each extension/student might belong to many classes/groups (determined by a constant sync from google apps ==> mongodb).

Is the only way to PING -> PONG an extension from the UI I describe via emit? Using socket.io I had to ping the server, and in the handler on the server then had to then ping the client (which would pass back a callback to server) which would then callback the original client (front-end) for a basic "check if extension is online" check?

Last night I was starting to implement some SC and I noticed if I watched the same channel/event multiple times I would then receive the event multiple times if you understand what I mean. Multiple watchers on same channel/event don't know about each other so when data comes in on that channel/event it triggers every watcher although all we really want is one.

Hence this shabby code (watchers.length < 1) from my chrome extension that will get called via interval. One student may be a member of a lot of groups in Google Apps or maybe just one. Calling watch via a loop doesn't seem the best way to go.

Is there any way keep the subscribe pattern but then call socket.on(EVENT) from the global socket channel (i.e. below I am watching a whole channel and doing if statements against data.event -- not sure if this is very low performance or if its something that would have to be done behind the scenes anyway) and also let clients on the other side publish to the global socket via a channel/group/room AND an event? Publish abstracts away so much complication away and works awesome but if it could also publish events and let middle ware handle the auth it would be nirvana.

// This code was triggered a lot over the last day even after client device was off and resumed and it reconnected and was still receiving single "SC viewer active events" from front end. Before the watchers.length < 1 statement each event would + 1 (i.e. 2 SC viewer active events then 3 then 4 then 5). function SocketHandler() { // Subcribe to private channel (i.e. student1@blah.com.au) socket.subscribe(clientid)

// Subscribe to all rooms client is a member of
for (let i in jsonGroups) {

    socket.subscribe(jsonGroups[i])
    var watchers = socket.watchers(jsonGroups[i])
    if (watchers.length < 1) {
        socket.watch(jsonGroups[i], function (data) {
            console.log("socket.watch(" + jsonGroups[i] + ") event: From " + data.from + ", To: " + data.to + ", Event: " + data.event)
            if (data.event == "viewer active") {
                console.log("SC viewer active event received")
                socket.publish(data.from, {
                    from: clientid,
                    to: data.from,
                    event: null,
                    message: "Ack " + data.event
                })
            }

        })
    }

}

}

jondubois commented 8 years ago

@stewartcelani Yes, you can force disconnect and reconnect in SC whenever you like. The autoreconnect kicks in if the connection is lost due to network failure.

If you call connect multiple times in SC, by default it will reuse the existing connection so it won't leak sockets.

Regarding your question about events - SC tries to keep it as simple as possible when it comes to channels. If you want to add events within your channels, you can use the if statement approach which you described (if you don't have too many different types of events) or a good approach would be to have an object which maps event names to functions (in this case you can declare as many handlers as you like).

E.g.

var eventHandlers = {
  foo: function (eventData) {
    // This is the foo event handler
  },
  test: function (eventData) {
    // This is the test event handler
  },
  // ... Add as many handlers as you like
};

someChannel.watch(function (data) {
  var handler = eventHandlers[data.event];
  if (handler) {
    handler(data.eventData);
  }
})