Networked VR Lab

Networked VR Lab Update : Whenever possible, you SHOULD use multicasting instead of Broadcasting !

For this lab, please read Sun trails about datagram and multicasting

If you need more examples, see

Exploring Network Communication Possibilities

   In the squash game assignment various game elements (racquets, balls, etc.) must to talk with each other over the network. Some of the messages will be critical to the game functioning (player logon messages) and thus will need to be sent via TCP. Other messages which are not critical (ball position updates), so long as not too many are dropped, can be broadcast via UDP.

  Last week we looked at how a client and server might communicate using TCP.  Using PrintWriters and BufferedReaders we were able to send  strings of text over the network. Considering the client and server don't  need to talk very often - logon and logoff messages mainly- you may be able  to modify this application to handle the initial client server communication.  The down side of this is you will have to parse strings at both ends (client  and server) -which can be time consuming- to get the desired information.  To improve the performance in this area you may want to look at the DataOutputStream  and DataInputStream classes which allow you to send primitive variable values over the network.

  e.g.)


Server
Client

ServerSocket ss = new ServerSocket(port);

Socket s = ss.accept();
DataInputStream in = new DataInputStream(s.getInputStream());
System.out.println("Client connected");

int i = in.readInt();
System.out.println("Got int : " + i);

s.close();
System.out.println("Connection closed");


Socket s = new Socket(addr, port);

DataOutputStream out = new DataOutputStream(s.getOutputStream());
System.out.println("Connected to server");

int i = 12345;
out.writeInt(i);
System.out.println("Sent integer : " + i);

s.close();
System.out.println("Closed connection");


Client connected
Got int : 12345
Connection closed



Connected to server
Sent integer : 12345
Closed connection



  Via this TCP communication you can send values to the client such as their  player number, racquet colour, court dimensions, game score, etc.


In the assignment you'll have to send the parameters (via DataOutputStream and DataInputStream objects) to each new player to allow them to construct the game (size of the board ...).

This leads to another problem - identifying messages.

  The server may send a player a message that contains 4 ints - their player  number (1 int) and racquet colour (3 ints). It also may send a message that  contains 30 floats - the squash court parameters. If the client assumes the wrong message it will start parsing the wrong values and your program will probably crash horribly. A simple solution to this problem is to send an integer (or byte) at the beginning of the message that identifies what message type it is, and thus what is contained in the rest of the message. For example if the first integer of the message evaluates to zero the program will know the message is a logon message and that there will be four more integers following. But if the first integer of the message evaluates to one the program will know the message is a logoff message and there will be one more integer in the message.




  Your network implementation will also make use of UDP datagram, allowing  racquets, balls etc. to inform other elements of the game of their position,  orientation etc. without needing direct connections to each of these other  elements. In this section of the application speed is critical. You need to send the critical information in messages that are as small as possible in order to minimise construction and deconstruction time. As such we won't be sending strings over the network. Unfortunately I was not able to find objects similar to DataOutputStream and DataInputStream that allow primitive variables to be converted to byte arrays and then reconstructed from byte arrays. Here are a couple of methods that allow you to convert integers or floats to byte arrays and back again. You should use this lab as an opportunity to test these functions. (btw if anyone does find a way to do this in any of the UDP networking classes I would really like to know.)


   /**
       Convert an integer to an array of bytes
   */
   public byte[] intToBytes(int i)
   {
       byte b[] = {(byte) (i >> 0), (byte) (i >> 8), (byte) (i >> 16), (byte) (i >> 24)};
       return b;
   }

   /**
       Convert an array of bytes to an integer
   */
   public int bytesToInt(byte[] b)
   {
       int i = ((b[0] & 0xff) << 0) + ((b[1] &0xff) << 8) + ((b[2] & 0xff) << 16) + ((b[3] & 0xff) << 24);
       return i;
   }

   /**
       Convert a float to an array of bytes
   */
   public byte[] floatToBytes(float f)
   {
       int i = Float.floatToIntBits(f);
       return intToBytes(i);
   }

   /**
       Convert an array of bytes to a float
   */
   public float bytesToFloat(byte[] b)
   {
       int i = bytesToInt(b);
       float f = Float.intBitsToFloat(i);
       return f;
   }


By using UDP broadcasting out application will not use up as much network resources as it would if we used TCP peer to peer or TCP client server configurations.

Be carreful to use Multicasting instead of UDP Broadcasting as in this example. See the sun trails for a full example :


Peer - Source
Peer - Destination

String addr = "[ip address]";
int port = [your assigned port number];

INetAddress dest = INetAddress.getByName(addr);
byte[] data = [use above methods to create byte array];

DatagramSocket s = new DatagramSocket();
DatagramPacket p = new DatagramPacket(data, data.length, dest, port);

s.send(p);
s.close();


int port = [your assigned port number];
byte[] b = new byte[4096];

DatagramSocket s = new DatagramSocket(port);
DatagramPacket p = new DatagramPacket(data, data.length);

s.receive(p);
[use above methods to decode byte array b]

s.close();





Tips:

You can broadcast messages over the network by specifying the address to something like "192.168.1.255".
By setting the initial buffer size to be 4096 we can be sure that an entire UDP packet can be received.

When implementing the network communication section of your application you'll have several objects communicating via UDP and TCP all at once. Make use of the range of port numbers allowed to you to avoid bottlenecks. For example racquet position updates might be broadcast on port p+1, where as ball position updates might be broadcast on port p+2 and logoff messages via TCP on port p+3.

You'll need to have different network listening / broadcasting objects running in different threads so as to allow the game logic and rendering threads to run smoothly. We saw how to do this last week - by writing classes that extend Thread then calling the start() method. However there is one further complication when you want your real-time application to run as smoothly as possible, that being, when Java creates new threads they are created at the same priority. Thus if you just go and create lots of different threads they'll all be competing for CPU cycles. By yielding you can allow other threads with the same priority a chance to run.

For example to make a thread yield once every 10 loops.

public void run()
{
   boolean done = false;
   int loop = 0;
  
   while(!done)
   {

       // insert code here

       loop ++;
       if (loop%10 == 0)
       {
           yield();
       }
   }
}