FlashComGuru Home streaming portalInfluxisCDNImediaseeUvault
                                                                                       Forum Index | Active Topics | Register
                                                                                                          List Overview | List Archives
                                                                                                                           About this site | Advertise
 

home

Adobe AIR (10)
Applications (36)
Books & Training (10)
Collaboration (15)
Components (8)
Events (69)
Flash Player (23)
Flex (32)
FMS (100)
General (119)
Hosting (5)
Jobs (14)
Off topic (36)
Press Releases (18)
Site Check (11)
Tools (46)
Videos & Players (67)

Follow me on Twitter

 
I'm currently working on a video chat app using Flash Media Server and Flex. This will be version 2 of my Ameegos.com excercise and this time I want to add real scalability to the app. It already supports multiple rooms (which users can create) and is based around a concept of one master application instance holding a persistent SharedObject containing all the room information which each room fetches as it starts up.
A very popular UK dating/adult site (URL on request ;-) has bought the code from me and is running a modified version of Ameegos on their site and the chat is proving popular to put it mildly. So popular in fact that a single server couldn't cope with the traffic and was frequently being brought to its knees by around 1500 concurrent video chatters.

And this is where the application failed: it wasn't able to scale across multiple servers; instead users have to manually navigate between servers.

Some background info: the room objects hold a variety of information, amongst it is a roomid which is used to build the application/room instance on the fly. As the rooms were already connecting to the master instance serverside and proxying a SharedObject I figured it wouldn't be too hard to store an entire RTMP string inside each room, effectively allowing unlimited servers to be added (in theory each room could have its own server although that's never necessary in this case as each room is limited to 70 to 80 users).
Long story short I went ahead and started building the serverside application which, if you have ever had to debug multiple instances of multiple applications on FMS, is *not* a nice thing to do.
The one problem I had to overcome was this: whe the very first user connects to the application (Which at this stage is not yet running) is automatically kicks onAppStart into life. Inside the onAppStart handler I am setting up my serverside connection to fetch the SharedObject contents from my master instance (no user ever connects directly to the master). I then make decisions based on that data as to where to send the user (best server/room in terms of current traffic). This was tricky because the SharedObject onSync would not fire quick enough before the onConnect was invoked and the connection accepted - this meant that crucial room data wasn't present in time and the first connection by the very first user was rejected. A rare case but still I wanted to fix it. But how?

I remembered reading about putting clients into a pending state before verifying credentials and accepting the connection later on. This should work for SharedObject related delays too...
A browse through the docs made it clear that this code would put a connecting client into pending (neither accepted nor rejected). Note: code formatting is a bit screwed up:

application.onConnect = function(client)
{
   return null; // client is now pending }
This was great but how can I accept this connection later on? How would I identify this particular pending client? Owen to the rescue who suggest to save a reference to it in the application scope:
application.onConnect = function(client)
{
// store client for approval this.client = client;
return null; // client is now pending }
This way I was able to process the connecting client and once the onSync returned approve it using a function that I could call from anywhere in my code, such as this:
approveConnection = function()
{
   if (application.client)
   {      
   // accept the connection application.acceptConnection(application.client);      
   }
}

What I still don't understand if this is really the right way to do it. What would happen if multiple clients connect at the same time - how does this code approve the right client reference? If I had multiple clients in a pending state would this code still work? Right now (with a single client) it works fine but I have a feeling that I may need to match the correct client via a application.clients lookup somehow or risk accepting the wrong one... Can someone clarify?

Comments
[Add Comment]
If I am not mistaken if another client connects before the onSync event fires, the reference to the first client (application.client) is lost and replaced by a reference to the second one.

I think the key here is to push pending clients in an array (application.pc_array) and then when onSync is fired accept the first one in the array.
# Posted By naicu octavian | 1/30/07 10:39 PM
thank you, I think you are right. I'll modify my code to do lookup.
# Posted By Stefan | 1/31/07 8:37 AM
I'm doing something similar with flash remoting:

application.onConnect = function(newClient)
{
var myASPXServiceResult = new ASPXServiceResult();
myASPXServiceResult.Client          = newClient;
var myASPXService = application.gatewayConnection.getService("evoChatRemoting",myASPXServiceResult);
myASPXService.startUserChatSession(newClient.ip);
}

ASPXServiceResult.prototype.startUserChatSession_Result = function(result)
{
if(result == "OK")
{
application.acceptConnection(this.Client);
}
}
# Posted By Jan Pies | 2/2/07 10:26 AM
hi Jan,
do you know if
application.acceptConnection(this.Client);
always reliably resolves to the right client?
# Posted By Stefan | 2/2/07 1:38 PM
Yes, the code above is reliable and proven, I have been using it for years in a frequently used application without any problems. You can store anything in a remoting result object, like references to client objects, I have been doing that ever since FlashCommServer 1.5 and it works like a charm.
# Posted By Jan Pies | 2/2/07 3:35 PM
thanks,
I'm just trying to get my head around it. I've used serverside Remoting myself and it always worked fine, I just want to figure out *how* it works.
This line
application.acceptConnection(this.Client);
would refer to whichever current Client then, it is not a avlue that you have stored previously anywhere, correct?

I wonder if someone can explain how the server keeps track of the different clients, I presume it *knows* which client initiated the remoting request and matches it to that automatically, even if another client connects in the meantime before the first Remoting call has returned.
# Posted By Stefan | 2/2/07 3:47 PM
ah sorry I missed that:
myASPXServiceResult.Client

You're storing the client reference - that makes sense. However for my purposes (which don't use Remoting) I still haven't figured out how to match clients properly. It's ghard to explain, maybe I should open a new post with more details.
# Posted By Stefan | 2/2/07 3:49 PM
Jan's code will work just fine. I am using remarkably similar code structure to do the same thing: postpone clients until I authenticate them trough remoting.

When he references the Client he's using
this.Client, but "this" actually refers to myASPXServiceResult, and in the onConnect function you have myASPXServiceResult.Client= newClient.

If he would have used: application.myASPXServiceResult.Client then his code would have failed when the next user connected before the previous one was accepted.
# Posted By naicu octavian | 2/2/07 3:57 PM
yeah I know the Remoting code works, but I am not using Remoting. I need to wait for a NConstatus before I approve clients. I am not sure how and where to store the client ref to achive that right now. I have some ideas... need to see how to implement it.
# Posted By Stefan | 2/2/07 4:03 PM
You could try and store it in the second Net Connection object that you are using to get the proxied shared object (nc.Client) or in the shared object itself
so.Client =...
followed by
so.onSync=function(){
this.Client
}

I hope this helps!
# Posted By naicu octavian | 2/2/07 4:24 PM
I reread your post:

>Inside the onAppStart handler I am setting up my serverside connection to fetch the SharedObject contents from my master instance

Why not set up this ss connection inside onConnect, and only set it up when the first client connects? this way you will have better acces to the Client object via ns.Client or so.Client

I hope this makes sense!
# Posted By naicu octavian | 2/2/07 4:50 PM
yes thanks it does. I have a few ideas and I am sure I can sort it.
the most obvious one (not sure why I haven't thought of this before, Will Law had to remind me) is to simply store all pending clients in an array and then simply approve them all (without having to match them up again) once my NC returns success. Hope this makes sense.
# Posted By Stefan | 2/2/07 4:52 PM
yeah I am obviously not getting enough sleep...
I could leave everything in my code as it was and simply add a pendingClients Array. Add every client to that while a variable startedUp == false and then approve all pending clients once the NS onStatus has returned. There was no need to identify a certain client, I can simply loop over them all and approve them. DOH DOH and DOH.

Who said FMS was hard? :-)
# Posted By Stefan | 2/2/07 8:23 PM