简体   繁体   中英

Device discovery over Wifi for defined period of time

I am writing a code to send a UDP Multicast over Wifi from my mobile device. There is a server code running on other devices in the network. The servers will listen to the multicast and respond with their IP Address and Type of the system (Type: Computer, Mobile Device, Raspberry Pi, Flyports etc..)

On the mobile device which has sent the UDP Multicast , I need to get the list of the devices responding to the UDP Multicast .

For this I have created a class which will work as the structure of the device details .

DeviceDetails.class

public class DeviceDetails
{
    String DeviceType;
    String IPAddr;
    public DeviceDetails(String type, String IP)
    {
        this.DeviceType=type;
        this.IPAddr=IP;
    }
}

I am sending the UDP Multicast packet at the group address of 225.4.5.6 and Port Number 5432 .

I have made a class which will call a thread which will send the UDP Packets . And on the other hand I have made a receiver thread which implements Callable Interface to return the list of the devices responding.

Here is the code:

MulticastReceiver.java

public class MulticastReceiver implements Callable<DeviceDetails>
{
    DatagramSocket socket = null;
    DatagramPacket inPacket = null;
    boolean check = true;
    public MulticastReceiver()
    {
        try
        {
            socket = new DatagramSocket(5500);
        }
        catch(Exception ioe)
        {
            System.out.println(ioe);
        }
    }
    @Override
    public DeviceDetails call() throws Exception
    {
        // TODO Auto-generated method stub
        try
        {
            byte[] inBuf = new byte[WifiConstants.DGRAM_LEN];
            //System.out.println("Listening");
            inPacket = new DatagramPacket(inBuf, inBuf.length);
            if(check)
            {
                socket.receive(inPacket);

            }

            String msg = new String(inBuf, 0, inPacket.getLength());

            Log.v("Received: ","From :" + inPacket.getAddress() + " Msg : " + msg);
            DeviceDetails device = getDeviceFromString(msg);
            Thread.sleep(100);
            return device;
        }
        catch(Exception e)
        {
            Log.v("Receiving Error: ",e.toString());
            return null;
        }
    }
    public DeviceDetails getDeviceFromString(String str)
    {
        String type;
        String IP;
            type=str.substring(0,str.indexOf('`'));
            str = str.substring(str.indexOf('`')+1);
            IP=str;
        DeviceDetails device = new DeviceDetails(type,IP);
        return device;
    }
}

The following code is of the activity which calls the Receiver Thread :

public class DeviceManagerWindow extends Activity
{
    public void searchDevice(View view)
    {
        sendMulticast = new Thread(new MultiCastThread());
        sendMulticast.start();
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        List<Future<DeviceDetails>> deviceList = new ArrayList<Future<DeviceDetails>>();

            Callable<DeviceDetails> device = new MulticastReceiver();
            Future<DeviceDetails> submit = executorService.submit(device);
            deviceList.add(submit);

        DeviceDetails[] devices = new DeviceDetails[deviceList.size()];
        int i=0;
        for(Future<DeviceDetails> future :deviceList)
        {
            try
            {
                devices[i] = future.get();
            }
            catch(Exception e)
            {
                Log.v("future Exception: ",e.toString());
            }
        }
    }
}

Now the standard way of receiving the packet says to call the receive method under an infinite loop. But I want to receive the incoming connections only for first 30seconds and then stop looking for connections.

This is similar to that of a bluetooth searching. It stops after 1 minute of search.

Now the problem lies is, I could use a counter but the problem is thread.stop is now depricated. And not just this, if I put the receive method under infinite loop it will never return the value.

What should I do.? I want to search for say 30 seconds and then stop the search and want to return the list of the devices responding.

Instead of calling stop() , you should call interrupt() . This causes a InterruptedException to be thrown at interruptable spots at your code, eg when calling Thread.sleep() or when blocked by an I/O operation. Unfortunately, DatagramSocket does not implement InterruptibleChannel , so the call to receive cannot be interrupted.
So you either use DatagramChannel instead of the DatagramSocket , such that receive() will throw a ClosedByInterruptException if Thread.interrupt() is called. Or you need to set a timeout by calling DatagramSocket.setSoTimeout() causing receive() to throw a SocketTimeoutException after the specified interval - in that case, you won't need to interrupt the thread.

Simple approach

The easiest way would be to simply set a socket timeout:

public MulticastReceiver() {
    try {
        socket = new DatagramSocket(5500);
        socket.setSoTimeout(30 * 1000);
    } catch (Exception ioe) {
        throw new RuntimeException(ioe);
    }
}

This will cause socket.receive(inPacket); to throw a SocketTimeoutException after 30 seconds. As you already catch Exception , that's all you need to do.

Making MulticastReceiver interruptible

This is a more radical refactoring.

public class MulticastReceiver implements Callable<DeviceDetails> {
    private DatagramChannel channel;
    public MulticastReceiver() {
        try {
            channel = DatagramChannel.open();
            channel.socket().bind(new InetSocketAddress(5500));
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);
        }
    }
    public DeviceDetails call() throws Exception {
        ByteBuffer inBuf = ByteBuffer.allocate(WifiConstants.DGRAM_LEN);
        SocketAddress socketAddress = channel.receive(inBuf);

        String msg = new String(inBuf.array(), 0, inBuf.capacity());

        Log.v("Received: ","From :" + socketAddress + " Msg : " + msg);
        return getDeviceFromString(msg);;
    }
}

The DeviceManagerWindow looks a bit different; I'm not sure what you intend to do there, as you juggle around with lists and arrays, but you only have one future... So I assume you want to listen for 30 secs and fetch as many devices as possible.

ExecutorService executorService = Executors.newFixedThreadPool(1);
MulticastReceiver receiver = new MulticastReceiver();

List<DeviceDetails> devices = new ArrayList<DeviceDetails>();
long runUntil = System.currentTimeMillis() + 30 * 1000;
while (System.currentTimeMillis() < runUntil) {
    Future<Object> future = executorService.submit(receiver);
    try {
        // wait no longer than the original 30s for a result
        long timeout = runUntil - System.currentTimeMillis();
        devices.add(future.get(timeout, TimeUnit.MILLISECONDS));
    } catch (Exception e) {
        Log.v("future Exception: ",e.toString());
    }
}
// shutdown the executor service, interrupting the executed tasks
executorService.shutdownNow();

That's about it. No matter which solution you choose, don't forget to close the socket/channel.

I have solved it.. you can run your code in following fashion:

DeviceManagerWindow.java

public class DeviceManagerWindow extends Activity
{
    public static Context con;
    public static int rowCounter=0;
    Thread sendMulticast;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_device_manager_window);
        WifiManager wifi = (WifiManager)getSystemService( Context.WIFI_SERVICE );
        if(wifi != null)
        {
            WifiManager.MulticastLock lock = wifi.createMulticastLock("WifiDevices");
            lock.acquire();
        }
        TableLayout tb = (TableLayout) findViewById(R.id.DeviceList);
        tb.removeAllViews();
        con = getApplicationContext();
    }
    public void searchDevice(View view) throws IOException, InterruptedException
    {
        try
        {
            sendMulticast = new Thread(new MultiCastThread());
            sendMulticast.start();
            sendMulticast.join();
        }
        catch(Exception e)
        {
            Log.v("Exception in Sending:",e.toString());
        }

here is the time bound search.... and you can quit your thread using thread.join

        //Device Will only search for 1 minute
        for(long stop=System.nanoTime()+TimeUnit.SECONDS.toNanos(1); stop>System.nanoTime();)
        {
            Thread recv = new Thread(new MulticastReceiver());
            recv.start();
            recv.join();
        }
    }
    public static synchronized void addDevice(DeviceDetails device) throws InterruptedException
    {
        ....
        Prepare your desired list here.
        ....
    }
}

Dont add any loop on the listening side. simply use socket.receive

MulticastReceiver.java

public class MulticastReceiver implements Runnable
{
    DatagramSocket socket = null;
    DatagramPacket inPacket = null;
    public MulticastReceiver()
    {
        try
        {
            socket = new DatagramSocket(WifiConstants.PORT_NO_RECV);
        }
        catch(Exception ioe)
        {
            System.out.println(ioe);
        }
    }
    @Override
    public void run()
    {
        byte[] inBuf = new byte[WifiConstants.DGRAM_LEN];

        //System.out.println("Listening");
        inPacket = new DatagramPacket(inBuf, inBuf.length);
        try
        {
            socket.setSoTimeout(3000)
            socket.receive(inPacket);
            String msg = new String(inBuf, 0, inPacket.getLength());
            Log.v("Received: ","From :" + inPacket.getAddress() + " Msg : " + msg);
            DeviceDetails device = getDeviceFromString(msg);
            DeviceManagerWindow.addDevice(device);

socket.setSoTimeout(3000) will set the listening time for the socket only for 3 seconds. If the packet dont arrive it will go further. DeviceManagerWindow.addDevice(device); this line will call the addDevice method in the calling class. where you can prepare your list

        }
        catch(Exception e)
        {
            Log.v("Receiving Error: ",e.toString());
        }
        finally
        {
            socket.close();
        }
    }
    public DeviceDetails getDeviceFromString(String str)
    {
        String type;
        String IP;
            type=str.substring(0,str.indexOf('`'));
            str = str.substring(str.indexOf('`')+1);
            IP=str;
        DeviceDetails device = new DeviceDetails(type,IP);
        return device;
    }
}

Hope that works.. Well it will work. All the best. Let me know if any problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM