Section 9.6. A Simple LobbyRooms Application

9.6. A Simple Lobby/Rooms Application

This example brings together all the topics we have covered so far in this chapter. You'll see how to use each kind of RMI in FlashCom to create much of the core code required to build real-world lobby/rooms applications.

In this scenario, we want users to connect to a lobby application, see a list of all the rooms on the site, create a room (with a password, for privacy), see how many people are in each room, and be able to join a room or delete one. The example does not include user authentication nor does it include user controls on who can create or delete particular rooms. However, it does provide the essential structure to which those features could be added. See Chapter 18 for more information on user authentication and authorization.

9.6.1. Using the Same Application Folder for the Lobby and Rooms

Since there is only one lobby and multiple rooms, one way to implement this on the server side is to use the default instance ( _definst_ ) for the lobby and all other instances for rooms. As should be familiar from the preceding chapter, here's our main.asc file, which checks the instance name to decide whether to load lobby.asc or room.asc :

 var slashPos = application.name.indexOf("/"); gAppName = application.name.slice(0, slashPos); gRoomName = application.name.slice(slashPos + 1); //   _definst_   is used for the lobby; any other instance is a room. if (gRoomName == "_definst_")   load("lobby.asc"); else   load("room.asc"); 

This approach allows us to keep all the server-side code for this application (lobby and rooms included) in the same application folder (in this sample, we'll call it lobby_rooms ), which makes it easier to deploy.

This example requires two separate Flash movies: one to connect to the lobby instance, which lets the user view, create, and delete rooms, and another to connect to an individual room instance. Let's look at the lobby part of the application first, followed by the room client and server code.

9.6.2. The Lobby

Here are some of the functions performed by the lobby:

  • Keep a list of rooms (each room's unique ID, name, password, and number of occupants )

  • Allow users to create new rooms

  • Allow users to delete rooms

  • Allow users to join rooms through client-side code

  • Interact with other application instances (the rooms) through server-side code

9.6.2.1 The client-side lobby.as code

Figure 9-1 shows the user interface of the lobby movie. A user can connect to a room by highlighting its name in the list and clicking the Join button. Selecting a room and clicking the Delete button results in a call( ) to the deleteRoom( ) method in the lobby instance, which deletes the room. Entering a room name and password and clicking the Create button results in a call( ) to the createNewRoom( ) method in the lobby, which creates a new room.

Figure 9-1. The lobby client interface

In the Flash authoring tool, place the following assets on the first frame of your Stage:


A List component

Give it the instance name roomList_lb .


Three Button components

Give them the instance names join_pb , create_pb , and delete_pb .


Two input TextFields

Give them the instance names roomName_txt and roomPwd_txt .

We stick with this example in AS 1.0 format for simplicity purposes, but the book's web site has a full-blown AS 2.0 code example.

Add the following code to the first frame of your movie (it's good practice to keep ActionScript in external .as files):

 #include "lobby.as" 

Save your movie as lobby.fla , and create an empty lobby.as text file in the same folder.

Let's go through the lobby.as code, one section at the time.

First, we define some constants and set up some UI handlers:

 // Location of your room's   .swf   movie, used in   getURL()   in   joinRoom()   . this.roomSwfURL = "http://myserver.com/lobby_rooms/room.swf"; // Set up UI handlers. this.create_pb.addEventListener("click", this); this.join_pb.addEventListener("click", this); this.delete_pb.addEventListener("click", this); this.click = function (evt) {   switch (evt.target) {     case this.create_pb:       createNewRoom( );       break;     case this.join_pb:       joinRoom( );       break;     case this.delete_pb:       deleteRoom( );       break;   } } 

We'll define the UI handlers ( createNewRoom( ) , joinRoom( ) , and deleteRoom( ) ) shortly.

Next , we create a NetConnection for this client:

 // Set up the room's NetConnection. this.nc = new NetConnection( ); this.nc.mc = this; this.nc.onStatus = function (info) {   trace(info.code);   if (info.code == "NetConnection.Connect.Success") {     // Connected; now connect assets.     this.mc.onConnect( );   } }; // Connect to the lobby. this.nc.connect("rtmp:/lobby_rooms/_definst_"); // The lobby is _definst_ 

Nothing special here. We call the onConnect( ) method once the connection to the lobby instance ( _definst_ ) is established. The rtmp:/lobby_rooms URI notation assumes that the two applications are running on the same server. Refer to Chapter 3 for more information about the NetConnection.connect( ) method.

Next comes the definition for onConnect( ) :

 // Called by   onStatus(  )   on success. this.onConnect = function ( ) {   // Get the shared object with the room names and user counts.   this.rooms_so = SharedObject.getRemote("public/roomNamesAndUsers",                                          this.nc.uri, true);   this.rooms_so.mc = this;   this.rooms_so.onSync = function (list) {     this.mc.updateRoomList( );   };   this.rooms_so.connect(this.nc); }; 

We get the remote shared object containing the room names and number of users and set up a simple onSync( ) method, which calls updateRoomList( ) , defined next:

 // Called by   rooms_so.onSync(  )   . this.updateRoomList = function (  ) {   // Not very scalable, but does the trick.   this.roomList_lb.removeAll( );   // Loop through the   data   array and add rooms to the list.   for (var roomID in this.rooms_so.data) {     this.roomList_lb.addItem(this.rooms_so.data[roomID].name + " (" +                              this.rooms_so.data[roomID].users + ")", roomID);   }   this.roomList_lb.sortItemsBy("label","ASC"); }; 

So every time we get an onSync( ) message on the shared object, we completely re-create the List component's contents; for each line, we add the name of the room and how many users are in it. For more efficient ways to update a list from the contents of a shared object, see Chapter 8 and Chapter 15.

All we have left to do is define the three click( ) button handlers. First, the one for the Create button:

 // Button handler for   create_pb   . this.createNewRoom = function ( ) {   // We could check for duplicate names here, but in this example   // we're going to allow them.   this.nc.call("createNewRoom", null,                this.roomName_txt.text, this.roomPwd_txt.text);   this.roomName_txt.text = this.roomPwd_txt.text = ""; }; 

The preceding method makes a client-to-server call to createNewRoom( ) (defined in lobby.asc , shown later), passing it the new room's name and password from the input text fields.

Now let's create the handler for the Delete button:

 // Button handler for   delete_pb   . this.deleteRoom = function ( ) {   var selectedRoomID = this.roomList_lb.selectedItem.data;   if (selectedRoomID!= undefined)     this.nc.call("deleteRoom", null, selectedRoomID); }; 

The preceding method simply gets the roomID of the selected room from the listbox and makes a client-to-server call to deleteRoom( ) (defined in lobby.asc ) to ask the server to delete the room.

Finally, here is the handler for the Join button:

 // Button handler for   join_pb   . this.joinRoom = function ( ) {   var selectedRoomID = this.roomList_lb.selectedItem.data;   if (selectedRoomID!= undefined) {     // Open the room swf in a new browser window, passing the roomID as a parameter     getURL(this.roomSwfURL + "?room=" + selectedRoomID, "_blank");   } }; 

The preceding code is the last method of lobby.as and is also very simple; it takes the selected roomID from the listbox and passes it to the room .swf , which we open in a new browser window with a getURL( ) call.

That's all the code necessary for the client side of the lobby application. Now let's look at the server side.

9.6.2.2 The server-side lobby.asc code

Let's step through the server-side lobby.asc file, which is loaded by the main.asc file if the application instance name is _definst_ .

First, we set up all the shared objects and data structures we need to function:

 trace("IN LOBBY"); application.onAppStart = function ( ) {   // Retrieve the last used   roomID   from a persistent private shared object.   this.lastRoomID_so = SharedObject.get("private/lastID", true);   if (this.lastRoomID_so.size( ) == 0) {     this.lastRoomID = 0;     this.lastRoomID_so.setProperty("id", this.lastRoomID);   }   this.lastRoomID = this.lastRoomID_so.getProperty("id");   // Get the shared object that contains the password for each room.   this.roomPasswords_so = SharedObject.get("private/roomPasswords", true);   // This shared object saves the name of the room and how many users are in it.   this.roomNamesAndUsers_so = SharedObject.get("public/roomNamesAndUsers", true);   // This is a hash table in which we save the server-to-server connections   // to each room, used in   swf_deleteRoom( )   .   this.serverSideConnectionsByRoomID = new Object( ); }; 

The code is pretty self-explanatory. It gets a private shared object named lastID , containing a counter used to create unique room IDs.

Then the code gets another two shared objects:

  • A private shared object containing an array of room passwords ( roomID is used to index into the array)

  • A public shared object (to which the client movies subscribe, as we've seen before) containing room names and user counts

We split the shared objects this way for security (so that clients never get room passwords sent to them via onSync( ) or in any other way).

We also create a hash table for saving references to server-to-server connections, which we will use in swf_deleteRoom( ) (described later).

Here comes the most interesting part of this example. Since the room names and passwords are kept only in shared objects used by the lobby, other server-side application instances (the rooms) will have to connect to the lobby (server-to-server connection) to ask for their own name and password. As we will see, all a room knows when it starts is its roomID . Also, we want the lobby to notify a room once it is deleted, so that it can in turn notify the .swf movies connected to it and stop accepting client connections.

The lobby's onConnect( ) method, therefore, needs to be smart enough to understand what type of client it is dealing witha .swf movie or a server-side room application instance. As seen earlier, we use the Client 's agent and referrer properties to identify a connection from another FlashCom instance:

 // Two types of clients connect to this app: .swf movies and room app instances. application.onConnect = function (client) {   // Clients cannot write to any shared object (or stream) directly.   client.writeAccess = "";   // Check the   client.agent   property and the app name.   if ( (client.agent.substr(0,8) == "FlashCom") &&        (client.referrer.split("/")[3] == "lobby_rooms") ) {     trace("This is a connection from room.asc!");     // This type of client can only interact with us using calls.     client.readAccess = "";     client.writeAccess = "";     // Get its room ID from the   client.referrer   property. This is done for     // added security; each room can make calls only about itself.     client.roomID = client.referrer.split("/")[4];     trace("roomID: " + client.roomID);     // Attach only the methods we want this client to be able to call.     client.getRoomInfo = asc_getRoomInfo;     client.personJoined = asc_personJoined;     client.personLeft = asc_personLeft;     // Save a reference to this client in the   serverSideConnectionsByRoomID   // table, used in   swf_deleteRoom( )   .     this.serverSideConnectionsByRoomID[client.roomID] = client;   } else {     trace("This is a connection from a .swf movie!");     //   .swf   movies can read the public shared objects and streams.     client.readAccess = "public";     client.writeAccess = "";     // Attach only the methods we want this client to be able to call.     client.createNewRoom = swf_createNewRoom;     client.deleteRoom = swf_deleteRoom;   }   application.acceptConnection(client); }; 

Notice how we don't allow server-to-server connections to subscribe to any shared object (by setting readAccess and writeAccess to an empty string), because we want them to interact with us only via calls (once again for security reasons). Client-side .swf movies, on the other hand, need to subscribe to the public/roomNamesAndUsers shared object, so they need to have read access set to "public".

Also notice how we attach only the appropriate methods to each type of client. Room instances will be able to invoke the getRoomInfo( ) , personJoined( ) , and personLeft( ) methods, while .swf movies will be able to invoke only the createNewRoom( ) and deleteRoom( ) methods. We don't attach any method to Client.prototype in this case, because we don't have any method that we want both types of clients to be able to call.

A last thing to notice is how we infer the roomID of the server-to-server connection from the client.referrer property. This trick, combined with the fact that getRoomInfo( ) doesn't take a roomID as input (as we will see shortly), makes the server-side application more secure.

Next, we have to define the three methods accessible by the rooms' application instances. We'll define the two methods accessible by the .swf movies later. Here's the code for the first method, to get the room information:

 // Methods for   room.asc   to call. None of these calls take a   roomID   parameter // because it is inferred from the   client.referrer   property in   onConnect(  )   . // Called by   room.asc   in   onAppStart( )   function asc_getRoomInfo( ) {   trace("getRoomInfo called by " + this.roomID); //   this   is the client   var roomSlot = application.roomNamesAndUsers_so.getProperty(this.roomID);   if (roomSlot == undefined) {     trace("The room doesn't exist.");     return {name: null, pwd:null};   } else {     roomName = roomSlot.name;     var roomPwd = application.roomPasswords_so.getProperty(this.roomID);     return {name:roomName, pwd:roomPwd};   } } 

This method is called by room.asc as part of the application startup code. This is how the room finds out its name and password. If the room doesn't exist (it's possible that someone tried to guess a roomID and attached it to the URL of the room.swf or simply is going to an old room that has since been deleted), we return a {name:null, pwd:null} object; otherwise , we get the values from the roomNamesAndUsers_so and roomPasswords_so shared objects and return them to room.asc .

The two methods that handle users joining and leaving a room are very similar to each other:

 // Called by   room.asc   in   verifyPassword( )   function asc_personJoined ( ) {   var roomSlot = application.roomNamesAndUsers_so.getProperty(this.roomID);    if (roomSlot == undefined)     return;   roomSlot.users++;   // This will trigger an   onSync( )   on the clients connected to the lobby.   application.roomNamesAndUsers_so.setProperty(this.roomID, roomSlot); } // Called by   room.asc   in   onDisconnect( )   function asc_personLeft ( ) {   var roomSlot = application.roomNamesAndUsers_so.getProperty(this.roomID);   if (roomSlot == undefined)     return;   roomSlot.users--;   // This will trigger an   onSync( )   on the clients connected to the lobby.   application.roomNamesAndUsers_so.setProperty(this.roomID, roomSlot); } 

This code is how a room instance tells the lobby that a user has successfully authenticated or that a user has left, and the lobby should update the users counter for the room. Notice how these methods don't return any value to the room and how setProperty( ) is used to trigger onSync( ) events on all .swf clients subscribed to the public/roomNamesAndUsers shared object (as we have seen, the onSync( ) method updates the roomList on the client side).

Figure 9-2 shows the code execution flow when a user enters a room. As you can see, room.asc connects to lobby.asc to notify it that someone has entered the room. The asc_personJoined( ) method updates the roomNamesAndUsers_so shared object, triggering a UI update in all the .swf clients connected to the lobby application.

Figure 9-2. Sequence diagram for a user entering a room

All that's left is to define the two methods that we saw the client-side lobby invoke in the previous section: createNewRoom( ) and deleteRoom( ) . Here is the server-side code for createNewRoom( ) :

 // Methods for the   .swf   movies to call. // Called by the lobby clients to create a new room function swf_createNewRoom (roomName, roomPwd) {   trace("createNewRoom: " + roomName + "," + roomPwd);   // Increment the   lastRoomID   counter and save it in the shared object.   application.lastRoomID++;   application.lastRoomID_so.setProperty("id", application.lastRoomID);   // Create a unique   roomID   that's hard to guess.   var roomID = "c" + application.lastRoomID + "r" + Math.round(Math.random( )*1000);   // Save an entry for the new room in the public   roomNamesAndUsers_so   // RSO (this will trigger an   onSync( )   on the clients).   application.roomNamesAndUsers_so.setProperty(roomID, {name:roomName, users:0});   // Save the password for the new room in the private   roomPasswords_so   RSO.   application.roomPasswords_so.setProperty(roomID, roomPwd);   trace("New Room Created:" + roomID); } 

First, the code increments the lastRoomID counter and saves it in the lastRoomID_so shared object (this guarantees us a unique ID). Then it manufactures a new roomID by using the counter and a random number, which makes it harder to guess.

We add the new entry to the roomNamesAndUsers_so shared object, which will trigger an onSync( ) on the clients (so that they can update their roomList_lb ), and save the password provided in the roomPasswords_so shared object. Figure 9-3 shows the process for creating a new room.

Figure 9-3. Sequence diagram for the creation of a new room

The last method of lobby.asc is deleteRoom( ) :

 // Called by the lobby clients to delete a room // (we could add a password check here). function swf_deleteRoom (roomID) {   trace("deleteRoom: " + roomID);   // Delete the slot from the shared objects.   // This will trigger an   onSync( )   on the clients.   application.roomNamesAndUsers_so.setProperty(roomID, null);   application.roomPasswords_so.setProperty(roomID, null);   // Server-to-server call to the room instance, which will notify   // the   .swf   s connected to it that their room has been deleted.   if (application.serverSideConnectionsByRoomID[roomID] != null)     application.serverSideConnectionsByRoomID[roomID].call("roomDeleted", null); } 

This code deletes the appropriate slots from the two shared objects, then makes a server-to-server call, this time from the lobby to the room instance, to notify that the room has been deleted. We use the client reference saved in the application.serverSideConnectionsByRoomID table (we saved it during onConnect( ) ) to find the appropriate application instance to call.

9.6.3. The Room Application

The room can perform a number of functions: it can be a video chat room, a multiuser game, or any other application you can imagine. Of interest for this chapter is how you connect to and get into the room with a password, and how you react to a room when it is deleted; the rest of the room application is left up to the reader.

9.6.3.1 The client-side room.as code

Figure 9-4 is a screenshot of the sample room we are going to build.

Figure 9-4. The login screen for a room

To create the room, in the Flash authoring tool, place the following assets on the first frame of your main timeline:


Two dynamic TextFields

Give them the instance names roomName_txt and feedback_txt .


An input TextField

Give it the instance name pwd_txt .


A Button component

Give it the instance name enter_pb .

Add the following code to the first frame of your movie:

 #include "room.as" 

Save your movie as room.fla and create an empty room.as text file in the same folder.

Let's go through the client-side room.as code, one section at the time:

 trace("Room = " + _root.room); if (_root.room == undefined) {   this.feedback_txt.text = "Invalid Room ID"; } else {   this.roomID = _root.room;   // The movie's NetConnection   this.nc = new NetConnection( );   this.nc.mc = this;   this.nc.onStatus = function (info) {     trace(info.code);     if (info.code == "NetConnection.Connect.Success")       trace("Connection successful, waiting until user enters correct password");     else if (info.code == "NetConnection.Connect.Rejected")       this.mc.feedback_txt.text = info.application.message;   };   // Called by the server, sending us the name for this room   this.nc.setRoomName = function (val) {     this.mc.roomName_txt.text = val;   };   // Called by the server if this room gets deleted   this.nc.roomDeleted = function ( ) {     this.mc.feedback_txt.text = "This room has been deleted";     this.close( ); // Closing the connection   };   // Connecting to the server   this.nc.connect("rtmp:/lobby_rooms/" + this.roomID); } 

As we saw in the joinRoom( ) method of lobby.as , the room.swf is passed a _root.room parameter. If the parameter was deleted by the user, we show an error. Otherwise, we define the movie's NetConnection , attach a regular onStatus( ) handler to it, and attach two methods setRoomName( ) and roomDeleted( ) that are going to be invoked by the server side. The setRoomName( ) method simply updates the roomName_txt text field to display the name of this room. The roomDeleted( ) method displays some feedback to the user and closes the NetConnection . Finally, we connect to the application instance (which has the same name as the roomID ).

The second half of the client-side code for room.as follows :

 this.onPasswordAccepted = function ( ) {   // The code for the actual room (chat, AVPresence, etc.) can now be initialized.   this.feedback_txt.text = "You would go inside the room now"; }; this.enter_pb.addEventListener("click", this); this.click= function (evt) {   var res = new Object( );   res.mc = this;   //   verifyPassword( )   returns true (password correct) or false (wrong password).   res.onResult = function (val) {     trace("verifyPassword returned " + val);     if (val == true)       this.mc.onPasswordAccepted( );     else       this.mc.feedback_txt.text = "Wrong Password, try again.";   };   this.nc.call("verifyPassword", res, this.pwd_txt.text); }; 

We define a method, onPasswordAccepted( ) , which is the gateway to the actual application. This method gets invoked only after the user has entered the correct password in the pwd_txt text field. Typically, one would show a different UI and connect any other asset (or communication components) to the main NetConnection at this point.

In order to get past the login screen, the user needs to send the correct password to the server. This is done through a client-to-server call to verifyPassword( ) , which returns TRue or false to the result object.

9.6.3.2 The server-side room.asc code

Let's step through room.asc ; as we saw earlier, it is loaded by the main.asc file if the application instance name is not _definst_ .

The onAppStart( ) logic for room.asc is more complex than usual:

 trace("IN ROOM"); // This is in a global function because we want to call it both upon // application startup and if the connection to the lobby is closed (reconnect). function connectToLobby( ) {   trace("Connecting to lobby...");   application.toLobby_nc.connect("rtmp://localhost/" + gAppName + "/_definst_");   // Note that   _definst_   is the lobby. } application.onAppStart = function ( ) {   application.roomDeleted = false;   // Server-to-server NetConnection to the lobby.   application.toLobby_nc = new NetConnection( );   application.toLobby_nc.onStatus = function (info) {     trace("Connection to Lobby onStatus:" + info.code);     if (info.code == "NetConnection.Connect.Success") {       // The lobby accepted our connection,       // now get this room's name and password.       var res = new Object( );       res.mc = this;       //   getRoomInfo( )   returns an object of the form {name: "xxx", pwd:"xxx"}.       res.onResult = function (val) {         trace("getRoomInfo returned: " + val.name + ", " + val.pwd);         // If   name   and   pwd   are null, the room no longer exists; notify all clients.         if ((val.name == null) && (val.pwd == null)) {           trace("This room doesn't exist any more");           this.mc.roomDeleted( );           return;         }         // Save the   name   and   pwd   in global variables.         application.roomName = val.name;         application.roomPwd = val.pwd;         // Notify all clients of the room name now that we know it.         for (var i = 0; i < application.clients.length; i++)           application.clients[i].call("setRoomName", null, application.roomName);       };       this.call("getRoomInfo", res);     }     else if (info.code == "NetConnection.Connect.Closed") {       // Connection lost, reconnect.       connectToLobby( );     }   };   // Called by   lobby.asc   when somebody deletes this room   // or by the preceding result object if   getRoomInfo(   ) returns   {null, null}   application.toLobby_nc.roomDeleted = function ( ) {     application.roomDeleted = true;     trace("This room has been deleted");     // Notify all clients (they will disconnect themselves).     for (var i = 0; i < application.clients.length; i++)       application.clients[i].call("roomDeleted", null);   };   // Create a permanent RTMP connection with the lobby.   connectToLobby( ); }; 

First, we define a global function that just connects the room's application instance to the lobby.

In onAppStart( ) , the code first sets the roomDeleted flag to false . Then it creates a server-to-server NetConnection to the lobby application ( application.toLobby_nc ). Just as we would if the room instance were a Flash client, we define an onStatus( ) handler on the NetConnection object. If the onStatus( ) handler notices that the NetConnection has been lost, it calls connectToLobby( ) to reconnect immediately. If the connection is successful, the room application instance immediately issues a server-to-server call, which invokes getRoomInfo( ) on the lobby, to find out its name and password. The getRoomInfo( ) function is defined in lobby.asc , as shown in the previous section. The code saves the results from getRoomInfo( ) in the global application.roomName and application.roomPwd variables. Because remote procedure calls are asynchronous, once we get the room name, we notify all the clients we might have accepted in the meantime, with the server-to-client call setRoomName( ) . If the result is {null, null} , it means that the room has been deleted in the meantime, so we tell each client to disconnect itself by calling the roomDeleted( ) method described later.

Next, we need to define a roomDeleted( ) method on this NetConnection , so that lobby.asc can notify us if this room gets deleted. In roomDeleted( ) , we set the roomDeleted flag to true , then we loop through all the connected clients and make a server-to-client call to the roomDeleted( ) method defined in room.as . That method will make the clients disconnect themselves after displaying a message.

Next we need to define an onConnect( ) method for room.asc :

 application.onConnect = function (client) {   if (application.roomDeleted) {     application.rejectConnection(client, {message: "This room no longer exists"});     return;   }   application.acceptConnection(client);   // If   getRoomInfo( )   already returned, pass the room name to this client.   if (this.roomName != undefined)     client.call("setRoomName", null, this.roomName);   // A client is not considered to be in the room until the user   // enters the correct password.   client.inRoom = false; }; 

This code first checks whether the room has been deleted. If so, we reject the connection with a feedback message and return. Otherwise, we accept the connection, and if the getRoomInfo( ) call already returned (i.e., if application.roomName is already defined), we call setRoomName( ) on this client. If this is not the case, setRoomName( ) will be called on the client when getRoomInfo( ) returns, as we saw in the onAppStart( ) function. Last, we set the inRoom flag to false ; although the connection has been accepted, the user is not considered in the room until she submits a valid password.

The asynchronous nature of the room-to-lobby (server-to-server) connection is a tricky point, which is best explained with the sequence diagrams in Figure 9-5 and Figure 9-6. Figure 9-5 shows the execution flow when a user connects to a room instance that's not already running. As the user connects, application.onAppStart( ) is triggered right before application.onConnect( ) . The room's onAppStart( ) method performs an asynchronous NetConnection.connect( ) to connect to the lobby instance, and the onConnect( ) method accepts the client's connection before the server-to-server connection is established ( onStatus( ) isn't invoked yet). When the onStatus event finally arrives, the room calls getRoomInfo( ) on the lobby, which is also an asynchronous call. Only when the call returns do we decide what to do. We either notify the connected client that the room was deleted (through server-to-client calls to roomDeleted( ) ), or we tell the client the room name (through server-to-client calls to setRoomName( ) ).

Figure 9-5. Sequence diagram for connecting to a room that's not already running

The execution flow for a second client that connects to the already running application instance is much simpler as shown in Figure 9-6. Because the room already knows its name and password (from the previous execution of getRoomInfo( ) ), the onConnect( ) handler can simply decide what to call on the client that's trying to connect (either reject the connection if the room has been deleted or call setRoomName( ) right after accepting the new client's connection).

Figure 9-6. Sequence diagram for connecting to a room that's already running

Next, we define an onDisconnect( ) function for room.asc :

 application.onDisconnect = function (client) {   trace("onDisconnect");   if (application.roomDeleted)     return;   // If the client was counted within the room, tell the lobby to decrease   // the occupant count when he leaves.   if (client.inRoom)     this.toLobby_nc.call("personLeft"); }; 

If the room has been deleted since a client connected, the code simply returns. Otherwise, if this client is already included in the occupant count for this room, we make a server-to-server call to the lobby to tell it that a person has left this room.

To finish our server-side room.asc code, we attach a function to Client.prototype , so that clients can call it to submit a password:

 // Called by the client Client.prototype.verifyPassword = function (pwd) {   if (pwd == application.roomPwd) {     // Password is correct; this client is now in the room.     this.inRoom = true;     // Tell the lobby to increase the occupant count for this room.     application.toLobby_nc.call("personJoined");     return true;   } else     return false; }; 

The verifyPassword( ) handler checks the submitted password against the one returned by getRoomInfo( ) . If the password is valid, the code sets the inRoom flag to true , and we tell the lobby (through another server-to-server call) that another person has joined this room.

That concludes our example. As is often the case when developing multiuser client/server applications, it feels as if all the different methods are like pieces in a puzzle, and one cannot see the full picture until all the pieces are in place.

This, and the asynchronous nature of remote calls, are perhaps the biggest challenges of developing client/server applications. We encourage you to ponder the previous example until you understand it fully, as it encompasses most of the concepts you will ever face when making use of remote procedures calls in client-side and Server-Side ActionScript.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net