Socket Improvements in AIR 3

In AIR 3 (currently in beta, available on Adobe Labs), we added a frequently requested feature to the Socket class: an output progress event. The Socket class has always dispatched a ProgressEvent which is designed to let you know when data is ready to read from the socket, however there was no event indicating how much data had been written from the socket’s write buffer to the network. In most cases, at any given moment, it doesn’t really matter how much data has been passed to the network and how much is left in the write buffer since all of the data eventually gets written before the socket is closed (which usually happens very quickly), however that’s not always the case. For example, if your application is writing a large amount of data and the user decides to exit, you might want to check to see if there is still data in the write buffer which hasn’t been transferred to the network yet. Or you might want to know when data has finished being transferred from the write buffer to the network so you can open a new socket connection, or perhaps de-reference your socket instance in order to make it eligible for garbage collection. Or you might just want to show an upload progress bar indicating how much data has been written to the network, and how much data is still pending.

All of these scenarios are now possible in AIR 3 with the OutputProgressEvent. An OutputProgressEvent is thrown whenever data is written from the write buffer to the network. In the event handler, developers can check to see how much data is still left in the buffer waiting to be written by checking the bytesPending property. Once the bytesPending property returns 0, you know all the data has been transferred from the write buffer to the network, and it is consequently safe to do things like remove event handlers, null out your socket reference, shut down your application, start the next upload in a queue, etc.

The code below (also available on Github, and as an FXP file) is a simple example of safeguarding your application from being closed while data is still in the write buffer. The application opens a socket connection to the specified server, writes the data from the input textarea, and then writes the response in the output textarea. It’s coded in such a way that if the user tries to close the application before all the data has been written from the write buffer to the network, it will stop the application from closing, then automatically close it later once it can verify that all the data has successfully been written. (Note that this isn’t a very realistic example since the data being written to the socket is just text, and will probably be limited enough that it will all be written from the socket to the network layer in a single operation, however you can easily imagine a scenario where megabytes of data are being written which could take several seconds or even minutes, depending on the quality of the client’s network connection.)

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" showStatusBar="false" creationComplete="onCreationComplete();">

  <fx:Script>
    <![CDATA[

      private var socket:Socket;
      private var readBuffer:ByteArray;
      private var socketOperationInProgress:Boolean;
      private var closeLater:Boolean;

      private function onCreationComplete():void
      {
        this.nativeWindow.addEventListener(Event.CLOSING, onClosing);
      }
      
      private function onClosing(e:Event):void
      {
        if (this.socketOperationInProgress)
        {
          this.closeLater = true;
          e.preventDefault();
        }
      }

      private function sendData():void
      {
        this.socketOperationInProgress = true;
        this.readBuffer = new ByteArray();
        this.socket = new Socket();
        this.socket.addEventListener(Event.CONNECT, onConnect);
        this.socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
        this.socket.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, onOutputProgress);
        this.socket.connect(this.server.text, Number(this.port.text));
      }
      
      private function onConnect(e:Event):void
      {
        this.socket.writeUTFBytes(this.input.text);
      }

      private function onSocketData(e:ProgressEvent):void
      {
        this.socket.readBytes(this.readBuffer, 0, socket.bytesAvailable);
        this.output.text += this.readBuffer.toString();
      }
      
      private function onOutputProgress(e:OutputProgressEvent):void
      {
        if (e.bytesPending == 0)
        {
          this.socketOperationInProgress = false;
          if (this.closeLater)
          {
            this.nativeApplication.exit();
          }
        }
      }
    ]]>
  </fx:Script>

  <s:VGroup width="100%" height="100%" verticalAlign="middle" horizontalAlign="center" paddingBottom="10" paddingTop="10" paddingLeft="10" paddingRight="10">
    <s:HGroup width="100%">
      <s:TextInput id="server" prompt="Host Address" width="80%"/>
      <s:TextInput id="port" prompt="Port" width="20%"/>
    </s:HGroup>
    <s:TextArea id="input" prompt="Input" width="100%" height="50%"/>
    <s:Button width="100%" label="Open Socket and Send Data" click="sendData();"/>
    <s:TextArea id="output" prompt="Output" width="100%" height="50%"/>
  </s:VGroup>
</s:WindowedApplication>

Keep in mind that AIR 3 is still in beta, so you might find bugs. If you do, here’s how to file them.

10 Responses to Socket Improvements in AIR 3

  1. maliboo says:

    What about with output progress for large URLRequests?

  2. Pingback: Cool Stuff with the Flash Platform - 8/30/2011 | Remote Synthesis

  3. Mika says:

    Hi, can you tell me whether this is supposed to be actually implemented in 3.0 RC1? I added a OutputProgressEvent.OUTPUT_PROGRESS listener to my socket, but it doesn’t fire when data is written. I can only trigger the handler by manually calling dispatchEvent on the socket, but then bytesPending is always 0 in the handler even with data pending.

    Another observation is that there’s no good way to let the handler know whether you’re really done with the socket. bytesPending might be zero if the write queue is empty, but that does necessarily not imply that you’re not going to send more bytes later. You’re using a global to work around this in your example, but with multiple open sockets that’s of course not an option.

    On a sidenote: any plans of finally fixing socket.close() on Windows so that it waits for all the data to be written before closing the socket, like it does on OS X?

  4. dd says:

    Sigh…….I don’t suppose Adobe managed to build HTTP 1.1 keepalives persistent connections into Adobe Air, or would that be too much to ask after four years of asking and waiting for what should be a basic feature?

  5. Pingback: Slides, Links, and Questions From my MAX 2011 Presentation « Christian Cantrell

  6. Tristian says:

    Now that AIR 3 is out, I still can’t get any event trigger off OutputProgressEvent.OUTPUT_PROGRESS. Is it working for anyone else?

  7. Peter Wood says:

    I’m getting the same as pina34colada and Tristian
    “OutputProgressEvent.OUTPUT_PROGRESS is always triggered once with 0 bytes pending”
    on my windows 7 machine it works great on mac on air and in FP 11.1 just not in windows.

  8. Pingback: Flash11/AIR3.0新特性 | augustli's blog

  9. Christian says:

    OutputProgressEvent.OUTPUT_PROGRESS works for me on windows 8 release preview build 8400