Last active
July 19, 2016 14:23
-
-
Save darrenfu/9a6202a73986207fa15e6c8ba9c16de0 to your computer and use it in GitHub Desktop.
To resolve packet fragmentation and reassembly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package { | |
import flash.errors.IOError; | |
import flash.events.ErrorEvent; | |
import flash.events.Event; | |
import flash.events.IOErrorEvent; | |
import flash.events.ProgressEvent; | |
import flash.events.SecurityErrorEvent; | |
import flash.net.Socket; | |
import flash.system.Security; | |
import flash.utils.ByteArray; | |
/** | |
* Handles the actual connection with the game room server. | |
*/ | |
public class SocketConnection extends Object { | |
public static const SIZE_BYTES_SHORT:int = 2; | |
public static const SIZE_NULL_MARKER:int = 4; | |
private static const DEFAULT_SOCKET_TIMEOUT:int = 10000; | |
protected var mDone:Boolean = true; | |
private var mAttemptReconnect:Boolean = true; | |
private var mReconnectCount:int = 0; | |
private const MAX_RECONNECTS:int = 3; | |
private var mSocket:Socket; | |
private var mBufferData:ByteArray = new ByteArray(); | |
/** | |
* Connects to the server. | |
*/ | |
private function restartConnection():void { | |
mDone = false; | |
try { | |
if (mPolicyFile != null) Security.loadPolicyFile(mPolicyFile); | |
} | |
catch (e:Error) { | |
Log.info(".startConnection: error "+e.message, this, Log.WARN_TEXT_COLOR); | |
} | |
mSocket = new Socket(); | |
mSocket.timeout = DEFAULT_SOCKET_TIMEOUT; | |
mSocket.addEventListener(Event.CONNECT, notifyConnected); | |
mSocket.addEventListener(Event.CLOSE, notifyDisconnected); | |
mSocket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); | |
mSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); | |
mSocket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler); | |
try { | |
mSocket.connect(mAddr, mPort); | |
} | |
catch (e:SecurityError) { | |
Log.fatal(".startConnection: "+e.message, this); | |
reconnect(new SecurityErrorEvent(SecurityErrorEvent.SECURITY_ERROR, false, false, e.message)); | |
} | |
} | |
/** | |
* Sends a message to the room server. | |
* | |
* @param p properties object containing message | |
*/ | |
public function sendToServer(p:Properties):void { | |
if (mSocket.connected) { | |
// convert props to byte array | |
var buffer:ByteArray = new ByteArray(); | |
for each (var key:String in p.getKeys()) { | |
buffer.writeUTF(key); | |
buffer.writeUTF(p.getProperty(key)); | |
} | |
var obfus:ByteArray = obfuscate(buffer); | |
var msg:ByteArray = new ByteArray(); | |
msg.writeShort(obfus.length); | |
msg.writeBytes(obfus); | |
try { | |
mSocket.writeBytes(msg); | |
mSocket.flush(); | |
} | |
catch (e:IOError) { | |
Log.info(".sendToServer: error sending message: "+e, this, Log.WARN_TEXT_COLOR); | |
} | |
} | |
else { | |
Log.info(".sendToServer: error sending message: socket not connected", this, Log.WARN_TEXT_COLOR); | |
} | |
} | |
/** | |
* Reconnects to the server. | |
*/ | |
private function reconnect(evt:Event):void { | |
Log.info("connection closed... ", this, Log.WARN_TEXT_COLOR); | |
if (! mDone && mAttemptReconnect && mReconnectCount++ < MAX_RECONNECTS) { | |
Log.info("reconnecting", this, Log.WARN_TEXT_COLOR); | |
restartConnection(); | |
} | |
else { | |
Log.info("exiting", this, Log.WARN_TEXT_COLOR); | |
notifyDisconnectListeners(evt); | |
} | |
} | |
/** | |
* Obfuscates a properties object to be sent to the room server. | |
* | |
* @param p properties object | |
* @return byte array of obfuscated data | |
*/ | |
private function obfuscate(byteArray:ByteArray):ByteArray { | |
var obByteArray:ByteArray = new ByteArray(); | |
for (var i:int = 0; i < byteArray.length; ++i) { | |
obByteArray[i] = byteArray[i] ^ 0xFF; | |
} | |
return obByteArray; | |
} | |
/** | |
* Handles raw data from the socket. | |
* | |
* @param evt The associated event. | |
*/ | |
private function socketDataHandler(event:ProgressEvent):void { | |
// read the data | |
mSocket.readBytes(mBufferData, mBufferData.length ); | |
// read and dispatch messages | |
while (mBufferData.bytesAvailable > SIZE_BYTES_SHORT) { | |
var msgLen:int = mBufferData.readShort(); | |
if (mBufferData.bytesAvailable >= msgLen) { | |
readMessage( msgLen ); | |
// when closing this can be a problem | |
if (mBufferData == null) { | |
return; | |
} | |
if (mBufferData.bytesAvailable == 0) { | |
// ended at the end of message; reset the buffer | |
mBufferData.position = 0; | |
mBufferData.length = 0; | |
break; | |
} | |
} else { | |
// can't read a full message so move pos back and break until we get more data | |
mBufferData.position -= 2; | |
break; | |
} | |
} | |
} | |
/** | |
* Reads a message from the buffer and sends it to the mesage dispatcher. | |
* | |
* @param msgLen The message length. | |
*/ | |
private function readMessage(msgLen:int):void { | |
// de-obfusacate | |
var startPos:int = mBufferData.position; | |
for (var i:int = startPos; i < startPos + msgLen; ++i) { | |
mBufferData[i] = mBufferData[i] ^ 0xFF; | |
} | |
// build dictionary | |
var msg:Properties = new Properties(); | |
while (mBufferData.position < startPos + msgLen) { | |
var key:String = mBufferData.readUTF(); | |
var val:String = mBufferData.readUTF(); | |
msg.setProperty(key,val); | |
} | |
// dispatch | |
if (mDebug) { | |
logIn("<- "+msg.toString()); | |
} | |
// reset the reconnect count when we get a good message | |
mReconnectCount = 0; | |
if (mMessageHandler != null) mMessageHandler.handleMessage(msg); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment