Requirements
Prerequisite knowledge

General experience of building applications
and web content with Flash Builder, the
Flex SDK, or Flash Professional is
Required products

Sample files

User level

Intermediate
 
This article illustrates how to use sockets to communicate between an AIR 2 application and ActionScript code running in Flash Player. The example includes two programs: an Adobe AIR application and a Flash Player application. The Adobe AIR application acts as the server and listens for Transport Control Protocol (TCP) connections on a socket. This application uses the ServerSocket class added in AIR 2. (If you aren't already familiar with the TCP protocol, don't worry. You don't really need to know much about TCP to use sockets.) The client Flash Player application runs in a browser and connects to the AIR server application. The programming tasks this article explores include:
 
  • Creating a socket server
  • Establishing a socket connection
  • Reading and writing data over a socket connection
  • Serving a socket policy file from an AIR application
  • Using the ActionScript Message Format (AMF) to send objects over a socket
  • Implementing IExternalizable for custom serialization

 
Application overview

This article illustrates socket communication between AIR and Flash Player. The article discusses two applications, an AIR 2 application that acts as a server and a Flash Player application that acts as the client.
 
 
Server project files
The AIR application acts as the socket server and uses AIR 2 features. You must use a development tool that has the AIR SDK 2 installed. Such tools include Flash Builder, Flash Professional CS5, and the command line tools in a Flex SDK that has been updated with the AIR 2 SDK.
 
The server application uses the following files:
 
  • ServerApplication.mxml: The main application file. Code in this file creates the server object and sets up the AIR window to display the messages sent by the client .
  • server/Server.as: Serves a single client connection.
  • server/ SocketService.as: Defines a secondary socket server to serve socket policy files requested by Flash Player.
  • ServerApplication-app.xml: The application descriptor file.
  • FlashProfessional/ServerApplication.fla: The Flash project file (used for Flash Professional CS5 only).
To use these files in Flash Builder, create a new desktop project named ServerApplication. Make sure that the main application file is named ServerApplication.mxml. Copy the sample files to the new project. In the Project Properties, check that the Flex SDK version used for the project has been updated to include the AIR 2 SDK. Add the SerializableObject.swc to the project build path.
 
To use these files in Flash Professional CS5, copy them to a suitable location on your hard drive. Open the SocketApplication.fla file. Add the SerializableObject.swc to the library path.
 
 
Client project files
The client runs in Flash Player, either standalone or in the browser. The project includes the following files:
 
  • ClientApplication.mxml: Defines the user interface for the client application.
  • SocketConnection.as: Defines the client socket code used to communicate with the server.
  • FlashProfessional/ClientApplication.fla: The Flash project file (used for Flash Professional CS5 only).
 
SerializableObject project files
The SerializableObject project defines a code library used by both the client and server applications.
 
  • SerializableObject.as: Defines the object that is passed between the client and the server over the socket. Demonstrates the IExternalizable interface.
  • bin/SerializableObject.swc: The code library file.
 
Running the example applications
To test the applications, create and build the projects in Flash Builder or Flash Professional. You can launch the applications in either order.
The client application has controls for sending objects and strings of various sizes. You can play with these to observe what happens as the message size increases. The server application simply logs the data receives and echoes it back to the client in string format.
 
Note: When you launch the client from Flash Builder or use the Test Movie command in Flash Professional, Flash Player does not request a socket policy file. To test the policy file, you can launch the client HTML page directly from the file system, the Flash Professional Preview HTML command, or a web server.
 

 
The Server: Listening for a connection

TCP sockets provide a persistent connection between two processes. These processes can be programs running on the same computer or they can be programs running on two Internet-connected computers half a world apart. Once the connection is established, the same socket can be used to exchange data until one side closes the connection (or the network fails).
 
When you use sockets in ActionScript, most of the work is performed by the TCP implementation of the operating system. When you initiate a socket connection from a client application, the TCP system sends a message to the server computer at the specified address and port. If there is a suitable process listening on the destination port, a TCP socket connection is established.
 
In the ActionScript socket API, the job of the ServerSocket object is to listen for the incoming TCP connections. Only a few simple steps are needed to create a socket server:
 
  1. Create a ServerSocket object
  2. Bind the object to a local IP address and port on the local computer
  3. Call the ServerSocket listen() method
When a client process connects to the address and port specified in the bind() method, the ServerSocket object dispatches a connect event. The ServerSocketConnectEvent object dispatched for this event contains a new, regular Socket object. The server conducts all communication with the client through this Socket object. The ServerSocket object continues to listen for new connections and will dispatch a new event containing a new Socket object for each connection.
 
In your AIR application, the code for the server is defined in the Server class. Because both the server and the client processes run on the same computer, the Server object binds the socket to the localhost address, 127.0.0.1. An arbitrary port number, 8087, is chosen—if this port is already used by another process, you will have to choose another and update both the server and the client code.
 
The following code is used to create the ServerSocket object to listen for connections:
 
try { // Create the server socket serverSocket = new ServerSocket(); // Add the event listener serverSocket.addEventListener( Event.CONNECT, connectHandler ); serverSocket.addEventListener( Event.CLOSE, onClose ); // Bind to local port 8087 serverSocket.bind( 8087, "127.0.0.1" ); // Listen for connections serverSocket.listen(); log( "Listening on " + serverSocket.localPort ); } catch(e:Error) { log(e); }
When a client connects, the ServerSocket object dispatches a connect event. In response, the server application creates a SocketService object that handles all communication with the client. The server doesn't stop listening, so you could connect with multiple clients. A SocketService object is created for each client.
 
The following function responds to the connect event:
 
public function connectHandler(event:ServerSocketConnectEvent):void { //The socket is provided by the event object var socketServicer:SocketService = new SocketService( event.socket, log ); socketServicer.addEventListener( Event.CLOSE, onClientClose ); clientSockets.push( socketServicer ); //maintain a reference to prevent premature garbage collection }

 
The Client: Making the connection

Creating the client side of a socket connection is simple. You create a Socket object and call the connect() method, passing in the IP address and port of the server. In this case, your server is listening on the address 127.0.0.1 and port 8087 (since that's what you used in the bind() method of the ServerSocket object).
 
The following code is used to create and set up the client socket:
 
//Create socket and attach event listeners socket = new Socket(); socket.addEventListener( Event.CONNECT, connectionMade ); socket.addEventListener( Event.CLOSE, connectionClosed ); socket.addEventListener( IOErrorEvent.IO_ERROR, socketFailure ); socket.addEventListener( SecurityErrorEvent.SECURITY_ERROR, securityError ); socket.addEventListener( ProgressEvent.SOCKET_DATA, dataReceived );
Instead of calling the Socket connect() method directly, the client application uses a timer. By using a timer, the client keeps trying to connect if the server is not available right away. The handler for the timer event calls the connect() event. When the connection is successful, the timer is stopped.
 
The following code is used by the timer event handler function to create the socket connection:
 
private function connectToServer( event:TimerEvent ):void { try { //Try to connect socket.connect( "127.0.0.1", 8087 ); } catch( e:Error ) { log( e ); } }
When the client successfully connects to the server, the Socket dispatches a connect event. The handler for this event sends the string "BEGIN" to the server, which indicates, in this application, that the client is going to start sending data:
 
private function connectionMade( event:Event ):void { socket.writeUTFBytes( "BEGIN" ); socket.flush(); //Stop retry timer retryTimer.removeEventListener( TimerEvent.TIMER, connectToServer ); retryTimer.stop(); }

 
Communicating over the socket

Once the connection is established, both the server application and the client application communicate using the same Socket API. It really doesn't matter whether it is the server or the client that is sending the data, the code for reading, writing, and sending the data is the same.
 
You can read the data available on a socket using the IDataInput methods of the Socket class, such as readUTFBytes(). For example, if the server expects the message to be a single string, it can use the following statement to read it:
 
var utfMessage:String = socket.readUTFBytes( socket.bytesAvailable );
This works for fairly short messages (up to a few kilobytes in length). However, for longer messages that require more than one socket data event to arrive, or messages containing different types of data, you have to know what to expect before you read. In general, there is no easy way for the recipient to just "know" how much and what kind of data is in a particular message. The sender and the receiver applications must agree on a common "protocol" for exchanging data.
 
 
Sending data
In this example, the agreement between the client and the server is that the client first writes two integer values to the socket before writing the message proper. The first integer declares the length of the message. The second integer is a code used to declare the data type as either "object" or "string." When the receiver gets a new message, it first reads the size header with the readInt() method to determine if the entire message has been received. If so, it uses the type code to determine how to read the rest of the message. (Your own scheme can, of course, be far more complex.)
 
The SocketConnection object, which handles the socket connection for the client application uses the following code to send an object:
 
public function sendObject( objectSize:Number ):void { var objectMessage:SerializableObject = new SerializableObject( objectSize , "AMF Encoded" ); var bytes:ByteArray = new ByteArray(); bytes.writeObject( objectMessage ); bytes.position = 0; try{ //Write the headers socket.writeUnsignedInt( bytes.length + 4 ); //message length socket.writeInt( TYPE_AMF ); //message type //Serialize the object to the socket socket.writeBytes( bytes ); //Make sure it is sent socket.flush(); } catch ( e:Error ) { log ( e.toString() ); } bytes.position = 0; log ( "Sending: " + objectMessage.toString() ); }
Instead of writing the object directly to the socket, the object is first serialized to a byte array. This allows you to write the byte array length property to the socket as the message size.
 
You should always call the socket flush() method when you are finished writing a full message. On some operating systems, flush() is called automatically between execution frames, but on others, you must call flush() or the data is never sent. Because of this difference in behavior, it is good practice to call flush() in every case. Also, since flush() is asynchronous, closing the socket immediately after calling flush() can prevent all the written data from being sent. Unfortunately, there's no event to tell you when the socket has been completely flushed. You can use a timer to close the socket if the issue affects your application.
 
 
Reading data
To read simply formatted messages, such as those sent from the server to the client in this example, you can often just read whatever data is available. For example, the following statement reads the entire socket buffer as a string:
 
var message:String = socket.readUTFBytes( socket.bytesAvailable );
However, when the messages are more complex and contains different types of data, you must be more careful. In this example, the server application reads the data from the client using a process described in the following pseudo code:
 
While socket.bytesAvailable >= length of message size header if (new message) read message size if (message size <= socket.bytesAvailable) read message type code read message (based on type code) else exit loop and wait for next socketData event
As long as there are at least 4 bytes available to read (the size of the integer declaring the message length), you do the following: if you haven't already started reading a message (by reading the length header), then you read an integer's worth of data as the message size. If this size is less than or equal to the data available from the socket, then you read the message. Otherwise, you exit from the loop and wait for the next socketData event. A while loop is used, because more than one message could be available in the socket.
 
The process is implemented as the following socketData event handler function:
 
public function socketDataHandler(event:ProgressEvent):void { //Read the data from the socket try { while( socket.bytesAvailable >= 4 )//while there is at least enough data to read the message size header { if( messageLength == 0 ) //is this the start of a new message block? { messageLength = socket.readUnsignedInt(); //read the message length header } if( messageLength <= socket.bytesAvailable ) //is there a full message in the socket? { var typeFlag:int = socket.readInt(); //read the message type header //Read the message based on the type if( typeFlag == TYPE_AMF ) //AMF object { var dataMessage:SerializableObject = socket.readObject() as SerializableObject; log( socket.remoteAddress + ":" + socket.remotePort + " sent " + dataMessage.toString() ); reply( "Echo: " + dataMessage.toString() ); } else if ( typeFlag == TYPE_STRING ) //UTF string { var utfMessage:String = socket.readUTF(); log( socket.remoteAddress + ":" + socket.remotePort + " sent " + utfMessage ); reply( "Echo: " + utfMessage ); } messageLength = 0; //finished reading this message } else { //The current message isn't complete -- wait for the socketData event and try again reply( "Partial message: " + socket.bytesAvailable + " of " + messageLength); break; } } } catch ( e:Error ) { log( e ); } }

 
Socket policy files: Granting permission to connect

In most circumstances, Flash Player requests a socket policy file from the server before it allows code to create a socket connection. The socket policy file describes who can connect to a server and which ports they can access. When the connect() method of a Socket object is called, the Flash Player runtime first opens a separate XMLSocket connection to request a socket policy document from the server.  The runtime sends the string, "<policy-file-request/>" over the socket and expects to get back an XML string terminated with a null byte.
 
Note: When you test an application in Flash Builder or Flash Professional, the application runs in a trusted context that does not require a socket policy file. To test the policy file server, you can launch the client application using a web server, directly from the file system, or by using the Preview HTML command in Flash Professional CS5.
 
Your server is no exception to the socket policy. You need to listen for a policy file request and respond with an XML policy document or the client won't be able to connect. This requires our server socket to have two modes: one for handling policy files and the other for handling regular requests. In the first mode, the server socket expects strings that it can read with the readUTFBytes() method. The socket looks for one of two strings. If the string is "<policy-file-request/>", then it responds with a policy file document permitting access. If the string is “BEGIN”, then the socket changes to the second mode. This handshake logic is implemented with the following function:
 
private function handshakeHandler( event:ProgressEvent ):void { var socket:Socket = event.target as Socket; //Read the message from the socket var message:String = socket.readUTFBytes( socket.bytesAvailable ); logCallback( "Received: " + message); if( message == "" ) { var policy:String = '\x00'; socket.writeUTFBytes( policy ); socket.flush(); socket.close(); logCallback("Sending policy: " + policy); } else if ( message == "BEGIN" ) { socket.removeEventListener( ProgressEvent.SOCKET_DATA, handshakeHandler ); socket.addEventListener( ProgressEvent.SOCKET_DATA, socketDataHandler ); socket.writeUTFBytes( "READY" ); socket.flush(); } }
The policy file used in this example is:
 
<cross-domain-policy> <allow-access-from domain="*" to-ports="8087" /> </cross-domain-policy>\x00
This policy allows a client from any domain to connect to the server at port 8087. In a real application, you should consider whether only allowing access to Flash Player content served from your own domain is a warranted security precaution. Specify a specific domain by replacing the “*” character in the domain attribute in the policy document with the name of the domain serving the SWF that should have access to the server.
 
You can serve the policy document from a different port than that used in the call to connect(). To do this, create a second ServerSocket instance listening on your designated policy file port. Additionally, the client must tell Flash Player where to load the document from. To inform the player of this location, you use the Security class, loadPolicyFile() method before calling connect():
 
Security.loadPolicyFile("xmlsocket://127.0.0.1:8083");
Using a second policy file server can simplify the primary server logic a bit.
 

 
Sending objects: Using AMF and IExternalizable

Although it is easy to send strings between two sockets using the writeUTFBytes() and readUTFBytes() functions, it is often convenient to pass objects back and forth over a socket. The writeObject() and readObject() methods defined by the Socket object serialize objects into a format (AMF) that can be sent over the network (or written to disk, for that matter).
 
The default AMF encoder only preserves public properties of an object. To preserve other properties, you have to write your own custom serialization routines. Also by default, the type information of an object is lost. The remote side of the socket only receives a generic Object. To preserve the class of an object, you must call the registerClassAlias() function in both the sender and the receiver of the object. This function maps a class name string to a class definition. The function call used in both the client and server applications is:
 
registerClassAlias( "SerializableObject", SerializableObject );
The registerClassAlias() method can be called anytime before objects of the class are serialized or deserialized. It must be called in both the client and the server. In Flex, you can use the [RemoteClass] metadata tag in the class file to do the same thing.
 
 
Customizing object serialization
Not all objects can be easily serialized. For example, serializing a Sprite using AMF will not work. For such objects, you can implement the IExternalizable interface.
 
When you use IExternalizable, the runtime calls your serialization methods instead of  the default AMF ones. In this example, the implementation of IExternalizable reads and writes the properties of the SerializableObject class . Because the writeExternal() and readExternal() methods are members of the class they are serializing, they can access private and protected members in addition to implementing custom serialization logic :
 
public function writeExternal( output:IDataOutput ):void { output.writeUTF( ID ); output.writeFloat( number ); output.writeUTF( string ); output.writeInt( padding.length ); //write byte array size output.writeBytes( padding ); } public function readExternal( input:IDataInput ):void { ID = input.readUTF(); number = input.readFloat(); string = input.readUTF(); var temp:int = input.readInt(); //read byte array size input.readBytes( padding, 0, temp ); }
As you can see, the order in which properties are read must match the order in which they are written. Note that the output and input parameters passed to these functions directly reference the Socket or ByteArray object on which you called writeObject(). This makes it easy to make mistakes such as reading data that does not belong to the object. In the above example, the length of the serialized byte array that is part of the object had to be written by the writeExternal() function so that the readExternal() code would know how many bytes to put into the byte array on deserialization. (You can look at the AMF specification for reasonable solutions to other challenges of serialization.)
 
When an object is deserialized, first a new object is created and then the readExternal() method is called. Because the original parameters passed to the constructor function are not available when an object is deserialized, only classes that have no required constructor parameters can be serialized and deserialized (whether or not you use a custom IExternalizable implementation).
 

 
Where to go from here

This article covered the basics of creating both the server and the client side of a socket connection. I showed you how to serve socket policy files and touched on using AMF and IExternalizable to transfer objects.
 
You can find more information on these topics in the following resources: