Our favorite number is 1 (or 0)

Creating Socket Servers with AIR 2


07.09.10 Posted in Blog by

Recently I had the opportunity to utilize the new ServerSocket class in Adobe Air 2.0. It was a relatively straight forward process but being less familiar with data packets than my teammates, there was a veiled mystery about how it worked. This new feature opens up a lot of possibilities for communicating with external entities as you might with ExternalInterface or SharedObjects. ServerSockets, however, allow you to do so across the wire. That’s an oversimplification to be sure but this article will demonstrate the basics of establishing and communicating with such a connection.

Flash has had socket communication skills for a while now but you always needed a web server acting as the hub. With ServerSocket, AIR 2 applications now have the ability to serve as a point of contact. The role of the ServerSocket instance is trivial yet effective. Essentially it binds and listens to a specified port on a given address for incoming connections. Upon a connection request, a ServerSocketConnectEvent.CONNECT event is broadcast. The event object contains a reference to a Socket instance which can be used for communicating with the client making the request. Each connection event object contains a separate Socket instance allowing you to message the clients separately.

Requirements

You will need to be able to compile your own .air application files. Overlaying the AIR 2 SDK in FlashBuilder is probably the easiest way to do this.

First up is our server app. Below is a simple MXML Application file which allows you start and stop the service as well as output incoming and outgoing messages. The client will have similar functionality.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx" layout="absolute" width="520" height="600" backgroundColor="#E6E6E6">
	<fx:Script>
		<![CDATA[
			private var _serverSocket:ServerSocket;
			private var _clientSocket:Socket;
 
			// all output will pass through this function
			private function log(textArea:TextArea, message:String):void{
				textArea.text += message + "\n";
			}
 
			/* our toggle button change handler will toggle the label of the button
			 * as well as stop and start the service
			 */
			private function onServerToggleChange(event:Event):void{
				// record the button state
				var selected:Boolean = Button(event.target).selected;
				// set the label text
				serviceToggleBtn.label = selected ? "Stop Server" : "Start Server";
 
				// create & bind or destroy our ServerSocket instance
				if(selected){
					_serverSocket = new ServerSocket();
					// listen for connection requests
					_serverSocket.addEventListener(ServerSocketConnectEvent.CONNECT, onServerSocketConnect);
					// bind to a Port and Address
					_serverSocket.bind(31337, "0.0.0.0"); // ports lower than 1024 require security privledges. 0.0.0.0 is the default address
					// finally, turn on the service by listening to the bound port and address
					_serverSocket.listen();
					log(outgoing_ta, "Server Started");
					address_lbl.text = "0.0.0.0";
					port_lbl.text = "31337";
				}else{
					if(_clientSocket != null && _clientSocket.connected){
						_clientSocket.close();
					}
					// turn off the service and stop listening by closing the socket
					_serverSocket.close();
					// remove the listener
					_serverSocket.removeEventListener(ServerSocketConnectEvent.CONNECT, onServerSocketConnect);
					// nullify the instance
					_serverSocket = null;
					log(outgoing_ta, "Server Stopped");
					address_lbl.text = "";
					port_lbl.text = "";
				}
			}
 
			// handle connection requests
			private function onServerSocketConnect(event:ServerSocketConnectEvent):void{
				// we are only expecting one client connection at a time in this demo so we must clean the reference
				if(_clientSocket != null){
					//_clientSocket.removeEventListener(DataEvent.DATA, onClientSocketData);
					_clientSocket.removeEventListener(ProgressEvent.SOCKET_DATA, onClientSocketData);
				}
				_clientSocket = event.socket;
				//_clientSocket.addEventListener(DataEvent.DATA, onClientSocketData, false, 0, true);
				_clientSocket.addEventListener(ProgressEvent.SOCKET_DATA, onClientSocketData, false, 0, true);
				log(incoming_ta, "Client Connected: " + Socket(event.socket).remotePort);
			}
 
			// handle incoming data from the client
			private function onClientSocketData(event:ProgressEvent):void{
				// data comes in as a ByteArray so we use Socket.readBytes() to store them in a ByteArray object
				var buffer:ByteArray = new ByteArray();
				_clientSocket.readBytes(buffer, 0, _clientSocket.bytesAvailable);
				log(incoming_ta, "Received: " + buffer.toString());
			}
 
			private function onSendMessageClick(event:MouseEvent):void{
				if(_clientSocket.connected){
					_clientSocket.writeUTFBytes("This is a message from the server.");
					_clientSocket.flush();
					log(outgoing_ta, "Sending a message to the client");
				}
			}
		]]>
	</fx:Script>
	<mx:Label x="10" y="10" text="Simple Server Demo" fontWeight="bold" fontFamily="Arial" fontSize="18"/>
	<mx:ApplicationControlBar x="10" y="40" width="500" fillAlphas="[1.0, 0.47]">
		<mx:Button label="Start Server" id="serviceToggleBtn" toggle="true" change="onServerToggleChange(event)"/>
		<mx:Button label="Send Message" click="onSendMessageClick(event)"/>
		<mx:Label text="address:" fontWeight="bold"/>
		<mx:Label id="address_lbl"/>
		<mx:Label text="port:" fontWeight="bold"/>
		<mx:Label id="port_lbl"/>
	</mx:ApplicationControlBar>
	<mx:Panel x="10" y="81" width="500" height="474" layout="absolute" title="Activity Monitor">
		<mx:Label x="10" y="10" text="Incoming"/>
		<mx:TextArea id="incoming_ta" y="36" width="460" height="160" x="10"/>
		<mx:Label x="10" y="209" text="Outgoing"/>
		<mx:TextArea id="outgoing_ta" y="235" width="460" height="160" x="10"/>
	</mx:Panel>
</mx:WindowedApplication>

And here is the client app. It connects to the same address and port the server is bound to and will output any communication between the two.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
						xmlns:mx="library://ns.adobe.com/flex/mx" layout="absolute"
						creationComplete="onCreationComplete(event)" width="520" height="600">
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
			private var _socket:Socket = new Socket();
 
			// all output will pass through this function
			private function log(textArea:TextArea, message:String):void{
				textArea.text += message + "\n";
			}
 
			// immediately set up listeners for our Socket instance
			private function onCreationComplete(event:FlexEvent):void{
				_socket.addEventListener(Event.CLOSE, onSocketClose);
				_socket.addEventListener(Event.CONNECT, onSocketConnect);
				_socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
				_socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			}
 
			// handle successful connections with the server
			private function onSocketConnect(event:Event):void{
				log(outgoing_ta, "Connection: " + Socket(event.target).remotePort);
			}
 
			// handle incoming messages from the server
			private function onSocketData(event:ProgressEvent):void{
				var buffer:ByteArray = new ByteArray();
				_socket.readBytes(buffer, 0, _socket.bytesAvailable);
				log(incoming_ta, "Received: " + buffer.toString());
			}
 
			// handle the socket connection being closed by the server
			private function onSocketClose(event:Event):void{
				log(incoming_ta, "The server has closed the connection");
			}
 
			//handle an error in connecting to the server
			private function onIOError(event:IOErrorEvent):void{
				log(outgoing_ta, "Connection Failure, make sure the server is running");
			}
 
			private function onConnectClick(event:MouseEvent):void{
				if(!_socket.connected){
					_socket.connect(addressinput.text, int(portInput.text));
				}
			}
 
			private function onSendRequestClick(event:MouseEvent):void{
				_socket.writeUTFBytes("This is a message from the client.");
				_socket.flush();
				log(outgoing_ta, "Sending message to " + _socket.remotePort);
			}			
		]]>
	</fx:Script>
 
	<mx:Label x="10" y="10" text="Simple Client Demo" fontWeight="bold" fontFamily="Arial" fontSize="18"/>
	<mx:ApplicationControlBar x="10" y="40" width="500" fillAlphas="[1.0, 0.47]">
		<mx:Label text="address:" fontWeight="bold"/>
		<mx:TextInput width="95" text="0.0.0.0" id="addressinput"/>
		<mx:Label text="port:" fontWeight="bold"/>
		<mx:TextInput id="portInput" text="31337" width="46"/>
		<mx:Button label="Connect To Server" click="onConnectClick(event)"/>
		<mx:Button label="Send Data" click="onSendRequestClick(event)"/>
	</mx:ApplicationControlBar>
	<mx:Panel x="10" y="81" width="500" height="474" layout="absolute" title="Activity Monitor">
		<mx:Label x="10" y="10" text="Incoming"/>
		<mx:TextArea id="incoming_ta" y="36" width="460" height="160" x="10"/>
		<mx:Label x="10" y="209" text="Outgoing"/>
		<mx:TextArea id="outgoing_ta" y="235" width="460" height="160" x="10"/>
	</mx:Panel>
</mx:WindowedApplication>

Once the server has started, the client can connect to it. After the connection has been made, either party can send or receive messages. This wont exactly replace the need for server side languages but there are plenty of creative ways to utilize ServerSocket, for instance, communicating between your desktop and an Android device.



Leave a Reply