Search code examples
actionscript-3flashnetwork-programmingairudp

Adobe Air - How to detect LAN servers (listening to a specific port)


I wonder how I can detect LAN servers.

I made a client/server app and it all works fine but you have to manually enter the IP address of the server.

Currently I'm trying to use a DatagramSocket on the client/server. The server will send a packet containing its IP address so the client can connect to that address. The packet is send it to its local broadcast IP (192.168.1.255) port 4444.

The client will listen to port 4444. Then receive the packet.

This works if I'm on the same computer (so the is nothing wrong with my code), but not when I try it on another computer and try to connect to my computer.

I'm not that familiar with networking so I was hoping someone can explain to me how to do this properly.

Also, in AIR broadcasting on 255.255.255.255 is not supported: Note: Sending data to a broadcast address is not supported. source


Solution

  • In order to achieve this I've created my own solution (which isn't perfect, but the only working way I could think of).

    On the server side I create a new DatagramSocket that will send a message to each Ip adress in a certain range.

    import flash.utils.setInterval;
    import flash.utils.clearInterval;
    ....
    
        private const subnetMask:Array = NetworkUtil.getSubnetMask(NetworkUtil.getPrefixLength());
        private const ip:Array = NetworkUtil.getIpAddress().split(".");
        private const ipBroadcastSocket:DatagramSocket = new DatagramSocket();
        private static var _broadcastIp:Boolean;
        private static var intervalId:int;
        ....
        {
        ....
            intervalId = setInterval(broadcastIP, 2000, NetworkUtil.getIpAddress());//broadcast the servers Ip-Address every 2 seconds
        }
    
        private function broadcastIP(message:String):void
        {
            if(_broadcastIp){
                try
                {
                    if(subnetMask[1] != 255 || subnetMask[2] != 255){
                        trace("!!! WARNING: NOT A 'C' CLASS NETWORK !!! (Will not broadcast IP.)");
                        clearInterval(intervalId);
                    }
                    else
                    {
                        var data:ByteArray = new ByteArray();
                        data.writeUTFBytes(message);
    
                        for(var i4:int = subnetMask[3]; i4 <= 255; i4++){
                            var tempIp:String = ip[0] + "." + ip[1] + "." + ip[2] + "." + (subnetMask[3] == 255 ? ip[3] : i4);
                            if(tempIp != NetworkUtil.getBroadcastIp() && tempIp != NetworkUtil.getSubnetIp(ip, subnetMask)){
                                serverDatagramSocket.send(data, 0, 0, tempIp, port);
                            }
                        }
                    }
                }
                catch (error:Error)
                {
                    trace(error.message);
                }
            }
        }
    

    Then on the client side, add another DatagramSocket. Then bind it to the same port used by the server and set it to receive mode.

    private var ipBroadcastSocket:DatagramSocket = new DatagramSocket();
    ipBroadcastSocket.bind(4444);
    ipBroadcastSocket.addEventListener(DatagramSocketDataEvent.DATA, dataReceived);
    ipBroadcastSocket.receive();
    
    private function dataReceived(e:DatagramSocketDataEvent):void
    {
        var data:String = e.data.readUTFBytes(e.data.bytesAvailable);
        trace("Server found with IP: "+ data);
    }
    

    Here is my network utils class for those that want to implement this:

    import flash.net.InterfaceAddress;
    import flash.net.NetworkInfo;
    import flash.net.NetworkInterface;
    
    public class NetworkUtil
    {
        private static var address:InterfaceAddress;
    
        {//static constructor
            getAddress(); //Get the adress of this host
        }
    
        public static function getAddress():void
        {
            var interfaceVector:Vector.<NetworkInterface> = NetworkInfo.networkInfo.findInterfaces();
            for each (var networkInt:NetworkInterface in interfaceVector) {
                if (networkInt.active && networkInt.displayName.toLowerCase() != "hamachi") { //Ignore the hamachi interface
                    for each (var addresss:InterfaceAddress in networkInt.addresses) {
                        if (addresss.ipVersion == "IPv4") {
                            if(addresss.address != "127.0.0.1"){
                                trace(networkInt.displayName); //Output ipAdress for debugging
                                address = addresss;
                            }
                        }
                    }
                }
            }
        }
    
        public static function getPrefixLength():int
        {
            return address.prefixLength;
        }
    
        public static function getBroadcastIp():String
        {
            return address.broadcast;
        }
    
        public static function getIpAddress():String
        {
            return address.address;
        }
    
        public static function getSubnetIp(currentIp:Array, subnetMask:Array):String
        {           
            for(var i:int; i < 4; i++){
                currentIp[i] = (subnetMask[i] == 255 ? currentIp[i] : 0);
            }
    
            return currentIp[0] + "." + currentIp[1] + "." + currentIp[2] + "." + currentIp[3];
        }
    
        public static function getAmountOfHosts(prefixLength:int):int
        {
            return (256 << (24-prefixLength)) -2;
        }
    
        public static function getSubnetMask(prefixLength:int):Array
        {
            var subnetMask:Array = [];
    
            for(var i:int = 0; i < 4; i++){
                var subnet:uint = 255;
                if(prefixLength >= 8){
                    prefixLength-=8;
                }else{
                    subnet = 255 - (255 >> prefixLength);
                    prefixLength=0;
                }
    
                subnetMask[i] = subnet;
            }
            return subnetMask;
        }
    
        public static function isValidIp(ip:String):Boolean {
            ip = ip.replace( /\s/g, ""); //remove spaces for checking
            var pattern:RegExp = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
    
            if(pattern.test(ip)){
                var octets:Array = ip.split(".");
                if (parseInt(String(octets[0])) == 0) {
                    return false;
                }
                if (parseInt(String(octets[3])) == 0) {
                    return false;
                }
                return true;
            }
            return false;
        }
    }