Managing Connections

[ LiB ]  

The best way to understand SSAS is to consider how your application would suffer if it depended on an unreliable client SWF. Imagine, for instance, a card game with one user as the dealer. If the dealer closes his browser, the others are out of luck. If you put the code responsible for dealing in SSAS, however, the game could continue to run regardless of whether anyone was connected.

CSAS is just all the code in a SWF movie. All your SSAS goes into a plain-text file called main.asc that resides in your application directory. Each app maintains up to one copy of a main.asc file. (For example, the previous examples didn't have a main.asc at all.) In addition, you can share code between apps through the include() command (which is nearly identical to Flash's #include directive). Although you can edit the main.asc with any text editor, you really ought to use Dreamweaver because it includes syntax coloring and code hints (see Figure 9.1).

Figure 9.1. Although you can use any text editor for main.asc files, Dreamweaver MX 2004 includes code hints for server-side ActionScript.

graphics/09fig01.jpg


This section is dedicated to making a main.asc file that can trap server events (such as a user connecting or the application itself instantiating) and include methods that clients or the application can trigger. I suppose there are other details, too, but the point is all SSAS goes into that main.asc file.

Key Events

There are three key events to which you can write a script that responds: the application instantiating, any user connecting, and any user disconnecting. The syntax for each event follows a familiar form:


 application.onEvent=function(){ } 


You just have to replace onEvent with the particular event you're trying to trap. For the ones previously mentioned, that would be onAppstart, onConnect , and onDisconnect . These events are part of the Application object, but saying application just refers to the current application for which you're coding. For a quick preview, SSAS also contains the Client object. The upcoming examples show a few properties that give you access to attributes of any one connected client.

onAppStart

When the first user visits your app, an instance comes to life. That app instance name matches what the client SWF specifies in the connect() method (or uses _definst_ if no value gets passed). From that moment, and until the app is unloaded or restarted, that instance lives in FCS's memory. You'll also see it listed in the App Inspector. If there's any code you want executed at the beginning (for instance, perhaps you have some variables to initialize), put the code within the onAppStart event. Listing 9.1 provides a simple example (which, like nearly everything in this chapter, just goes in your main.asc file).

Listing 9.1. onAppStart Skeleton
 application.onAppStart=function(){ trace(this.name+" started at "+new Date().toString()); } 

Notice that in addition to demonstrating the onAppStart event, this example shows that one of the properties to any application instance is name . In addition to looking through the help files, a good way to find more such events and properties is to type application . into a text editor that supports FCS code hints (such as Dreamweaver) and the code-completion drop-down list will appear.

Another thing to notice here is that I'm using trace() and new Date() and toString() all standard ActionScript supported in SSAS. One slight difference, however, is that trace() outputs a string to the Live Log tab inside the App Inspector (not to your Output window as you might expect).

The easiest way to see the preceding example is to first place the main.asc file in an application folder, perhaps named say my_app, and then manually load the app by typing my_app/testInstance into the App/Inst field of the App Inspector. At this point, you'll be too late to have witnessed the application start, but you can select the instance now listed in the App Inspector, click View Detail, make sure you're looking at the Live Log tab, and then you click Reload App (see Figure 9.2). It's a hassle, but you'll get used to reloading apps and, besides, it's just the nature of how an app starts.

Figure 9.2. Reloading while already inspecting an app means you'll see trace statements in the onAppStart event.

graphics/09fig02.jpg


It may be obvious that code in the onAppStart executes before the onConnect event (that you'll see next ) triggers when the first user connects. Probably the most common use for onAppStart is initializing variables. If a slot in my remote shared object (RSO) will have an array (into which I plan to push() values), for instance, I'll use the onAppStart to dimension it as an array (by setting it equal to [] ). Also, because you can store variables within the application instance, an opportune time to initialize is at app start. (You'll see how to store variables in the application instance in the "Application Object's Additional Features" section and the syntax for RSOs in the "Accessing Remote Shared Objects from the Server Side" section.)

onConnect

Although onAppStart always happens first, you'll see that onConnect is a much more active event. Here you can trigger scripts that notify all other users when someone new has joined the app. In fact, it might be better to think of onConnect as "on attempt to connect" because inside the onConnect you can accept or reject a client. It's weird because if you don't have an onConnect callback defined, everyone is accepted. As soon as you define the onConnect event, however, you must include either acceptConnection() (to let a client in) or explicitly rejectConnection() (which effectively gets triggered anyway if you forget to include acceptConnection() ).

Although you must remember to accept a connection, it turns out that onConnect is mainly a place where you manage connections (for instance, track the active users) and where you assign variables to identify each particular client. Just like how an application can maintain its own set of variables, so can each client instance. Here I'm not talking of how a SWF may have a bunch of its own variables, however, but rather about how the server side can assign variables associated with particular connected clients. It's almost like how you might manage several radio stations on a car radio. Although you program one station under the 1 button and another under 2, each of those stations maintains its own properties such as their call letters and frequency (and they don't care under what button they reside). Ultimately, the server can't reach into a SWF and see its internal variables and a SWF can't reach into the server and see variables assigned to it (not directly anyway). It's not so much that you want to keep everything private (and you'll see how with messaging you can share any data you decide to). It's just that the application (that is, the "server side") has to have a way to keep track of all the connected users who areotherwiseidentical. Such tracking variables need not be available to the SWFs.

Listing 9.2 includes several common tricks. Remember, however, that the one thing you'll always need is acceptConnection() somewhere.

Listing 9.2. Example onConnect() Callback
 1 application.onConnect = function(thisClient) { 2 thisClient.user_id=Math.random(); 3 if (thisClient.referrer=="http://www.phillipkerman.com/test.swf"){ 4 application.acceptConnection(thisClient); 5 }else{ 6 application.rejectConnection(thisClient); 7 } 8 } 

Again, the only part you need inside onConnect is line 4. The basic code here assigns to this client a random value for their user_id propertya variable name I just made up. Then, if the built-in referrer property equals a specific string, they are accepted; otherwise , they're rejected. Notice both the acceptConnection() and rejectConnection() methods require that you pass an argument pointing to the client instance in question.

Naturally, a random ID for each user isn't practical, but I wanted to show that assigning homemade variables is identical to referring to properties. The variables assigned stay with this client instance while they are connected.

Note

Don't Forget application.acceptConnection()

I realize I've already said this a million times, but you really must include acceptConnection() if you're going to write an onConnect() callback. Even after saying this, I just spent 20 minutes hunting down a bug in the examples because I left out this simple line of code! It's particularly elusive because much of your app will continue to work.


Incidentally, rejectConnection() accepts a second optional parameter that you can use to explain to the client why they're not being accepted. For example, you may only want to allow 10 users to be connected at a time. To use the standard messaging techniques covered later, you must have a connected client. This technique doesn't require a connection. All you do is pass a generic object with named properties. For example, this can be used in place of line 6 in the preceding listing:


 var whyObj=new Object(); whyObj.reason="maxUserLimit" application.rejectConnection(thisClient, whyObj); 


Then, on the client side, you'll have to know to look for the reason property (which, I just made up) that will show up inside the application property of the info object received by onStatus . (And, you do have to use application verbatim.) Here's what it might look like on the client side:


 my_nc.onStatus = function(info) { if (info.code == "NetConnection.Connect.Reject") { if (info.application.reason=="maxUserLimit"){ //tell the user somehow } }; 


You can see that I've been using thisClient for the first parameter received. (I could have used anythingand for your information, most of the help files use newClient. ) The interesting part is that when a client SWF issues connect("rtmp:/my_app/testInstance"), onConnect() is then triggered and automatically receives a reference to the client doing the triggering. Actually, the client SWF can pass any number of optional parameters when they connect() , but the first parameter sent shows up in the second slot (after the client itself). Listing 9.3 shows a more practical alternative to the random user_id variable.

Listing 9.3. Passing Values to onConnect()

In the SWF, use the following:

 my_nc.connect("rtmp:/my_app/testInstance", username_txt.text); 

Then, the main.asc can look like this:


 1 application.onConnect = function(thisClient, username) { 2 thisClient.username=username; 3 this.acceptConnection(thisClient); 4 } 


Pretty simple really: Whatever text is found in the SWF's username_txt instance gets passed from the client side and then, on the server-side onConnect() , assigns that value to the client instance's homemade username property. You just have to remember the first parameter passed shows up second, after a reference to the client. Notice that thisClient in line 2 is the client instance and the this in line 3 is the application.

You'll see that after you have a reference to the particular client instance, you can easily grab any property or homemade variable.

In addition to accepting and setting variables for a connecting client, the onConnect() event is a good place to notify others that someone new has connected. Perhaps you only want to tell the teacher in a teacher/student application. It's also possible to send an object full of information back to a client as they're being disconnected. For these tasks , you could use an RSO or an alternative form of messaging (topics that both come up later this chapter). I guess I just wanted to say, you'll see onConnect() again.

One last note before moving on: Because SWF files can be decompiled, you'll never want to put the "right" password right in your FLA. However, it's not bad form to store a password in the main.asc; because, after all, you shouldn't make this file directly accessible to web visitors . For example, I could put the following code within the preceding onConnect() listing:


 if(username==" phillip "){ thisClient.role="admin"; }else{ thisClient.role="regular"; } 


In this case, "phillip" isn't a particularly difficult password to crack. The point is, however, that unless a client passes that exact string, the value for the homemade property role will be "regular" . There are certainly more types of validation schemes such as accessing a database of usernames through an application server. (Macromedia's DevNet site has several articles on this very topic.) However, although this example is hardwired and uses an easy string to crack, this is a legit way to control access. (To carry this through, you'd want to refer to the homemade role variable later in the code and treat those with a value of "admin" differently than you treat a "regular". )

Note

Using the Component Framework

If you use any of the communication components , they will effectively commandeer the onConnect event, so be sure to read the "Integrating the Communication Components" section at the end of this chapter to learn how to get around this.


onDisconnect

To round out the three key events, onDisconnect() triggers when a user closes the browser or navigates to another page. In addition, if you boot off a user using clientInstance.disconnect() , that user exits via the onDisconnect() event. By the way, just like onConnect() , you don't have to write an onDisconnect() callback. If you do, however, include one important line at the very end: return true . It's not as critical as acceptConnection() is to onConnect() , but by doing so you'll avoid weird situations where FCS thinks a particular client is still connected. Here's a good starter script:


 application.onDisconnect = function(thisClient) { trace(thisClient.username + " has left the app"); return true; } 


This code assumes you assigned a username property to this client at some point earlier (for instance, when the client connected). Notice that at the end, I return true . This doesn't have to be on the very last line. Realize, however, that when return is encountered , it skips the rest of the code.

Note

Keeping an App Running

Application instances will time out based on a setting in your configuration folders. However, the following code shows how to override that behavior:


 application.onAppStop=function(){ return false; } 


Basically, every time the app attempts to stop, you say "no."


There's not a whole lot more I can say about onDisconnect() except that it's a convenient time to handle certain "housekeeping" tasks. For example, you might actually want to notify all the remaining users that "client x has left the building." Also, if you're maintaining an application variable or RSO containing a list of connected users, you'll want to remove the person who just left. These sorts of tasks involve a few things not yet covered, so wait until you get through the next few code listings to see how you would actually pull it off. For now, just realize that these key events are available and you can write code that triggers when they occur.

Application Object's Additional Features

The Application object has more to offer than just a few key events (discussed earlier). There are more events, some built-in properties, and the ability to create your own variables (equivalent to built-in properties). Also, as you'll see in the "Accessing Remote Shared Objects from the Server Side" section later in this chapter, when you get() an RSO on the server side, you'll probably want to store the reference in an application variable.

You can think of application variables as global variables. That is, once created, you can access them from anywhere in your main.asc file. The syntax is very straightforward: You just use the form application.variableName including "application" verbatim. (Alternatively, you can use a reference to the active application, such as this , when inside an application callback.) The following example uses a couple of homemade application variables to track who's connected. It relies on an Input text field instance username_txt, a button instance connect_btn, and this code inside the SWF:


 connect_btn.onPress=function(){ my_nc= new NetConnection(); my_nc.connect("rtmp:/track_user/r1",username_txt.text); } 


Then, the code in Listing 9.4 goes inside your main.asc file inside the application folder track_user.

Listing 9.4. Using Application Variables to Track Users
 1 application.onAppStart=function(){ 2 this.userList=[]; 3 this.userCount=0; 4 } 5 6 application.onConnect = function(thisClient, username) { 7 this.userCount++; 8 thisClient.slot=this.userCount-1; 9 this.userList.push(username); 10 trace("total users = " + this.userCount); 11 this.acceptConnection(thisClient); 12 } 13 14 application.onDisconnect = function(thisClient) { 15 this.userCount--; 16 var newList=[]; 17 for(var i=0;i<this.userList.length;i++){ 18 if(thisClient.slot != i){ 19 newList.push(this.userList[i]); 20 } 21 } 22 this.userList=newList; 23 trace("remaining users "+this.userList); 24 return true; 25 } 

There's a little bit of everything here. First you'll see application.userList and application.userCount is initialized (in lines 2 and 3) as an array and number respectively. (Actually, I use this.userList , but that's the same as application.userList while inside this callback.) Then, anytime someone connects, userCount is incremented (line 7) and, to give each client instance a unique identifier I made up the client variable called slot (which appears again when the user disconnects). I really couldn't resist including that a bit early (the next section is about the Client object). Anyway, the application variable userList contains an array of all usernames, and userCount tracks how many items are in that array.

Down in the onDisconnect callback, you can see that first userCount is decremented (line 14). Then I make a fresh array variable newList that will get built up during the loop in lines 17 through 21. Basically, this loop adds each username from userList to newList unless the index matches the slot property for the client who's leaving. Finally, line 22 dumps the temporary newList into userList .

I should probably mention that not only is the userCount variable in the preceding listing unnecessary (after all, I could always just check the length property of userList ), but there turns out to be a built-in property for all application instances called clients . It's some sort of weird cross between an array and a generic object. In practice, it's closer to an array, as this example shows:


 for (var i = 0; i < application.clients.length; i++){ trace(application.clients[i].username); } 


Notice that application.clients[i] refers to the actual client instance in that slot (whereas my userList was a list of strings). Also, your client instances need a username property created for this code to work.

Application variables are just plain convenient. Alternatively, you may find a use to store variables inside RSOs. RSOs offer the following three advantages over application variables:

  • They can be set to be persistent, whereas application variables are cleared when the application unloads.

  • Clients can be automatically and immediately notified when an RSO's contained variables get modified.

  • Clients have direct access to the values inside RSOs.

With all those advantages of RSOs, application variables are still a better choice when appropriate because they're so easy to use.

Finally, although standalone functions aren't really part of the application object per se, I think it makes sense to mention that you can write functions in the main.asc the same way you can in Flash MX 2004.

Now that you've learned about the Application object, it's time to move on to the Client object. Although these are two different objects, they work together and your scripts will nearly always contain a reference to both.

Additional Client Object Information

It turns out that if you're reading this chapter in order, you've already seen a bit about how the Client object works. From the perspective of the server, each connected user is an individual instance of the Client object. Given a reference to a particular instance, you'll gain access to various properties and methods. In addition, you can make your own variables that get treated like properties. (As a reminder, you saw the built-in referrer property and a few cases with made-up homemade variables, including username and slot. )

It's important to understand that unlike how the Application object enables you to use either the literal application or a reference such as this , you must always use a reference when you intend to address an individual client instance. That is, if you ever use Client literally, you'll be referring to the Client objectnot a particular instance. We'll use Client later in this section when attaching functions to its prototype (thereby adding the function to every instance). To access properties, methods, and homemade variables, however, always use a reference to a particular client (for example, the parameter I keep naming thisClient that gets passed to the onConnect event).

Although the sky's the limit for what sorts of homemade variables you assign to client instances, do check out the server-side ActionScript dictionary; in it you'll find some really handy methods and properties. In addition to referrer , you'll find properties such as ip and agent , which are both useful. In addition, getStats() and setBandwidthLimit() are interesting methods.

There's still more you can do with the Client object. When you read about call() in the "Messaging" section of this chapter, you'll see how to let client SWFs trigger functions defined inside the main.asc. Even still, the Client object isn't super excitingit's just a convenient and necessary way help keep things in order.

Accessing Remote Shared Objects from the Server Side

As you learned in Chapter 8, "Foundation Communication Server," RSOs enable you to share efficiently data among multiple connected users. The application can have access to and read and write RSOs as well as receive notices when others change RSOs. On top of this, a server-side script (that is, your main.asc file) has the additional power to lock an RSO when it's about to change a value so that no one else can mess with it. This means that there's no need to check whether a change succeeded (like you do on the client side). Locking also temporarily pauses any notices going out that could otherwise repeatedly trigger everyone's onSync events. Finally, RSOs offer a gateway over which you can send messages to everyone connected to the RSO.

Syntax Differences

To optimize performance, the syntax for reading and writing data stored in an RSO doesn't use the familiar dot syntax. The two equivalent code samples in Listing 9.5 compare CSAS to SSAS by achieving the same basic task.

Listing 9.5. Remote Shared Objects in CSAS Versus SSAS

In CSAS:

 my_rso=SharedObject.getRemote("some_so", my_nc.uri, true); my_rso.data.myProp=my_rso.data.myProp+1; 

In SSAS:


 myRSO=SharedObject.get("some_so", true); current=myRSO.getProperty("myProp"); myRSO.setProperty("myProp", current+1); 


The client-side code should be familiar from Chapter 8. Basically, the some_so RSO contains a variable called myProp . (Remember, variables are accessed as properties within the data property.)

There are a few things to notice as different in the server-side code. First, there's only a get() method (no getRemote() , because everything is remote). Second, you don't have to include the NetConnection uri property. Finally, the big difference is that instead of accessing the values inside the data property (using dot syntax), you must use getProperty() and setProperty() . These both require you to provide a string version of the property name.

For some, setProperty() and getProperty() will bring back memories of Flash 4; for others, it will just appear funky. It's actually slightly more convenient when identifying property names dynamically (as opposed to using bracket references).

Remember that with dot syntax you can access properties dynamically by generating a string placed in brackets. Anyway, the server-side version always uses strings.

In practice, it makes sense to first get your SSAS to grab a reference to an RSO, store that value in an application variable, and then just apply getProperty() and setProperty() to that reference. That is, you don't need to get() the RSO before each use. For instance, this is a typical example from an onAppStart event:


 Application.onAppStart=function(){ this.myRSO.SharedObject.get("some_so", true) if(this.myRSO.getProperty("myArray")==undefined){ this.myRSO.setProperty("myArray",[]); } } 


This code just creates an application variable called myRSO and makes sure it starts with a fresh array. From this point forward, you can refer to this.myRSO or application.myRSO and then continue with setProperty() or getProperty() .

While on the subject of storing an array in a slot of an RSO, take a look now at perhaps the biggest inconvenience of the syntax differences. Suppose you just want to push() a value on the end of the array (in your RSO). You can't just add ".push()" on the end of your getProperty() . You have to do a two-step process (drawn out in Listing 9.6).

Listing 9.6. Accessing an Array in an RSO
 var temp=application.myRSO.getProperty("myArray"); temp.push("a value"); application.myRSO.setProperty("myArray", temp); 

The idea is to grab the array, push() a value into it, and then put it back into the RSO. On the client side, you could do it in one swoop like this: myRSO.data.myArray.push("a value") . It's just that in the case of RSOs on the server side, you must get and then set.

Although the syntax differences for RSOs on the server side are significant, you'll be happy to know these differences are balanced by the fact that they are powerful and have some special features.

Locking

One really cool feature of RSOs on the server side is that you can lock and unlock them. The main sequence is lock it, change it, and then unlock it. On the server, therefore, you can change values in an RSO with utmost confidence that the change will be successful. You may recall from Chapter 8 that whole deal with the info object (received in the onSync callback) that had various values for the code property. You can still use onSync on the server side, but you won't need to check whether info.code=="success" as you must on the client side.

Mainly, locking gives priority to the server-side script. (That is, clients can't jump in and change the RSO while it's locked.) Another nice feature is that when you finally unlock the RSO, all the changes are sent to clients in a single message. Realize that, normally, each client is notified of each change to each property in an RSO as it changes. Lock just says, "Don't let anyone edit this RSO...do these changes...and now that we're done, tell everyone what's happened in a single message." Listing 9.7 provides a simple example.

Listing 9.7. Simple Lock/Unlock Sequence
 application.myRSO.SharedObject.get("some_so", true); application.myRSO.lock(); application.myRSO.setProperty("myProp","a value"); application.myRSO.unlock(); 

I guess the only thing to add is that you definitely need to remember to unlock the RSO.

Sending Messages on RSOs

So far this discussion has focused on how to use RSOs on the server; now, however, it's time to transition to the topic of using RSOs for more than just storing shared data. Namely, you can send messages over an RSO. That is, one connected client (or the server) can issue myRSO.send("someFunction") and everyone connected to myRSO will have the someFunction() triggered. It's very similar to how messages are sent over the LocalConnection object. However, this isn't "local"; it's over the network. You'll see how send() works in the next section. Just remember it's tied to RSOs.

Messaging

As already mentioned a few times, you can send messages between connected users. The term messaging might need some explanation. Here I'm talking about triggering functions defined in the other client's files and, optionally , passing values. The two basic ways are SharedObject.send() and NetConnection.call() . For send() , you use an RSO instance and trigger functions in all connected clients' files. For a single client to trigger a function in the main.asc file and get a result returned, you must use call() on a NetConnection instance. Although it was impossible for me to get this far in the book without mentioning send() and call() , now it's time to go through both in detail.

Using SharedObject.send()

You can use RSOs as a channel over which you trigger methods in all connected clients. Like any function or method, there's two parts : the definition and the trigger. For the definition, you just define a callback on an RSO instance. Then, to trigger it, you use the send() method on an instance connected to the same RSO. Listing 9.8 shows a typical example. You just need a my_btn button instance, a message_txt input text field, an other_txt dynamic text field, and an empty application folder called messaging.

Listing 9.8. Using SharedObject.send()
 1 my_nc=new NetConnection(); 2 my_nc.connect("rtmp:/messaging/r1") 3 4 my_rso=SharedObject.getRemote("someRSO",my_nc.uri,false); 5 6 my_rso.onMyFunction=function(param){ 7 other_txt.text="someone said " + param; 8 } 9 10 my_rso.connect(my_nc); 11 12 my_btn.onPress=function(){ 13 my_rso.send("onMyFunction", message_txt.text); 14 } 

Nothing much new here except in line 6 you see the onMyFunction callback defined (on the my_rso instance). In addition, it's set up to accept a parameter. Then, in line 13 you see how to trigger that function using send() (again, on the my_rso instance). When you test this by yourself, you just see the text move from message_txt to other_txt .

Note that onMyFunction can be renamed . And, you can define more callbacks.

Listing 9.8 is more interesting when two people can connect and send messages to each other. However, the fact that it works by itself is a bit interestingjust because now you know send() looks for a function attached to the RSO instance in any movie connected to that RSO (including the movie that triggered it). Suppose you want to send messages to everyone else except yourself? (Ignore, for the moment, that you could probably pull this off by changing variables inside an RSO.) One solution is to send an additional parameter identifying me and only trigger code in the function when that parameter doesn't match me . However, you'll need a surefire way to uniquely identify each user. That is, if you just use the user's login name or something, there might be two Phillips .

A different tack involves making two or more SWFssay, student.swf and teacher.swf or admin.swf and regular.swf . Then, the contents of one movie's onMyFunction callback could contain different scripts. Personally, I try to have as few different source FLAs as possible to maintainso, even if I want several different roles, I try to squeeze them all into a single FLA that dynamically changes depending on the user. (Say when the teacher logs into the SWF buttons that are normally hidden get revealed.) It's not so bad if you just have two FLAs to maintain. If you want more uniquely identified users, the server-side script can certainly handle giving each client a unique number (see Listing 9.4). However, you'll have to wait until call() is discussed to see how the server can pass variables back to the client. Doing it all on the client side would definitely be a hassle. You could have a variable inside the RSO that each user increments when the user connects and that is then used to identify each user. To ensure no two users have the same value, you have to check the success every time a user changes it. Go ahead and work this out if you want. The downloadable files for this chapter contain a solution. Just remember the main point here: A little bit of server-side code will make it much easier.

Before looking at call() , it's worth looking at an example of server-side code using send() . In fact, it is a way for variables to get passed (as parameters) from the server to all users connected to an RSO. Listing 9.9 shows how the server can notify everyone when someone new arrives (without using a data slot in the RSO). This yexample is like Listing 9.4.

Listing 9.9. Using SharedObject.send() on the Server Side

Put this code in your main.asc file in the track_user application folder.

 1a application.onAppStart=function(){ 2a application.message_so=SharedObject.get("mso",false); 3a } 4a application.onConnect = function(thisClient, username) { 5a application.message_so.send("onNewUser", username) 6a thisClient.username=username; 7a this.acceptConnection(thisClient); 8a } 9a application.onDisconnect = function(thisClient) { 10a application.message_so.send("onLeaveUser", thisClient.username); 11a return true; 12a } 

In the FLA, you need a username_txt input text field instance, a connect_btn button instance, plus a dynamic text field named message_txt , plus this code:


 1 connect_btn.onPress=function(){ 2 my_nc= new NetConnection(); 3 my_nc.onStatus=function(info){ 4 if(info.code=="NetConnection.Connect.Success"){ 5 initSO(); 6 } 7 } 8 my_nc.connect("rtmp:/track_user/r1", username_txt.text); 9 } 10 11 function initSO(){ 12 message_so=SharedObject.getRemote("mso", my_nc.uri, false); 13 14 message_so.onNewUser=function(who){ 15 message_txt.text = "New user: "+who; 16 } 17 message_so.onLeaveUser=function(who){ 18 message_txt.text = who + " has left"; 19 } 20 message_so.connect(my_nc); 21 } 


To compare the client side to the server side, first notice that the same name is used to identify the RSO in line 2a and line 12. That is, both client and server share the RSO named mso . Notice that it's unimportant that the server-side code uses application.message_so and the client side uses message_so . (Those are just the name for the local instance.)

Anyway, the main idea here is that lines 5a and 10a of the server-side code use send() to trigger a function defined in the client SWFs. It also passes a parameter. Then in lines 15 and 18 the client SWF displays the received value in a meaningful string onscreen.

RSO's send() is often more convenient than combining data properties with the onSync event. However, onSync can be more reliably lightweight. Because FCS handles all requests to send messages or change variables, you don't really know exactly when users will receive triggers sent (using send() ). At least with data properties (variables in the RSO) you have the info.code property, which enables you to check success. Deciding to use variables or send() is partially a matter of personal preference. I think it really comes down to the nature of your objective. If you want to ensure everyone is in sync with variables, you might as well just share those variables. For an easy way to send out notices (that aren't extremely time critical), however, use send() .

Using NetConnection.call()

The call() method is particularly useful because it enables you to trigger functions that return values. That is, just triggering a function is useful; sending parameters is still more useful; but, sometimes, you'll want to write a function that returns a result to whoever called it. For example, you may want a function to calculate a value and send the answer back. In Chapter 8, we actually used call() to ascertain the length of a stream. Because only SSAS can find a stream's length, however, the client movie called a function on the server that returned the answer.

Not only does call() have the option for returning values, it's also the only way you can truly trigger scripts in an individual client or just the main.asc file. That is, when you issue send() on an RSO, every client or main.asc currently connected to that object receives the event. Actually, if a particular SWF has no callback defined for the event name in the send() , it is ignored. But with call() , it's possible to target just a single client or the main.asc.

Listing 9.10 shows first how a single client can call() a function in the server script, and then how the server calls a particular client's script. In the first example, the client passes a parameter but does not expect any results returned.

Listing 9.10. Using call() from the Client

Anywhere in the main.asc file, include this code:

 Client.prototype.myFunction=function(param){ trace("client says " + param); } 

Then, from inside any SWF, you can issue this script:


 my_nc.call("myFunction", null, "hello"); 


I suppose it's weird that in the SWF you issue call() on your NetConnection instance and that triggers a function in the prototype of the Client object at the server. In any case, notice that the first parameter in call() is the function name and the second parameter is for an optional object that you prepare to handle a result (discussed next). That parameter is null for now because nothing is being returned from the server-side function. Interestingly, to send parameters, you start with the third parameter (after null in the preceding example), and that value will arrive at the server function inside the parentheses.

In Listing 9.11 you can see that the server script can trigger a function defined on the client side in nearly the opposite way: This time, from the server, this example call() s on a client instance to trigger a callback defined on the NetConnection instance inside a particular client SWF. It sounds worse than it is.

Listing 9.11. Using call() from the Server

You can set up the client-side code first:

 my_nc= new NetConnection(); my_nc.onStatus=function(info){ if(info.code=="NetConnection.Connect.Success"){ //proceed } } my_nc.onMyFunction=function(param){ trace("server said: " + param); } my_nc.connect("rtmp:/test_app/r1"); 

Now you can trigger onMyFunction from the server-side code:


 application.onConnect=function(thisClient){ application.acceptConnection(thisClient); thisClient.call("onMyFunction",null, "I'm a server"); } 


Notice that the onStatus callback has been left in the client-side codenot only for good practice, but because it shows how custom callbacks are nearly identical.

The trick with going from the server to the client is to confirm that you have a reference to the client instance. In addition, notice that I don't issue call() until after I've issued the acceptConnection() on the client.

Now that you know the basic structure for call() , it's time to cover the way it returns values. The definition for the function will look familiar: just the keyword return followed by the value you want to send back. When such a function gets called, however, you must first set up a "result object" with an onResult callback defined. That's because such function calls traveling over the network run asynchronously. (Because they may take a second, you don't want Flash to freeze while it waits for the result.) The following practical listing should look familiar from Chapter 8 (where it was also used). However, this time it should make more sense.

Listing 9.12. Using call() to Find a Stream's Length

Here's the function on the server side:

 Client.prototype.getLength =function (filename){ return Stream.length("mp3:"+filename); } 

Here's the client-side part that first sets up the result object and then triggers the


 call(): resultObj = new Object(); resultObj.onResult = function(result) { totalLength = result; }; my_nc.call("getLength", resultObj, "rock"); 


First, resultObj is just a homemade variableyou can name it anything. However, you must use onResult for your callbackthat's what gets triggered when a function called with call() returns a value.

As a quick reminder how this works (and, coincidentally, a quick preview of the "Server-Side Stream Object" section coming up), only server-side code can use the Stream.length() method. If you translate the last line of the preceding example, it would read, "Trigger the getLength function on the server; use the generic object resultObj that I predefined to handle the result; and pass the string "rock" (because the server-side function needs a stream filename from which to get the length)."

Optimizing

I'm sorry to say that this section won't tell you "everything else" you need to know to make successful FCS apps. There's so much more, and you'll only really begin finessing your apps after you build a few. Most of my suggestions come from the experience of doing things the hard way first. That's not a bad way to learn because you sometimes appreciate things more. This section covers just a few tips and tricks to both enhance your app's performance and reduce the work necessary to build it.

It's Still ActionScript

This may go without saying, but remember that CSAS and SSAS are both ActionScript. Nearly everything from traditional ActionScript is supported in FCS. For example, you can use the Date object, the Math object, and setInterval() (just to name a few personal favorites). For example, the auction site I built needed a countdown clock that would keep ticking away regardless of who was connected. Obviously, I had to keep the timer code in the server script. It was pretty simple to define an interval with setInterval() . Note that no ActionScript 2 is supported in FCS.

Defining Functions Inside the Prototype

Because it's possible to have thousands of people connected at one time, it's good to know at least a little bit about extending the prototype of an objectin this case, the Client and the Application objects. When we set up a function on the server side that a particular client could call() , we defined that function inside the prototype of the Client object. Obviously, we didn't want to define it for just one client instance. In additionand, probably not as obviouswe didn't want to define it for each client individually. That is, putting a function definition inside the prototype of an object type makes it available to each client that gets instantiated . However, it doesn't get duplicated with each instance. If you define a function on each individual instance, it does get duplicated and begins to eat up memory unnecessarily. Listings 9.13 and 9.14 shows the difference.

Listing 9.13. Sloppily Defining Functions on Each Instance
 application.onConnect(thisClient){ application.acceptConnection(thisClient); thisClient.onMyBadFunction=function(){ //some code } } 

In this example, every new client instance (new user connecting) is given its own identical copy of the onMyBadFunction . Each copy takes up additional memory. Don't do it this way!

Listing 9.14. Efficiently Defining a Function in the Prototype
 1 application.onConnect(thisClient){ 2 application.acceptConnectiont(thisClient); 3 } 4 Client.prototype.onMyFunction=function(){ 5 //some code 6 } 

I didn't need the onConnect part but included it to show that lines 4 through 6 can stand on their own. The idea here is that onMyFunction is now part of the prototype for the Client objectthat's big "C" Client meaning the object, not an individual instance. When defining functions this way, you store them in the prototype, meaning the template that is used when any new instance is created. Although in the end, this code works the same as the previous listing, this way is much better because the code resides in just one place.

You can use this same basic technique to extend the Application object. It's a bit touchy because you have to use an uppercase A. And, actually, it's not like it's any better than writing a traditional function in the main.asc file. In any event, Listing 9.15 provides a practical example of a formatted date. Because I might need to call this function from several places in my app, I want it in a function.

Listing 9.15. Extending the Application Object
 Application.prototype.getTD=function(){ var now= new Date(); theString=1+now.getMonth() +"/"+now.getDate() +"/"+now.getFullYear() +" "+now.getHours() +":"+ (now.getMinutes()<10 ? "0"+now.getMinutes() : now.getMinutes()) +":"+ (now.getSeconds()<10 ? "0"+now.getSeconds() : now.getSeconds()); return (theString); } //then call it using: answer=application.getTD(); 

This code is quite useful. It's actually used it in an upcoming example. Basically, it's a function stuffed inside the prototype for the Application (with a big A) object. Remember to include "application." every time you call it.

Using Messaging Sparingly

I think the concept of messaging is much easier to grasp than RSOs. However, RSOs tend to be more efficient because they only broadcast variables that change. (Naturally, you also should structure your RSOs to have lots of small properties instead of fewer large propertiesas Chapter 8 explained.) The problem with messaging is that you're really never sure when a message is received. Actually, with call() you should use the optional result object toif nothing elseconfirm a message was received. With RSOs, each user may not be 100 percent in sync every second, but there will always be just one value for any variable in the RSO.

I suppose this section might be better titled "think about it" because my main point is that after you get your rough design planned or an initial prototype built, it's a good idea to take a pause to analyze things. Decide whether you're sending messages unnecessarily. Make sure your RSOs are parsing through the data received in their onSync events so that just the parts that changed update. In traditional Flash apps, less than ideal ActionScript code will have little or no impact on performance. (Usually the impact involves graphic- related issues such as moving a semitransparent clip over text, for example.) However, any time you send data over the Internet, be extra careful that the data is small and that you're not sending it any more frequently than you have to. In addition, there's an extra burden to do error checks that follow-up when failures occur. It's a pretty safe bet that if a client remains connected, the client will get notified when an RSO changes.

[ LiB ]  


Macromedia Flash MX 2004 for Rich Internet Applications
Macromedia Flash MX 2004 for Rich Internet Applications
ISBN: 0735713669
EAN: 2147483647
Year: 2002
Pages: 120

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