Section 16.1. Coordinating Instances

16.1. Coordinating Instances

Achieving a unified application experience often involves controlling what instance each user connects to and making users aware of what other people are doing in other instances. The broad categories of applications are (1) those in which each instance can pretty much manage itselfoften with help from an application server and databaseand (2) those in which instances must cooperate with one another in real time. An example of the first type of application is a video conference application in which one person manages a room instance and controls who is invited to visit it. A list of invitees held in a database is often all that is needed to invite users and control access.

An example of the second type of application is an online gaming system in which users enter one of a series of lobbies depending on their interests or how busy each lobby is. From there, they can see what game rooms are available and agree to move to one that contains a game of interest to them, such as chess. Another example in which real-time coordination is required is a help desk waiting room in which users wait in a queue to get into a help operator's room for assistance. Each operator may need to see who is in the queue and even select some users out of order based on information each user provides or information about ongoing problems.

There are three common ways to make multiple instances appear as one application:

  • Allow instances to manage themselves based on information in a database.

  • Create network connections between instances so that they can share information in real time with one another.

  • Have the instances poll an application server and database for real-time coordination.

16.1.1. Managing Independent Instances

For some applications, an application server working with a database can provide all the logic required to manage a set of application instances that do not need to interact directly with one another. In traditional teleconferencing applications, the essential part of setting up and using a conference is scheduling each meeting, establishing a connection between each conference site, and getting participants into the meeting room on time. A web-based conferencing system may not have to do much more. A meeting time must be set and an application instance name reserved for that time. Participants must be invited to the meetingoften by emailand sent a URL they can use to connect to the conference. Finally, only people invited to the meeting should be aware of the instance (room) and be able to connect.

The work of defining instance names and the user records for the people who will connect to them, distributing a unique URL to users, and controlling access can be done with a web application and database. FlashCom and Flash are not required for the setup phase. FlashCom (and Flash) are required only when each user actually connects to the conference room instance. For example, each user may receive a unique URL that includes a room name as extra path information. The .swf loaded from the page must know the RTMP address of the correct room to connect to. If the web application part of a conferencing system has generated the URL for a specific room, it can translate the extra path information into HTML FlashVars attributes that can be used to embed the RTMP address right in the page.

Another scheme is to have the .swf file access an XML file with the RTMP connection information. The room instances do not even need to be on the same server. A web application can schedule the use of room instances across multiple FlashCom Servers based on the total number of simultaneous participants. For example, each meeting that starts within a certain time window can be allocated to the server with the fewest number of scheduled users. An even more sophisticated system can be used that assigns users a load figure based on the user role they are assigned when they connect. Users with roles that will place a lot of demand on the server will get a higher load number and users who demand less, a lower number. The web application can use the user's load rating to spread application instances across multiple servers in a way that more accurately balances the load across machines.

This chapter does not discuss further the web applications that are responsible for setting up and controlling multiple but independent instances. However, Example 18-11 includes an example of using FlashVars and a discussion of how to restrict access to specific resources, such as restricting room instances to certain users. See the section called "Access Control Tables" in that chapter.

16.1.2. Interinstance Communications

The only way for instances to share information in real time is for one instance to create a network connection to another instance. As described in Chapter 4, a NetConnection object can be used to connect two FlashCom application instances in the same way a Flash movie uses a NetConnection to connect to an instance. The methods and NetConnection information objects are almost identical.

When one instance attempts to connect to another, it is treated as if it were a Flash client, so we can refer to it as a client instance . There are no restrictions on how many or what type of instance can connect to another one, other than the physical capacity of the server to support network connections. However, each instance-to-instance connection is counted by the FlashCom license manager, as discussed in the Preface. Instances of an application can connect to an instance of the same application on the same server, to a different application on the same server, or to applications on a different server.

Once a network connection has been established between instances, the client instance can use its NetConnection object, much as a Flash client can, to connect to resources that belong to the instance to which it is connected. The NetConnection object can be used by the client instance to play streams, connect to shared objects, and call remote methods that belong to the other instance. The instance that owns a shared object or stream is referred to as a master instance .

16.1.2.1 Proxies

After a client instance connects to a master instance, it can get a reference to the master instance's shared objects and play the master instance's streams. Flash clients connected to a client instance see the master instance's shared objects and streams as though they belong to the client instance. Because the client instance is making the master instance's resources available to its clients , the shared object and streams are referred to as proxies . See Chapter 5, Chapter 8, and Chapter 9 for more details on proxied streams, shared objects, and remote methods. From the Flash client's point of view, it simply uses a shared object, stream, or remote method belonging to the client instance to which it is connected. It has no way of knowing that the resource belongs to another instance.

Interinstance connections and proxies make possible a number of applications. For example, a chat room system can be built in which every visitor, regardless of whether she is connected to a room or the lobby, can know where all the other users in the system are.

Figure 16-1 illustrates a simple example of a lobby and chat room system.

Figure 16-1. A lobby and chat room system in which each room instance connects to the master lobby instance

Each Flash client connects to only one instance at a time. A user is either in the lobby or in a chat room. When each chat room instance is created, it opens a NetConnection to the lobby and proxies any resources it needs to make available to its clients.

A lobby instance can maintain a users shared object that each room updates as users connect and disconnect from each room. Since each slot contains user and room information, the shared object contains all the necessary information to determine where each user is within the system. If each client (chat) instance makes a proxy of the global user list available to its clients, every user will be able to see where everyone else is within the system. Sample code and a basic example of a lobby chat room system with a global user list is available at:

http://flash-communications.net/technotes/flashLobby/index.html

Of course, the scheme illustrated in Figure 16-1 has limits. If too many clients connect to the lobby instance, lobby performance will suffer. So a variation on having a single lobby with multiple chat rooms is to introduce multiple lobbies as illustrated in Figure 16-2.

Figure 16-2. Multiple lobbies connected together via a single master instance

Figure 16-2 illustrates a better performing and more flexible chat system. Clients first connect to one of many lobby instances. Each lobby instance connects to a single master instance. The master instance does not accept Flash client connections. Its only purpose is to provide each lobby with common resources such as a users shared object. Each lobby updates the users shared object when clients connect and disconnect. While a client is connected to the chat system, it never closes its connection to a lobby. The lobby provides each client with a proxy to the users shared object and any other resources it needs. For example, if a user connected to one lobby wants to invite someone connected to another lobby to chat, a message to the other user can be routed through the master instance.

When clients connect to a chat room, they create additional NetConnection objectsone for each chat room. Chat rooms do not need to connect back to a lobby or the master instance. Each client can tell its lobby what rooms it is connected to if that information must be made available to other clients.

Coding a multi-lobby system is not nearly as complicated as it may appear. As long as the code for the master instance, lobby, and chat rooms can be kept separate, it is not too difficult. A good practice is to use different .asc files within a single application to create instances with different behaviors.

For example, we can create five files in an applications/chatSystem subdirectory:


main.asc

Loads the other files depending on the RTMP address


master.asc

Responsible for creating global objects for the lobbies


lobby.asc

Connects to the master instance and proxies its resources


room.asc

Provides resources for each chat room


error.asc

Rejects client connections with invalid RTMP addresses

The main.asc file can load one of the other files by examining its own application.name property:

 switch (application.name.split("/")[1]) {   case "lobby":     load("lobby.asc");     break;   case "room":     load("room.asc");     break;   case "master":     load("master.asc");     break;   default: load("error.asc"); } 

Loading the correct .asc file relies on the use of RTMP addresses with the following format:

 rtmp://host.domain.com/chatSystem/   typeOfInstance   /   instanceName   

For example, when each lobby starts, it will request a connection to the master instance at:

 rtmp://host.domain.com/chatSystem/master 

When a client connects to a lobby, it will connect to an instance with an address such as:

 rtmp://host.domain.com/chatSystem/lobby/lobby21 

When a client connects to a chat room, it will use an address such as:

 rtmp://host.domain.com/chatSystem/room/room42 

In each case, the main.asc file checks the typeOfInstance portion of the RTMP address to determine which .asc file to load.

After each file is loaded and initialized , it can start accepting connections. The master instance needs to authenticate and accept connections from lobby instances only. It does not have to contain any code to accept connections from Flash clients. The lobby must open a connection to the master instance when it starts and need accept connections only from Flash clients. Room instances have only to authenticate and accept connections from Flash clients. Finally, the room and lobby instances have only to support the features expected from a room or lobby, but not both in the same instance.

Example 16-1 shows the contents of the master.asc file, which makes use of the PFCS framework discussed in the preceding chapter.

Example 16-1. The master.asc file
 load("pfcs/Text.asc");           //Part of PFCS framework load("pfcs/PFCSComponent.asc");  //Part of PFCS framework load("ActiveChatUsers_Root.asc");//See Example 16-3 validIPs = {}; validIPs["141.117"] = true; validIPs["209.29.148"] = true; validIPs["192.168.0.23"] = true; validIPs["127.0.0.1"] = true; application.onAppStart = function ( ) {   this.hostIdentifier = "k38djskl3o9";   this.clientHash = {};   activeUsers = new ActiveChatUsers_Root("main"); }; application.onConnect = function (client, instanceName, hostIdentifier) {   // The static   Text.trim( )   method trims leading and trailing whitespace. It is   // defined in the   Text.as   file, which is provided on the book's web site.   instanceName   = Text.trim(instanceName);   hostIdentifier = Text.trim(hostIdentifier);   // Make sure the strings have something that might be useful in them.   if (!instanceName  !hostIdentifier) {     application.rejectConnection(client,          {msg:"Empty or missing user instanceName or hostIdentifier."});     return false;   }   // Check the IP address of the client instance.   var ip = client.ip   var validAddress = false;   do {     if (validIPs[ip]) {       validAddress = true;       break;     }     ip = ip.substring(0, ip.lastIndexOf("."));   } while (ip.length > 0);   if (!validAddress) {     application.rejectConnection(client, {msg:"Access Denied."});     return false;   }   // Check the host identifier.   if (hostIdentifier != this.hostIdentifier) {     application.rejectConnection(client, {msg:"Access Denied."});     return false;   }   client.pfcs = {     user:{userName:client.ip + "/" + instanceName}   }   this.clientHash[client.pfcs.user.userName] = client;   client.writeAccess = "";   client.readAccess = "";   activeUsers.addClient(client);   return true; } application.onDisconnect = function (client) {   var userName = client.pfcs.user.userName;   delete this.clientHash[userName];   activeUsers.removeClient(client); }; 

Most of the code is designed to test whether a lobby instance is really connecting; the code is explained in detail in Chapter 18. An activeUsers object is created, which is responsible for creating a global users list. As each lobby connects, it is represented by a Client object within the onConnect( ) method. Each client object is passed to the activeUsers.addClient( ) method so that the activeUsers object can provide it access to the global users list.

Example 16-2 shows the contents of the lobby.asc file. It should look similar to the code discussed in Chapter 13 and Chapter 15, with the exception of the Auth component that is used to authenticate each Flash client. See Chapter 18 for information on the Auth component.

Example 16-2. The lobby.asc file
 load("pfcs/PFCSEventDispatcher.asc"); load("pfcs/PFCSFramework.asc"); load("pfcs/Auth.asc"); load("pfcs/PFCSComponent.asc"); load("pfcs/NetConnectionFactory.asc"); load("pfcs/SharedObjectFactory.asc"); load("ActiveChatUsers_Leaf.asc");//See Example 16-4 application.onAppStart = function ( ) {   pfcs = new PFCSFramework( );   // Tack on a NetConnection.   pfcs.nc = NetConnectionFactory.get( );   // The   connect( )   call includes a host password as the last parameter   pfcs.nc.connect("rtmp://localhost/chatSystem/master",                   application.name, "k38djskl3o9");   // Create an authentication object to authenticate clients.   auth = new Auth('lobbyAuth');   // Create a main Component Manager.   cManager = new PFCSComponentManager("main");   cManager.addComponent(ActiveChatUsers_Leaf); }; //   username   and   password   are actually   ticketID   and   ticket   . application.onConnect = function (client, userName, password) {   return auth.authenticate(client, userName, password); }; application.onAuthenticate = function (client, result) {   // Make sure the authenticated user has not already logged in.   if (pfcs.clients[result.USERNAME]) {     application.rejectConnection(client, {msg:"You are already logged in."});     return;   }   var noRoles = true;   for (var p in result.ROLES) {     var obj = result.ROLES[p];     if (typeof obj == "function") continue;     noRoles = false;     break;   }   if (noRoles) {     application.rejectConnection(client, {msg:"Access Denied."});     return;   }   // Control access and bandwidth based on one of the user's roles.   client.readAccess = "";   // Deny all readAccess to start.   client.writeAccess = "";  // Deny all writeAccess to start.   //   addClient( )   exTRacts data, including the userName, from the result    // object and stores it in the client in the   client.pfcs.user   object.   // Store the client in the   pfcs.clients   object by userName.   pfcs.addClient(client, result);   // If any components are still waiting, enqueue the client.   if (pfcs.getPendingComponents( )) {     pfcs.addPendingClient(client);   }   else {     // Add the client to each component.     cManager.addClient(client);     // Accept the client's connection request.     application.acceptConnection(client);   } }; application.onDisconnect = function (client) {   cManager.removeClient(client);   pfcs.removeClient(client) }; application.onAppStop = function ( ) {   cManager.close( ); }; 

Aside from the code to handle Flash clients, the lobby connects to the master instance as follows :

 pfcs.nc = NetConnectionFactory.get(  ); pfcs.nc.connect("rtmp://localhost/chatSystem/master",                 application.name, "k38djskl3o9"); 

The NetConnectionFactory.get( ) method returns a modified NetConnection object that the lobby uses to connect to the master instance. Any component that needs to know if the nc NetConnection is connected can set itself up as a listener on nc . In a full production environment, additional instance-level error handling should be provided in case the connection cannot be established.

In many cases, a client connection cannot be accepted until every component is fully initialized. When a component relies on a NetConnection to connect to another instance, the initialization may take some time while the connection is established and shared objects connected. Consequently, the onConnect( ) method places arriving Flash clients in the PFCS framework's pending client queue if any components are not ready:

 // If any components are still waiting, enqueue the client. if (pfcs.getPendingComponents(  )) {   pfcs.addPendingClient(client); } else {   // Add the client to each component.   cManager.addClient(client);   // Accept the client's connection request.   application.acceptConnection(client); } 

The preceding two .asc files are not long and complex, because much of the work of providing resources to clients is done by the PFCS framework and ActiveChatUsers component. The ActiveChatUsers component is similar to the PeopleList component used in earlier chapters but displays users connected to all lobby instances in one list. The complete code is available on the book's web site.

To further simplify coding, each file loads its own version of each communication component. As an example, let's look at creating the activeUsers shared object. The ActiveChatUsers component in the client includes a DataGrid to display the names of every user on the system. It connects to the activeUsers proxied shared object in the lobby instance. Within the lobby instance, an ActiveChatUsers object creates the proxy for the client to connect to by connecting to the master instance's activeUsers shared object. Finally, the ActiveChatUsers object in the master instance creates the global users list shared object. Figure 16-3 illustrates the relationship between instances and component objects.

Figure 16-3. Three ActiveChatUser objects working together to proxy and display in the client a shared object that belongs to the master instance

To make writing a component that has to be distributed across two separate instances (the lobby and master) simpler, create separate filesone for the master and one for the lobby:


ActiveChatUsers_Root.asc

Contains the code that implements the component in the master


ActiveChatUsers_Leaf.asc

Contains the code required to implement the component in the lobby

We chose filenames ending in "_Root" and "_Leaf" rather than "master" and "proxy" to reflect the typical upside-down tree structure of a FlashCom broadcast network, as seen in Figure 16-6. Root nodes represent a single master instance or server, and leaf nodes represent the instance or server to which Flash clients connect.

Example 16-3 shows the code from the ActiveChatUsers_Root.asc file that is loaded into the master instance by the master.asc code shown in Example 16-1.

Example 16-3. The ActiveChatUsers_Root class definition
 function ActiveChatUsers_Root (name) {   // Set this component's path.  PFCSComponent.apply(this, [name]);   // Create the shared object that the leaf instances will proxy with   // user information.   this.activeUsers_so = SharedObject.get("pfcs/ActiveChatUsers/main/activeUsers",                                         false); } // Inherit from PFCSComponent. ActiveChatUsers_Root.prototype = new PFCSComponent( ); ActiveChatUsers_Root.prototype.constructor = ActiveChatUsers_Root; p = ActiveChatUsers_Root.prototype; p.className = "ActiveChatUsers_Root"; p.addClient = function (client) {   // We assume a valid   ActiveChatUsers_Leaf   is connecting.   // Give read and write access to the path.   this.addAccessPath(client, "readAccess", "pfcs/ActiveChatUsers/main");   this.addAccessPath(client, "writeAccess", "pfcs/ActiveChatUsers/main"); }; p.removeClient = function (client) {   this.removeAccessPath(client, "readAccess", "pfcs/ActiveChatUsers/main");   this.removeAccessPath(client, "writeAccess", "pfcs/ActiveChatUsers/main"); }; p.close = function ( ) { }; 

The code uses the PFCSComponent base class and PFCSFramework introduced in Chapter 15. The master.asc file shown in Example 16-1, which implements the master instance, loads the ActiveChatUsers_Root.asc file and creates an object this way:

 load("ActiveChatUsers_Root.asc"); activeUsers = new ActiveChatUsers_Root("main"); 

Afterward, every lobby that connects to the master instance is passed to the activeUsers object:

 activeUsers.addClient(client); 

Example 16-4 lists the code for the ActiveChatUsers_Leaf class, which is stored in ActiveChatUsers_Leaf.asc and instantiated in the lobby. When the leaf instance is first created, it gets the nc object from the pfcs framework object and makes itself a listener on it. If the NetConnection is already established, the onConnect( ) method is called to set up the activeUsers shared object. Otherwise, the onConnect( ) method is called when the connection is established.

Example 16-4. The leaf object defined in ActiveChatUsers_Leaf.asc
 function ActiveChatUsers_Leaf (name) {   // Set this component's path.   PFCSComponent.apply(this, [name]);   this.nc = pfcs.nc;   this.nc.addEventListener("onConnect", this);   if (this.nc.isConnected) {     this.onConnect( );   }   pfcs.addPendingComponent(this); } // Inherit from PFCSComponent. ActiveChatUsers_Leaf.prototype = new PFCSComponent( ); p = ActiveChatUsers_Leaf.prototype; p.constructor = ActiveChatUsers_Leaf; p.className = "ActiveChatUsers_Leaf"; p.addClient = function (client) {   // Provide read-only access for Flash clients.   this.addAccessPath(client, "readAccess", "/pfcs/ActiveChatUsers/main");   // Add the client to the shared object.   this.activeUsers_so.setProperty(pfcs.getUserName(client),                                   {rn: application.name, t:new Date( )}); }; p.removeClient = function (client) {   // Remove read-only access from Flash clients.   this.removeAccessPath(client, "readAccess", "/pfcs/ActiveChatUsers/main");   this.activeUsers_so.setProperty(client.pfcs.user.userName, null); }; p.onConnect = function (ev) {   this.activeUsers_so = SharedObjectFactory.getRemote(                        "pfcs/ActiveChatUsers/main/activeUsers",                        false, this.nc);   this.activeUsers_so.addEventListener("onFirstSync", this); }; p.onFirstSync = function (ev) {   // Tell the framework we are ready!   pfcs.removePendingComponent(this); }; p.close = function ( ) { }; 

When the ActiveChatUsers_Leaf component has received its first onFirstSync( ) call, it is fully initialized and tells the pfcs framework it is ready. If it is the last component to report it is ready, the pfcs framework will accept the connection of any pending clients.

Finally, the ActiveChatUsers_Leaf object is responsible for updating the activeUsers shared object each time a client connects to or disconnects from the lobby. When addClient( ) is called, it adds a slot for the client in the activeUsers shared object. When the client disconnects, it deletes the slot in the shared object.

The code in Example 16-3 and Example 16-4 provides across multiple lobbies an activeUsers proxied shared object that contains a slot representing every user connected to all the lobbies. Other components can be designed to perform similar tasks , such as sending messages between any two clients. Or, in a large conference scenario in which each instance represents a separate conference room, other information, such as who is currently speaking in each room or a list of viewable streams from other rooms, can be distributed across instances.

A global users list with information about a hundred-thousand active users will require a large number of frequent updates and may consume a few megabytes of memory in every client that connects to it. Memory consumption on the server may be extreme, and the time and bandwidth required to send thousands of records to each client may be considered too much. For systems with several thousand simultaneous users or more, user searches and size -limited friend lists are more appropriate than simply providing a massive global list of every connected user.



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