More Books
Flash Communication Server
Flash Communication Server
Table of Contents
Copyright
About the Authors
Brian Lesser
Giacomo
Joey Lott
Robert Reinhardt
Justin Watkins
Foreword
Preface
What Does FlashCom Offer?
What's in This Book?
How to Use This Book
Audience
ActionScript 1.0 Versus ActionScript 2.0
Server-Side ActionScript
The flash-communications.net Site
Director, Breeze, and Other Options
Flash Video Options
Licensing and Hosting Options
Conventions Used in This Book
Voice
Using Code Examples
Safari Enabled
Comments and Questions
Acknowledgments
Part I:  FlashCom Foundation
Chapter 1.  Introducing the Flash Communication Server
Section 1.1.  Clients and Servers
Section 1.2.  Creating an Application
Section 1.3.  Real-Time Messaging Protocol
Section 1.4.  The Communication Classes
Section 1.5.  Communicating with Application Servers, Databases, and Directory Servers
Section 1.6.  Firewalls and Security
Section 1.7.  Getting Started
Section 1.8.  Hello Video!
Section 1.9.  Conclusion
Chapter 2.  Communication Components
Section 2.1.  Overview of Communication Components
Section 2.2.  Summary of Communication Components
Section 2.3.  Creating an Application that Monitorsa Connection
Section 2.4.  Building a Simple Chat Room
Section 2.5.  Adding Audio and Video to the Chat Room
Section 2.6.  Forgoing the SimpleConnect Component
Section 2.7.  Conclusion
Chapter 3.  Managing Connections
Section 3.1.  Making a Connection
Section 3.2.  Managing a Connection
Section 3.3.  Reusing a NetConnection Object
Section 3.4.  Multiple Simultaneous NetConnection Objects
Section 3.5.  Testing and Debugging Network Connections
Section 3.6.  Subclassing the NetConnection Class
Section 3.7.  Communication Components Without SimpleConnect
Section 3.8.  Conclusion
Chapter 4.  Applications, Instances, and Server-Side ActionScript
Section 4.1.  Scripting Application Instances
Section 4.2.  Differences Between Flash ActionScript and Server-Side ActionScript
Section 4.3.  The Life of an Application Instance
Section 4.4.  Running a Simple Hello World Test Script
Section 4.5.  A More Realistic Example
Section 4.6.  Instance-to-Instance Communications
Section 4.7.  Script Filenames and Locations in Detail
Section 4.8.  Testing and Debugging Server-SideScript Files
Section 4.9.  Designing Communication Applications
Section 4.10.  Conclusion
Part II:  Audio, Video, and Data Streams
Chapter 5.  Managing Streams
Section 5.1.  A Simple Publisher/Subscriber Example
Section 5.2.  Stream Names
Section 5.3.  Publishing Streams in Detail
Section 5.4.  Playing Streams in Detail
Section 5.5.  The Stream Class
Section 5.6.  Publishing and Playing ActionScript Data
Section 5.7.  Creating Synchronized Presentations
Section 5.8.  The NetStream and Stream Information Objects
Section 5.9.  Stream Enhancements and Limitations
Section 5.10.  Conclusion
Chapter 6.  Microphone and Camera
Section 6.1.  Working with Microphone/Audio Input
Section 6.2.  Working with Camera Input
Section 6.3.  Building a Message-Taking Application
Section 6.4.  Building a Surveillance Application
Section 6.5.  Conclusion
Chapter 7.  Media Preparation and Delivery
Section 7.1.  Audio and Video Compression
Section 7.2.  Converting Prerecorded Materialto FLV Format
Section 7.3.  Using Flash Pro's Media Components
Section 7.4.  Enabling Multiple Bit Rate FLVsWithin an Application
Section 7.5.  Streaming MP3 Audio
Section 7.6.  Conclusion
Part III:  Remote Connectivity and Communication
Chapter 8.  Shared Objects
Section 8.1.  Objects and Shared Objects
Section 8.2.  Getting a Shared Object in Flash
Section 8.3.  Updates and Frame Rates
Section 8.4.  Scripting Shared Objects on the Server
Section 8.5.  Temporary and Persistent Shared Objects
Section 8.6.  Proxied Shared Objects
Section 8.7.  Shared Objects and Custom Classes
Section 8.8.  Avoiding Collisions
Section 8.9.  Optimizing Shared Object Performance
Section 8.10.  Broadcasting Remote Method Callswith send( )
Section 8.11.  A Simple Video and Text Chat Application
Section 8.12.  Conclusion
Chapter 9.  Remote Methods
Section 9.1.  Why Use Calls?
Section 9.2.  The send( ) and call( ) Methods
Section 9.3.  Client-to-Server Calls
Section 9.4.  Server-to-Client Calls
Section 9.5.  Server-to-Server Calls
Section 9.6.  A Simple Lobby/Rooms Application
Section 9.7.  Debugging Calls
Section 9.8.  Advanced Topics
Section 9.9.  Conclusion
Chapter 10.  Server Management API
Section 10.1.  Connecting to the Admin Service
Section 10.2.  Using the Server Management API
Section 10.3.  Server Management API Uses
Section 10.4.  Conclusion
Chapter 11.  Flash Remoting
Section 11.1.  The Remoting Gateway
Section 11.2.  Remoting Basics
Section 11.3.  Role of Remoting in FlashCom Applications
Section 11.4.  Securing Access
Section 11.5.  Conclusion
Chapter 12.  ColdFusion MX and FlashCom
Section 12.1.  Understanding ColdFusion MXand Flash Remoting
Section 12.2.  Using Flash Remoting to Log Events
Section 12.3.  Getting a List of Streams
Section 12.4.  Using ColdFusion and FTP to Mirror Streams
Section 12.5.  Conclusion
Part IV:  Design and Deployment
Chapter 13.  Building Communication Components
Section 13.1.  Source Files
Section 13.2.  People Lists
Section 13.3.  A Simple People List
Section 13.4.  Listenable Shared Objects
Section 13.5.  Status and People List
Section 13.6.  Text Chat
Section 13.7.  Shared Text
Section 13.8.  Video Conference and Video Window
Section 13.9.  PeopleGrid
Section 13.10.  Summary
Section 13.11.  Conclusion
Chapter 14.  Understanding the Macromedia Component Framework
Section 14.1.  The Component Framework
Section 14.2.  Under the Hood of the Chat Component
Section 14.3.  Creating a Simple Component from Scratch: SharedTextInput
Section 14.4.  Creating a Container Component: SharedAddressForm
Section 14.5.  Creating an Authenticating Component
Section 14.6.  Integrating Components with Your Existing Applications
Section 14.7.  Understanding the Framework
Section 14.8.  Conclusion
Chapter 15.  Application Design Patterns and Best Practices
Section 15.1.  Shared Object Management
Section 15.2.  Moving Code to the Server
Section 15.3.  Building Façades on the Server
Section 15.4.  Server-Side Client Queues
Section 15.5.  A Framework for Recording and Playing Back Componentized Applications
Section 15.6.  Components and Component Frameworks
Section 15.7.  Conclusion
Chapter 16.  Building Scalable Applications
Section 16.1.  Coordinating Instances
Section 16.2.  Scalability and Load Balancing
Section 16.3.  Conclusion
Chapter 17.  Network Performance, Latency,and Concurrency
Section 17.1.  Latency
Section 17.2.  Bandwidth
Section 17.3.  Concurrency
Section 17.4.  Conclusion
Chapter 18.  Securing Applications
Section 18.1.  The Three A's: Authentication, Authorization, and Accounting
Section 18.2.  Authentication
Section 18.3.  Authorization
Section 18.4.  Accounting
Section 18.5.  Suggestions and References
Section 18.6.  Conclusion
Index
SYMBOL
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
R
S
T
U
V
W

4.5. A More Realistic Example

Writing and testing a simple main.asc script for helloWorld is an important first step in becoming familiar with any new environment. However, while the helloWorld application introduces the application, Client, and info objects, it's not very useful. Although not a full-featured implementation, Example 4-3 is a bit more realistic main.asc script in that it shows many of the methods already discussed working in a more typical manner. The code in Example 4-3 demonstrates:

  • Accepting or rejecting client connections based on a username and password

  • Adding properties and methods to the Client object more simply, by adding an object property instead of individual properties and methods

  • Limiting guest clients but not other types of clients when a maximum number of users is reached

  • Using the server-side setInterval( ) function to periodically update information about connected clients

The code listed in Example 4-3 is not commented to save space, but the version on the web site is. Take a moment to read through the code. It is described immediately after the listing.

Example 4-3. A more realistic main.asc
users = {  Brian: {password: "secretPassword1", role: "author"},
          Robert: {password: "secretPassword2", role: "author"},
          Justin: {password: "secretPassword3", role: "author"},
            Joey: {password: "secretPassword4", role: "author"},
           Peldi: {password: "secretPassword5", role: "author"},
           Guest: {password: "Guest",           role: "guest"}
};
access = {author: {readAccess: "/", writeAccess: "/"},
           guest: {readAccess: "/public", writeAccess: ""}
};
MAXCONNECTIONS = 5;

function User (client, userName, role) {
  this.client = client;
  this.userName = userName;
  this.role = role;
  this.connectTime = new Date( ).getTime( );
}

User.prototype.getTimeConnected = function ( ) {
  var now = new Date( ).getTime( );
  return (now - this.connectTime)/1000; // seconds
};

User.prototype.getPingTime = function ( ) {
  var client = this.client;
  if (client.ping( )) {
    return client.getStats( ).ping_rtt / 2;
  }
  else{
    return "Client is not connected."
  }
};

User.prototype.getConnectionInfo = function ( ) {
  return "  IP: "              + this.client.ip +
         ", user name: "       + this.userName +
         ", connection time: " + this.getTimeConnected( ) +
         ", ping time: "       + this.getPingTime( );
};

function listCurrentUsers ( ) {
  trace("-------------- Current Users --------------");
  var i, user;
  var total = application.clients.length;
  trace("There are " + total + " current users.");
  for (i = 0; i < total; i++) {
    user = application.clients[i].user;
    trace(user.getConnectionInfo( ));
  }
  trace("-------------------------------------------");
}

application.onAppStart = function ( ) {
  trace(application.name + " is starting at " + new Date( ));
  setInterval(listCurrentUsers, 60000);
};

application.onConnect = function (client, userName, password) {
  trace("Connection attempt from IP: " + client.ip + " userName: " + userName);
  var user = users[userName];
  if (!user || !password || user.password != password) {
    application.rejectConnection(client, {msg: "Invalid user name or password."});
    return;
  }
  if (application.clients.length >= MAXCONNECTIONS && user.role == "guest") {
    application.rejectConnection(client, {msg: "Chat is already full."});
    return;
  }
  client.user = new User(client, userName, user.role);
  client.writeAccess = access[user.role].writeAccess;
  client.readAccess  = access[user.role].readAccess;
  application.acceptConnection(client);
  trace("Connection accepted at " + new Date( ));
};

application.onDisconnect = function (client) {
  trace(client.user.userName + " is disconnecting at: " + new Date( ));
  trace(client.user.userName + " was connected for " +
  client.user.getTimeConnected( ) + " seconds.");
};

application.onAppStop = function ( ) {
  trace(application.name + " is stopping at " + new Date( ));
};

The following sections examine the major functionality in the code example.

4.5.1. Authenticating and Customizing Clients

Clients that connect to an application instance can be authenticated in a number of ways. Chapter 11 provides an example of authenticating users using Flash Remoting and ColdFusion; Chapter 18 describes a number of other approaches and their strengths and weaknesses.

Example 4-3 uses a simplistic approach of providing a hardcoded users object, an associative array of objects. The objects it contains can be accessed using property names like Brian, Robert, and Justin. The objects contain password and role properties for each user. For example users.Brian contains an object with Brian's password and role. Accessing Brian's password in the users object could be done using dot notation or using the square bracket operator. In Example 4-3s onConnect( ) method, an object is retrieved from the users object this way:

var user = users[userName];

If userName contains a string that matches a property name in the users object, the user variable is assigned an object reference, and it will have a password and role property. For example, if userName is "Brian", user.password will be secretPassword1. If userName does not contain a property name that exists in users, the value of user will be undefined. All of this is taken into account in this if statement:

if (!user || !password || user.password != password) {
  application.rejectConnection(client, {msg: "Invalid user name or password."});
  return;
}

Each part of the condition is necessary to prevent errors and validate the user. For example, if no user were found, the user variable does not contain an object reference and will evaluate to false in the Boolean expression. In that case, the client connection will be rejected. If the user passed in an empty or undefined password string, in the Server-Side ActionScript, password will evaluate to false and again the client will be rejected. Assuming user is an object and password is a non-empty string, the user.password property can be safely accessed and compared to the password the client provided.

Be sure that a variable contains an object before attempting to access any of its properties. Otherwise, a type error will occur on the server, and execution of the script will stop until the instance's script is allocated another thread by FlashCom when an event handler or other callback function must be called. See "try/catch/finally Statements" earlier in this chapter for another way to avoid errors halting script execution.


If the user is authenticated, the client object can be customized to store information about the user, control access to resources, and be extended with additional methods. A good way to package related information and methods together is to add an object as a new property of the client object:

client.user = new User(client, userName, user.role);

The User constructor can also be used to initialize the client further without cluttering the onConnect( ) method too much. In Example 4-3, the User constructor also gets the connection time and stores it in the user object along with userName, role, and a copy of the client reference.

Read and write access to shared objects and streams is controlled by looking up the readAccess and writeAccess properties of objects in the access object:

client.writeAccess = access[user.role].writeAccess;
client.readAccess  = access[user.role].readAccess;

For example, if the user.role property is "guest", the previous statements will have this effect:

client.writeAccess = "";
client.readAccess  = "/public";

The empty string means that the client cannot write to or create any shared objects or publish any streams to this instance. However, the client can read data and play streams in the public directory or any of its subdirectories. If the user.role were "author", the statements would have this effect:

client.writeAccess = "/";
client.readAccess  = "/";

The "/" string indicates that the client has access to any resource.

4.5.1.1 Using the Client.prototype object

In Example 4-3, every client object is given a user object property before its connection is accepted. That way, every client will contain common information about each user such as his username and will contain common methods such as user.getConnectionInfo( ). In some application designs, where a common object is not added to each client, the most efficient way to add common methods to every object is to attach them to Client.prototype. Example 4-4 shows part of a script that uses the prototype object to make available a method for all client objects.

Example 4-4. Using the Client.prototype object to extend all clients
Client.prototype.getTimeConnected = function (  ) {
  var now = new Date( ).getTime( );
  return (now - this.connectTime)/1000; // seconds
};

application.onConnect = function (client, userName, password) {
  client.connectTime = new Date( ).getTime( );
  application.acceptConnection(client);
};

application.onDisconnect = function (client) {
  trace("Client connected for: " + client.getTimeConnected( ));
};

The Client class cannot be subclassed because the Client constructor is never called directly in a script. Whenever it is necessary to significantly extend the Client class, you should use composition. Adding the user object to each client and then delegating work, such as getting the client's connection information, to the user object is one example of composition. Other objects can also be added in order to take on other responsibilities on behalf of the client. For detailed discussions on attaching methods to a class's prototype, see ActionScript for Flash MX: The Definitive Guide. For thorough discussions of composition versus inheritance, see Essential ActionScript 2.0.

4.5.2. Limiting the Number of Client Connections

The number of currently accepted client connections is always available via the length property of the application.clients array. Restricting the number of clients that can connect at any one time is a simple matter of checking the length property and rejecting clients when it reaches a certain size. In some applications, it may be necessary to allow unlimited connections from users of one type and restrict connections of another type. In Example 4-3, guest connections are not accepted when the number of accepted clients reaches a certain point:

if (application.clients.length >= MAXCONNECTIONS && user.role == "guest") {
  application.rejectConnection(client, {msg: "Chat is already full."});
  return;
}

This example shows how to reject a connection to prevent it from being established. You can use application.disconnect( ) at any time to disconnect a client that has already been accepted.

4.5.3. Performing Periodic Updates with setInterval( )

You'll often want to perform some action periodically. On the client side, this may be achieved in several ways, such as using timelines or enterFrame events. However, neither of these is available on the server. In server-side code, you can use setInterval( ) to call a function or method after a certain amount of time. The function or method can be called once or many times at regular intervals. In the onAppStart( ) method in Example 4-3, setInterval( ), is called and passed a reference to the listCurrentUsers( ) function:

setInterval(listCurrentUsers, 60000);

In this example, listCurrentUsers( ) is called once every minute (60,000 milliseconds). In turn, listCurrentUsers( ) iterates over the application.clients array and calls each client's user.getConnectionInfo( ) method. This method uses the properties of the client object, its user object, and User class methods to output text about the client. See Macromedia's Server-Side Communication ActionScript Dictionary (available in PDF format from the URL cited earlier and also in LiveDocs format from http://livedocs.macromedia.com/flashcom/mx2004) for a listing of all the properties and methods of the Client class.

Various Client properties can be output to illustrate their values. The ping( ) and getStats( ) methods of the Client class do more than provide static information about the client. Calling ping( ) immediately sends a test message that makes the round-trip from the server to the Flash client and back again. The message may take some time to return, so the ping( ) method returns TRue if the previous ping message was returned by the client (or the server believes the client is still connected) and false if the client was not connected:

if (client.ping(  )) {
  return client.getStats(  ).ping_rtt / 2;
}
else {
  return "Client is not connected."
}

If ping( ) returns true, calling the getStats( ) method returns an information object whose ping_rtt property contains the round-trip time in milliseconds from the last available ping( ) call. The ping( ) method should be used with caution because it sends each ping message at the highest available prioritypotentially delaying other RTMP messages. When ping( ) is used to determine network latency, it should not be called too frequently. As a guideline, Macromedia's ConnectionLight component's default ping interval is 2 seconds.