views:

407

answers:

2

Hello,

I have multiplayer game, which reads XML data from the server:

 _socket = new Socket();
 _socket.addEventListener(ProgressEvent.SOCKET_DATA, handleTcpData);
.....
private function handleTcpData(event:Event):void {
  while (_socket.bytesAvailable) {
     var str:String = _socket.readUTF();
     updateGUI(str);
  }
}

In most cases it works ok, but sometimes users complain that the game hangs for them. They are able to send in commands to the server, but from their descriptions I suspect, that the function above doesn't work well for them and the updateGUI() stops being called.

This issue is difficult to debug... I think my server (in Perl/C, non-forking, uses poll()) works well, I must be doing something wrong at the Flash side.

Is it a good idea to call readUTF() here? It could well be that only part of the UTF string arrives and then the Flash movie would freeze in readUTF(), wouldn't it? (I've never seen that though)

Regards Alex

UPDATE:

Yes thanks, I need to catch EOFError, also I've got a good advise in a mailing list that I must read _socket.bytesAvailable bytes into a ByteArray and work with that (check if my complete message arrived) - anything else is unreliable.

So I've come up with this, but still have a flaw there:

private function handleTcpData(event:Event):void {
       var len:uint;

       if (0 == _socket.bytesAvailable)
               return;

       try {
               _socket.readBytes(_ba, _ba.bytesAvailable, _socket.bytesAvailable);

               // read the length of the UTF string to follow
               while (_ba.bytesAvailable >= 2) {
                       len = _ba.readUnsignedShort();

                       // invalid length - reset
                       if (0 == len) {
                               trace('XXX invalid len = ' + len);
                               _ba.position = 0;
                               _ba.length = 0;
                               return;
                       }

                       if (_ba.bytesAvailable >= len) {
                               var str:String = _ba.readUTFBytes(len);
                               updateGUI(str);

                               // copy the remaining bytes
                               // (does it work? can it be improved?)
                               var newBA:ByteArray = new ByteArray();
                               newBA.readBytes(_ba);
                               _ba = newBA;
                       }
               }
       } catch(e:Error) {
               trace('XXX ' + e);
               _ba.position = 0;
               _ba.length = 0;
               return;
       }
}
  • when there are too many bytes available, for example 800 and my messages are only 400 bytes long, then it stucks.

Also I wonder, if copying remaining bytes could be improved (i.e. without allocating a new ByteArray each time)?

I can reproduce this flaw often, luckily

A: 

All socket.read* methods may throw EOFError and IOError, so make sure you are catching them. It may be something as simple as that?

Another thought, the readUTF() call assumes that the first two bytes (a short) should contain the length in bytes of the UTF-string. If your perl server doesn't send that length, it may be that the readUTF() call interprets the first UTF character as the byte length, and thus ends up waiting for more data which never arrives.

Try using:

  var str:String = _socket.readUTFBytes(_socket.bytesAvailable);

Other than that, you really need to be able to reproduce the issue or it's quite impossible to know if you have fixed it.

Martin Wickman
Yes thanks, my Perl-server sends that prefix length, this isn't a problem.
Alexander Farber
A: 

I think, I've found the solution. Due to TCP-nature there can be 2 problematic cases:

1) several UTF-strings with prefixes arrive at once 2) an incomplete UTF-string with prefix arrives

This code should handle both:

private function handleTcpData(event:Event):void {
// CASE 1, JUST KEEP EXTRACTING UTF-STRINGS
 while(_socket.bytesAvailable) {
 try{
   var str:String = _socket.readUTF();
   updateGUI(str);
  }catch(e:Error){
    // CASE 2 INCOMPLETE STRING,
    // WILL BE READ BY LATER handleTcpData CALL
    return;
  }
 }
}

Also, in my original code I was copying the ByteArray wrong way. I should have:

var newBA:ByteArray = new ByteArray();
_ba.readBytes(newBA);
_ba = newBA;

or even do it in-place without creating newBA.

Alexander Farber