0

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.

Sharda Singh
  • 727
  • 3
  • 10
  • 19

2 Answers2

2

Instead of calling stop(), you should call interrupt(). This causes a InterruptedException to be thrown at interruptable spots at your code, e.g. 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.

Community
  • 1
  • 1
skirsch
  • 1,640
  • 12
  • 24
-1

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.

Veer Shrivastav
  • 5,434
  • 11
  • 53
  • 83
  • Well, this only works for the "good case". I actually don't see where your main thread leaves again after calling `recv.join();` in case the `DatagramSocket` does not receive anything. Both the receiver as well as the main thread will "hang" in such cases. Also, the statement "you can quit your thread using thread.join" is just plain wrong if you read the [javadoc](http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()). – skirsch Apr 11 '13 at 06:20
  • @skirch: You are right at some points.. but the code runs pretty well.. As I dont have any thing to stop the thread execution so I guess it is the best way that I myself wait for the thread to die. – Veer Shrivastav Apr 11 '13 at 09:03
  • The point is: your thread won't die if no data is sent to your socket. I'll post some code later when I find the time. – skirsch Apr 11 '13 at 10:56