Java Tip 40: Object transport via datagram packets

Transport Java objects over the network with datagram packets

One of the attractive features of Java 1.1 is its inclusion of the classes ObjectInputStream and ObjectOutputStream. With this new API (the method writeObject(Object o) in class ObjectOutputStream and the method object readObject() in class ObjectInputStream), one can get a snapshot of running objects at any time, no matter how complex the object graph. Because such a snapshot is provided through the ObjectOutputStream class, which is a descendent of the OutputStream class, one can easily wrap this stream with any other output stream, thereby implementing any desirable functionality (such as a FileOutputStream).

The presence of these new classes in Java 1.1 makes it possible to transmit a running object over the network. To do this, the object, along with those objects that have been referenced, must be serializable -- that is, able to be converted into a byte stream. Fortunately, in Java 1.1, most built-in classes are serializable. Some classes, however, are not (class Object is a prime example). Not to worry. If your class is derived from a non-serializable class, serialization can be achieved by using method defaultWriteObject() in the ObjectOutputStream class, and can then be deserialized by method defaultReadObject() in class ObjectInputStream.

Once serialized, the object is ready to transmitted over a network. The following example demonstrates how to make an object serializable and how to send it through a stream socket:

//Object Output
import java.net.*;
import java.io.*;
//The sample class to be sent: Factory
class Factory implements Serializable
{  private void writeObject(ObjectOutputStream out) throws IOException  
{    out.defaultWriteObject();  }
  private void readObject(ObjectInputStream in)
               throws IOException, ClassNotFoundException  
{    in.defaultReadObject();  }
}
public class ShowObjOutput
{  public static void main(String[] arg)  
{    try    
{      ObjectOutputStream os;
      Socket sock = new Socket("panda.cs.uno.edu", 6000);
 //panda is the host name
      Factory fa = new Factory();
      os = new ObjectOutputStream( new
             BufferedOutputStream(sock.getOutputStream()));
      os.writeObject(fa);
    }
    catch (IOException ex)
    {}
  }
}

This next example shows you how an object is received by ObjectInputStream from a stream socket:

//Object Input
import java.net.*;
import java.io.*;
public class ShowObjInput
{  public static void main(String[] arg)
  {    try    
{      ObjectInputStream is;
      ServerSocket servSock = new ServerSocket(6000);
      Sock sock;
      sock = servSock.accept();
      is = new ObjectInputStream( new
               BufferedInputStream(sock.getInputStream()));
      Factory o = (Factory)is.readObject();
    }    catch (IOException ex)
    {}
  }
}

Besides tightly coupled sockets, Java also supports connectionless datagram communication with the DatagramSocket class. Can we achieve object input/output with datagram communication? Accomplishing this is not as straightforward as with stream sockets. The problem is that the DatagramSocket is not attached to any stream; rather, a DatagramSocket takes a byte array as parameter for sending and receiving.

It is conceivable that in order to construct a datagram packet, the object must be converted into a byte array. This conversion can be very difficult to achieve if the object is involved in a complicated object graph. A number of published articles have discussed ways in which to implement the object serialization -- that is to pickle (to serialize or to marshal) and unpickle (unmarshal) Java objects into (from) byte stream. However, because the object graph can be very complex, converting a general object graph into a byte array will likely require substantial coding efforts.

So, how do we avoid writing complex pickling code? Here is a way to transmit objects in datagram packets without coding for pickling.

The above figure illustrates the data flow when transmitting an object through a datagram. Following the seven steps given below, you will be able to implement this data flow that transmits an object, myObject, of any object type.

  • Step 1. Preparation: Make your object, let's say myObject, serializable by implementing serializable interface.

  • Step 2. Create a ByteArrayOutputStream object, called, for example, baoStream

  • Step 3. Construct an ObjectOutputStream, say ooStream, using baoStream.

  • Step 4. Write the object myObject to baoStream by calling the method writeObject() of ooStream.

  • Step 5. Retrieve the byte array buffer from baoStream, using its method toByteArray().

  • Step 6. Construct a DatagramPacket, say, dPacket, using the array buffer retrieved from Step 5.

  • Step 7. Send dPacket through the DatagramSocket by calling its method send().

To receive an object, go through the steps listed above but in reverse order, replacing ObjectOutputStream with ObjectInputStream and ByteArrayOutputStream with ByteArrayInputStream.

When programming with a socket, sendTo is a standard function used in the connectionless protocol. I rewrote this function so as to be able to transmit objects. The following code example shows how to implement the send method in a Sender class:

import java.io.*;
import java.net.*;
public class Sender
{  public void sendTo(Object o, String hostName, int desPort)  
{    try    
{      InetAddress address = InetAddress.getByName(hostName);
      ByteArrayOutputStream byteStream = new
          ByteArrayOutputStream(5000);
      ObjectOutputStream os = new ObjectOutputStream(new
                              BufferedOutputStream(byteStream));
      os.flush();
      os.writeObject(o);
      os.flush();
      //retrieves byte array
      byte[] sendBuf = byteStream.toByteArray();
      DatagramPacket packet = new DatagramPacket(
                          sendBuf, sendBuf.length, address, desPort);
      int byteCount = packet.getLength();
      dSock.send(packet);
      os.close();
    }
    catch (UnknownHostException e)
    {
      System.err.println("Exception:  " + e);
      e.printStackTrace();    }
    catch (IOException e)    { e.printStackTrace();
 }
  }
}

The code listing below demonstrates how to implement a receive method in a Receiver class. Method recvObjFrom is for the receiver to receive the object. You can include this method in your code to receive runtime objects.

import java.io.*;
import java.net.*;
public class Receiver
{  public Object recvObjFrom()  
{    try
    {
      byte[] recvBuf = new byte[5000];
      DatagramPacket packet = new DatagramPacket(recvBuf,
                                                 recvBuf.length);
      dSock.receive(packet);
      int byteCount = packet.getLength();
      ByteArrayInputStream byteStream = new
                                  ByteArrayInputStream(recvBuf);
      ObjectInputStream is = new
           ObjectInputStream(new BufferedInputStream(byteStream));
      Object o = is.readObject();
      is.close();
      return(o);
    }
    catch (IOException e)
    {
      System.err.println("Exception:  " + e);
      e.printStackTrace();
    }
    catch (ClassNotFoundException e)
    { e.printStackTrace(); }
    return(null);  }
}

One may worry about the size of the byte array -- because when you construct ByteArrayOutputStream or ByteArrayInputStream, you have to specify the size of the array. Since you don't know the size of a runtime object, you will have trouble specifying that size. The size of a runtime object is often unpredictable. Fortunately, Java's ByteArrayInputStream and ByteArrayOutputStream classes can extend their sizes automatically whenever needed.

Conclusion

By taking advantage of Java's built-in serialization code, I have illustrated a way to transmit objects with using datagram packets. As we have seen, the trick is to use the byte array stream to "streamize" objects into byte arrays.

Shengxi (Sunny) Zhou received a Bachelor of Science degree in computer science in 1991 from Shanghai University of Science and Technology. During the past six years, he has worked for several high-tech companies including Hewlett-Packard. Currently he is getting his Master's degree in computer science at the University of New Orleans. He is working with professor Shengru Tu on his thesis, and his recent research focuses on Java's network facilities.
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more