5Mixer / mphx

A little library to let you make multiplayer games easily with Haxe. No longer maintained and better options are available.
MIT License
125 stars 16 forks source link

Does not compile with the flash target #16

Closed yannsucc closed 8 years ago

yannsucc commented 8 years ago

Hi ! on TcpClient.hx, when haxe targeting flash, they don't compile on line 124

#if flash
import flash.net.Socket;
#else
import sys.net.Host;
import sys.net.Socket;
#end
[...]

function readSocket(socket:Socket)
    {
        try
        {
            dataReceived(socket.input);
        }catch(e:haxe.io.Eof){
            loseConnection("Lost connection to server");
        }
    }

the socket.input does'nt exist with flash.net.Socket

5Mixer commented 8 years ago

Eek, to be honest, I actually hadn't tried the flash target at all. I believe that #if !flash was actually in the original HxNet code.

Flash as a client target seems pretty important, and Node Js on the server does too. I'll put it on my list of things to fix. Thanks for posting.

On 9 Mar 2016, at 10:30 pm, yannsucc notifications@github.com wrote:

Hi ! on TcpClient.hx, when haxe targeting flash, they don't compile on line 124

if flash

import flash.net.Socket;

else

import sys.net.Host; import sys.net.Socket;

end

[...]

function readSocket(socket:Socket) { try { dataReceived(socket.input); }catch(e:haxe.io.Eof){ loseConnection("Lost connection to server"); } } the socket.input does'nt exist with flash.net.Socket

— Reply to this email directly or view it on GitHub.

yannsucc commented 8 years ago

i temporary fix it with :

    function readSocket(socket:Socket)
    {
        try
        {
            #if flash
            var buf = new StringBuf();
            var last : Int;
            var line : String = "";
            try 
            {
                while((last = socket.readByte()) != 10)
                    buf.addChar(last);

                line = buf.toString();

                if (line.charCodeAt(line.length - 1) == 13)
                    line = line.substr(0, -1);

            } catch ( e : EOFError ) 
            {
                line = buf.toString();
                if(line.length == 0)
                    throw (new Eof());
            }
            recieve(line);

            #else
            dataReceived(socket.input);
            #end

        }catch (e:haxe.io.Eof) {

            #if !flash
            loseConnection("Lost connection to server");
            #end
        }
    }

I'm not sure for the Eof check. But if i remove the #if !flash, when socket contains no bytes, => close connection :(

it's a ugly fix.

Another mistake into : mphx.tcp.NetSock.hx see this commit on hxnet : https://github.com/sh-dave/hxnet/commit/06622eb068f4f7aa6e2b539dd190ca1f2fb6d086

it missing socket.flush(); I temporary fix it too

    public function writeBytes(bytes:Bytes):Bool
    {
        try
        {
#if flash
            // if (writeLength) socket.writeInt(bytes.length);
            for (i in 0...bytes.length)
            {
                socket.writeByte(bytes.get(i));
            }

            socket.flush();
#else

so now it working ! ^^ but with ugly fix.

Another mistake, we can't manage flash Error like ioError using addEventListener(Event.ioError) see : http://help.adobe.com/fr_FR/FlashPlatform/reference/actionscript/3/flash/net/Socket.html

So if server don't response, we see this error : [Fault] exception, information=Error: Error #2002: Operation attempted on invalid socket.

Maybe the better way to manage flash.net.socket is to implements a new Class ? like : class TcpFlashClient implements IClient

yannsucc commented 8 years ago

First, sorry for my english,

here a solution (tested and working, but need some improvement i think) (sorry, i can't made any pull request now but if you need it, i will take time to made it)

I made a TcpFlashClient who implements IClient for specific use of the flash.net.Socket :

#if flash
package client;
import flash.errors.EOFError;
import flash.events.SecurityErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.Event;

import flash.net.Socket;
import haxe.io.Bytes;
import mphx.client.EventManager;
import mphx.client.IClient;
import mphx.serialization.HaxeSerializer;
import mphx.serialization.ISerializer;
import mphx.tcp.NetSock;

/**
 * yannsucc
 * @author 
 */
class TcpFlashClient implements IClient
{

    public var serializer:ISerializer;
    public var events:EventManager;
    private var buffer:Bytes;

    public var cnx:NetSock;
    private var m_client:Socket;

    private var m_readSockets:Array<Socket>; // utility ?

    // all handler for different case (ConnectError, Connect, Server Close, Connection lost for any reason)
    public var onConnectionError : Void->Void;
    public var onConnectionEstablished : Void->Void;
    public var onServerClose : Void->Void;
    public var onConnectionLost : Void->Void;

    private var m_host:String;
    private var m_port:Int; 

    public function new(host:String, port :Int) 
    {
        serializer = new HaxeSerializer();
        events = new EventManager();
        buffer = Bytes.alloc(8192); //no utility ?
        m_host = host;
        m_port = port;
    }

    public function isConnected():Bool 
    { 
        return cnx != null && cnx.isOpen(); 
    }

    public function connect():Void
    {
        m_client = new Socket(m_host, m_port);

        //add specific handler for connection 
        m_client.addEventListener(Event.CONNECT, onFlashConnectEvent);
        m_client.addEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEventConnect);
        m_client.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEventConnect); 
    }

    private function onFlashConnectEvent(event : Event) : Void
    {
        trace("Connection etablished on : " + m_host +":" + m_port);

        //remove specific handler for connection 
        m_client.removeEventListener(Event.CONNECT, onFlashConnectEvent);
        m_client.removeEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEventConnect);
        m_client.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEventConnect);  

        //add generic Flash event Handler
        m_client.addEventListener(Event.CLOSE, onFlashServerClose);
        m_client.addEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEvent);
        m_client.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEvent);
        //maybe add : m_client.addEventListener(ProgressEvent.SOCKET_DATA, *insertCallbackHere* ); but actually, done by update()

        // prevent recreation of array on every update
        m_readSockets = [m_client];
        cnx = new NetSock(m_client);

        if (onConnectionEstablished != null)
            onConnectionEstablished();
    }

    private function onFlashIoErrorEventConnect(event : IOErrorEvent) : Void
    {
        trace("Connection failed on : " + m_host +":" + m_port + " error : " + event.toString());

        //remove specific handler for connection 
        m_client.removeEventListener(Event.CONNECT, onFlashConnectEvent);
        m_client.removeEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEventConnect);
        m_client.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEventConnect);  

        if (onConnectionError != null)
            onConnectionError();
    }

    private function onFlashSecurityErrorEventConnect(event : SecurityErrorEvent) : Void
    {
        trace("Connection failed on : " + m_host +":" + m_port + " error : " + event.toString());
        if (onConnectionError != null)
            onConnectionError();
    }

    private function onFlashIoErrorEvent(event : IOErrorEvent) : Void
    {
        loseConnection(event.toString());
    }

    private function onFlashSecurityErrorEvent(event : SecurityErrorEvent) : Void
    {
        loseConnection(event.toString());
    }

    public function send(event:String, ?data:Dynamic):Void
    {
        if (!isConnected())
        {
            trace("No connection, ABORT send(" + event + ")");
            return;
        }

        var object = 
        {
            t: event,
            data:data
        };

        var serialiseObject =  serializer.serialize(object);
        var result = cnx.writeBytes(Bytes.ofString(serialiseObject + "\r\n"));
    }

    private function onFlashServerClose(event : Event) : Void
    {
        trace("server close connection : " + event.toString());
        this.close();

        if (onServerClose != null)
            onServerClose();
    }

    private function loseConnection(reason:String = "") : Void
    {
        trace("Client disconnected with code : " + reason);
        this.close();

        if (onConnectionLost != null)
            onConnectionLost();
    }   

    public function close():Void
    {

        if (cnx != null)
        {
            //this object contains a reference of the m_client socket and try to close it.
            //this object must only remove the reference (client = null) and don't try to close the socket.
            // only this Class (Tcpclient) must do it.
            //so i put an ugly trick 
            try
            {
                this.cnx.close(); 
                this.cnx = null;
                m_client = null;
            } catch (e : Dynamic)
            {
                trace("Warning, can't close correctly NetSock object : " + e);
            }
        }

        this.cnx = null;

        //ugly :(
        try
        {
            if (m_client != null) //only if the NetSock object (cnx) faile close the socket
            {
                m_client.close();
                m_client = null;
            }
        }catch (e:Dynamic)
        {   
            trace("Warning, can't close socket : " + e);
        }       

    }

    public function update(timeout:Float = 0):Void
    {
        if (!isConnected()) 
            return; 
        readSocket(m_client);
    }

    private function readSocket(client)
    {
        var buf = new StringBuf();
        var last : Int;
        var line : String = "";

        //try to get RT/LF line
        try 
        {
            while((last = m_client.readByte()) != 10)
                buf.addChar(last);

            line = buf.toString();

            if (line.charCodeAt(line.length - 1) == 13)
                line = line.substr(0, -1);

            recieve(line);

        } catch ( e : EOFError ) 
        {
            //nothing special ?
            //trace("no data on socket");
            //line = buf.toString();
            //if(line.length == 0)
                //throw (new Eof());
        } catch (e : Dynamic)
        {
            trace("unknow error : " + e);
        }

    }

    public function recieve(line:String) : Void
    {
        //Then convert the string to a Dynamic object.
        var msg = serializer.deserialize(line);
        //The message will have a propety of T
        //This is the event name/type. It is t to reduce wasted banwidth.
        //call an event called 't' with the msg data.
        events.callEvent(msg.t,msg.data);
    }       
}
#end

CARE ! I fix localy a mistake into NetSock.hx, on writesBytes function, you need to add socket.flush(), else the server don't receive correctly the data send by the client.

An other point into NetSock Class into close() function. The socket parameters is a reference here, it's not a good idea to call socket.close here (only make a socket = null) socket.close(), must be call into a class who create it (like TcpClient).

Thx for this librairie :)

5Mixer commented 8 years ago

Wow, thanks very much, this is very helpful and will give me a great head start with supporting flash. I'll take a better look tonight and attempt to intergrate it into the code base. Thanks for your help.

On 11 Mar 2016, at 3:47 am, yannsucc notifications@github.com wrote:

First, sorry for my english,

here a solution (tested and working, but need some improvement i think) (sorry, i can't made any pull request now but if you need it, i will take time to made it)

I made a TcpFlashClient who implements IClient for specific use of the flash.net.Socket :

if flash

package client; import flash.errors.EOFError; import flash.events.SecurityErrorEvent; import flash.events.IOErrorEvent; import flash.events.Event;

import flash.net.Socket; import haxe.io.Bytes; import mphx.client.EventManager; import mphx.client.IClient; import mphx.serialization.HaxeSerializer; import mphx.serialization.ISerializer; import mphx.tcp.NetSock;

/**

  • yannsucc
  • @author */ class TcpFlashClient implements IClient {

    public var serializer:ISerializer; public var events:EventManager; private var buffer:Bytes;

    public var cnx:NetSock; private var m_client:Socket;

    private var m_readSockets:Array; // utility ?

    // all handler for different case (ConnectError, Connect, Server Close, Connection lost for any reason) public var onConnectionError : Void->Void; public var onConnectionEstablished : Void->Void; public var onServerClose : Void->Void; public var onConnectionLost : Void->Void;

    private var m_host:String; private var m_port:Int;

    public function new(host:String, port :Int) { serializer = new HaxeSerializer(); events = new EventManager(); buffer = Bytes.alloc(8192); //no utility ? m_host = host; m_port = port; }

    public function isConnected():Bool { return cnx != null && cnx.isOpen(); }

    public function connect():Void { m_client = new Socket(m_host, m_port);

    //add specific handler for connection 
    m_client.addEventListener(Event.CONNECT, onFlashConnectEvent);
    m_client.addEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEventConnect);
    m_client.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEventConnect); 

    }

    private function onFlashConnectEvent(event : Event) : Void { trace("Connection etablished on : " + m_host +":" + m_port);

    //remove specific handler for connection 
    m_client.removeEventListener(Event.CONNECT, onFlashConnectEvent);
    m_client.removeEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEventConnect);
    m_client.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEventConnect);  
    
    //add generic Flash event Handler
    m_client.addEventListener(Event.CLOSE, onFlashServerClose);
    m_client.addEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEvent);
    m_client.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEvent);
    //maybe add : m_client.addEventListener(ProgressEvent.SOCKET_DATA, *insertCallbackHere* ); but actually, done by update()
    
    // prevent recreation of array on every update
    m_readSockets = [m_client];
    cnx = new NetSock(m_client);
    
    if (onConnectionEstablished != null)
      onConnectionEstablished();

    }

    private function onFlashIoErrorEventConnect(event : IOErrorEvent) : Void { trace("Connection failed on : " + m_host +":" + m_port + " error : " + event.toString());

    //remove specific handler for connection 
    m_client.removeEventListener(Event.CONNECT, onFlashConnectEvent);
    m_client.removeEventListener(IOErrorEvent.IO_ERROR, onFlashIoErrorEventConnect);
    m_client.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onFlashSecurityErrorEventConnect);  
    
    if (onConnectionError != null)
      onConnectionError();

    }

    private function onFlashSecurityErrorEventConnect(event : SecurityErrorEvent) : Void { trace("Connection failed on : " + m_host +":" + m_port + " error : " + event.toString()); if (onConnectionError != null) onConnectionError(); }

    private function onFlashIoErrorEvent(event : IOErrorEvent) : Void { loseConnection(event.toString()); }

    private function onFlashSecurityErrorEvent(event : SecurityErrorEvent) : Void { loseConnection(event.toString()); }

    public function send(event:String, ?data:Dynamic):Void { if (!isConnected()) { trace("No connection, ABORT send(" + event + ")"); return; }

    var object = 
    {
      t: event,
      data:data
    };
    
    var serialiseObject =  serializer.serialize(object);
    var result = cnx.writeBytes(Bytes.ofString(serialiseObject + "\r\n"));

    }

    private function onFlashServerClose(event : Event) : Void { trace("server close connection : " + event.toString()); this.close();

    if (onServerClose != null)
      onServerClose();

    }

    private function loseConnection(reason:String = "") : Void { trace("Client disconnected with code : " + reason); this.close();

    if (onConnectionLost != null)
      onConnectionLost();

    }

    public function close():Void {

    if (cnx != null)
    {
      //this object contains a reference of the m_client socket and try to close it.
      //this object must only remove the reference (client = null) and don't try to close the socket.
      // only this Class (Tcpclient) must do it.
      //so i put an ugly trick 
      try
      {
          this.cnx.close(); 
          this.cnx = null;
          m_client = null;
      } catch (e : Dynamic)
      {
          trace("Warning, can't close correctly NetSock object : " + e);
      }
    }
    
    this.cnx = null;
    
    //ugly :(
    try
    {
      if (m_client != null) //only if the NetSock object (cnx) faile close the socket
      {
          m_client.close();
          m_client = null;
      }
    }catch (e:Dynamic)
    {   
      trace("Warning, can't close socket : " + e);
    }       

    }

    public function update(timeout:Float = 0):Void { if (!isConnected()) return; readSocket(m_client); }

    private function readSocket(client) { var buf = new StringBuf(); var last : Int; var line : String = "";

    //try to get RT/LF line
    try 
    {
      while((last = m_client.readByte()) != 10)
          buf.addChar(last);
    
      line = buf.toString();
    
      if (line.charCodeAt(line.length - 1) == 13)
          line = line.substr(0, -1);
    
      recieve(line);
    
    } catch ( e : EOFError ) 
    {
      //nothing special ?
      //trace("no data on socket");
      //line = buf.toString();
      //if(line.length == 0)
          //throw (new Eof());
    } catch (e : Dynamic)
    {
      trace("unknow error : " + e);
    }

    }

    public function recieve(line:String) : Void { //Then convert the string to a Dynamic object. var msg = serializer.deserialize(line); //The message will have a propety of T //This is the event name/type. It is t to reduce wasted banwidth. //call an event called 't' with the msg data. events.callEvent(msg.t,msg.data); }
    }

    end

    CARE ! I fix localy a mistake into NetSock.hx, on writesBytes function, tou need to add socket.flush(), else the server don't receive correctly the data send by the client.

An other point into NetSock Class into close() function. The socket parameters is a reference here, it's not a good idea to call socket.close here (only make a socket = null) socket.close(), must be call into a class who create it (like TcpClient).

Thx for this librairie :)

— Reply to this email directly or view it on GitHub.

5Mixer commented 8 years ago

Hey, I finally got this running. It seems flash is really sensitive when it comes to socket security. I got around this temporarily by whitelisting the app as a developer app in flash settings, and that stopped secuirity error 2020.

Also, I looked at the logs, it looks like there is an error where data tries to be sent by the connection has not yet been established. This was an issue with websockets too, all I did was had a ready flag, and until that ready flag was true, all sent data is put into a cue. When the connection is established, it sends all cued data and marks ready as true.

I might try doing that tommorow, and reading a little more on security things.

5Mixer commented 8 years ago

Quick update: A few tiny tweaks later, and the client and server are connecting and functioning with a flash client. https://i.gyazo.com/ae4bc71318705fd9a786d39d7512e015.gif However, at the moment flash is only recieving, not sending. I'm not yet sure why.

Also, I did a little reading into flash secuirity, it seems as though a file has to be served on the server telling flash that it is fine with connecitons. There is a little bit more info over at: http://old.haxe.org/doc/flash/security

5Mixer commented 8 years ago

Alright, latest commit fully works, atleast with the movement haxeflixel example. It's a tiny bit slower than other targets, but the whole library isn't performance crazy yet.

Thank you so much for dropping the code here, having flash as an option for client multiplayer is awesome. I just had a HTML5, neko and flash windows open, all talking to each other.

Security is still a potential issue, so perhaps I will deal with that in the future.

Breakfasttt commented 8 years ago

No problem, thx for your work. i will try that tomorrow. :) (wrong account ^^)

5Mixer commented 8 years ago

Issue progressing on #19, for further details see there.

(Flash client works, however secuitiy issues make it fail unless it is specifically whitelisted. Fixing at #19 )