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.
