Network Protocol Java

//package com.ryanm.util.net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
/**
 * Performs broadcast and multicast peer detection. How well this
 * works depends on your network configuration
 * 
 * @author ryanm
 */
public class PeerDiscovery
{
  private static final byte QUERY_PACKET = 80;
  private static final byte RESPONSE_PACKET = 81;
  /**
   * The group identifier. Determines the set of peers that are able
   * to discover each other
   */
  public final int group;
  /**
   * The port number that we operate on
   */
  public final int port;
  /**
   * Data returned with discovery
   */
  public int peerData;
  private final DatagramSocket bcastSocket;
  private final InetSocketAddress broadcastAddress;
  private boolean shouldStop = false;
  private List responseList = null;
  /**
   * Used to detect and ignore this peers response to it's own query.
   * When we send a response packet, we set this to the destination.
   * When we receive a response, if this matches the source, we know
   * that we're talking to ourselves and we can ignore the response.
   */
  private InetAddress lastResponseDestination = null;
  /**
   * Redefine this to be notified of exceptions on the listen thread.
   * Default behaviour is to print to stdout. Can be left as null for
   * no-op
   */
  public ExceptionHandler rxExceptionHandler = new ExceptionHandler();
  private Thread bcastListen = new Thread( PeerDiscovery.class.getSimpleName()
      + " broadcast listen thread" ) {
    @Override
    public void run()
    {
      try
      {
        byte[] buffy = new byte[ 5 ];
        DatagramPacket rx = new DatagramPacket( buffy, buffy.length );
        while( !shouldStop )
        {
          try
          {
            buffy[ 0 ] = 0;
            bcastSocket.receive( rx );
            int recData = decode( buffy, 1 );
            if( buffy[ 0 ] == QUERY_PACKET && recData == group )
            {
              byte[] data = new byte[ 5 ];
              data[ 0 ] = RESPONSE_PACKET;
              encode( peerData, data, 1 );
              DatagramPacket tx =
                  new DatagramPacket( data, data.length, rx.getAddress(), port );
              lastResponseDestination = rx.getAddress();
              bcastSocket.send( tx );
            }
            else if( buffy[ 0 ] == RESPONSE_PACKET )
            {
              if( responseList != null && !rx.getAddress().equals( lastResponseDestination ) )
              {
                synchronized( responseList )
                {
                  responseList.add( new Peer( rx.getAddress(), recData ) );
                }
              }
            }
          }
          catch( SocketException se )
          {
            // someone may have called disconnect()
          }
        }
        bcastSocket.disconnect();
        bcastSocket.close();
      }
      catch( Exception e )
      {
        if( rxExceptionHandler != null )
        {
          rxExceptionHandler.handle( e );
        }
      }
    };
  };
  /**
   * Constructs a UDP broadcast-based peer
   * 
   * @param group
   *           The identifier shared by the peers that will be
   *           discovered.
   * @param port
   *           a valid port, i.e.: in the range 1025 to 65535
   *           inclusive
   * @throws IOException
   */
  public PeerDiscovery( int group, int port ) throws IOException
  {
    this.group = group;
    this.port = port;
    bcastSocket = new DatagramSocket( port );
    broadcastAddress = new InetSocketAddress( "255.255.255.255", port );
    bcastListen.setDaemon( true );
    bcastListen.start();
  }
  /**
   * Signals this {@link PeerDiscovery} to shut down. This call will
   * block until everything's timed out and closed etc.
   */
  public void disconnect()
  {
    shouldStop = true;
    bcastSocket.close();
    bcastSocket.disconnect();
    try
    {
      bcastListen.join();
    }
    catch( InterruptedException e )
    {
      e.printStackTrace();
    }
  }
  /**
   * Queries the network and finds the addresses of other peers in
   * the same group
   * 
   * @param timeout
   *           How long to wait for responses, in milliseconds. Call
   *           will block for this long, although you can
   *           {@link Thread#interrupt()} to cut the wait short
   * @param peerType
   *           The type flag of the peers to look for
   * @return The addresses of other peers in the group
   * @throws IOException
   *            If something goes wrong when sending the query packet
   */
  public Peer[] getPeers( int timeout, byte peerType ) throws IOException
  {
    responseList = new ArrayList();
    // send query byte, appended with the group id
    byte[] data = new byte[ 5 ];
    data[ 0 ] = QUERY_PACKET;
    encode( group, data, 1 );
    DatagramPacket tx = new DatagramPacket( data, data.length, broadcastAddress );
    bcastSocket.send( tx );
    // wait for the listen thread to do its thing
    try
    {
      Thread.sleep( timeout );
    }
    catch( InterruptedException e )
    {
    }
    Peer[] peers;
    synchronized( responseList )
    {
      peers = responseList.toArray( new Peer[ responseList.size() ] );
    }
    responseList = null;
    return peers;
  }
  /**
   * Record of a peer
   * 
   * @author ryanm
   */
  public class Peer
  {
    /**
     * The ip of the peer
     */
    public final InetAddress ip;
    /**
     * The data of the peer
     */
    public final int data;
    private Peer( InetAddress ip, int data )
    {
      this.ip = ip;
      this.data = data;
    }
    @Override
    public String toString()
    {
      return ip.getHostAddress() + " " + data;
    }
  }
  /**
   * Handles an exception.
   * 
   * @author ryanm
   */
  public class ExceptionHandler
  {
    /**
     * Called whenever an exception is thrown from the listen
     * thread. The listen thread should now be dead
     * 
     * @param e
     */
    public void handle( Exception e )
    {
      e.printStackTrace();
    }
  }
  /**
   * @param args
   */
  public static void main( String[] args )
  {
    try
    {
      int group = 6969;
      PeerDiscovery mp = new PeerDiscovery( group, 6969 );
      boolean stop = false;
      BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
      while( !stop )
      {
        System.out.println( "enter \"q\" to quit, or anything else to query peers" );
        String s = br.readLine();
        if( s.equals( "q" ) )
        {
          System.out.print( "Closing down..." );
          mp.disconnect();
          System.out.println( " done" );
          stop = true;
        }
        else
        {
          System.out.println( "Querying" );
          Peer[] peers = mp.getPeers( 100, ( byte ) 0 );
          System.out.println( peers.length + " peers found" );
          for( Peer p : peers )
          {
            System.out.println( "\t" + p );
          }
        }
      }
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
  }
  private static int decode( byte[] b, int index )
  {
    int i = 0;
    i |= b[ index ] << 24;
    i |= b[ index + 1 ] << 16;
    i |= b[ index + 2 ] << 8;
    i |= b[ index + 3 ];
    return i;
  }
  private static void encode( int i, byte[] b, int index )
  {
    b[ index ] = ( byte ) ( i >> 24 & 0xff );
    b[ index + 1 ] = ( byte ) ( i >> 16 & 0xff );
    b[ index + 2 ] = ( byte ) ( i >> 8 & 0xff );
    b[ index + 3 ] = ( byte ) ( i & 0xff );
  }
}