Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Network Programming - Datagram Clients

Java Programming, Lesson # 564, Revised 04/22/98.

Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.

Introduction

As we have explained in previous lessons, sockets come in three varieties which are implemented by the Java classes named Socket, DatagramSocket, and ServerSocket.

This lesson will concentrate on the use of the DatagramSocket class that is used to implement the User Datagram Protocol (UDP).

Previous lessons in this series have concentrated on the use of the TCP protocol. TCP is designed to provide reliable communications between a client and a server.

With TCP, if data packets are lost or damaged in transmission, corrected packets are transmitted. If the packets arrive in incorrect order, the order is corrected before the data in those packets is presented to the application program.

The situation is different with UDP. With UDP, there is no guarantee that the packets will arrive in the correct order at the destination, or no guarantee that they will arrive at all.

UDP stands for Unreliable Data Protocol.

However, those packets that do arrive should arrive more quickly than with TCP, and in some cases, speed of arrival is more important than an absolute guarantee of arrival. For example, the transmission of real time audio signals might be a case where speed of arrival is more important than guaranteed arrival. (When you listen to the morning talk show while driving to work, there is no guarantee that the signals from the radio station will arrive at the antenna on your car. The usually do, but not one-hundred percent of the time.)

Two classes are used to implement the UDP protocol in Java:

UDP programming is different from TCP programming in at least two respects: The DatagramSocket class is used by both the client end and the server end of a datagram communication setup.

The DatagramPacket class is used at both ends to construct or to take apart packets of data that are sent and received via a DatagramSocket object.

To send data, you construct a DatagramPacket object and send it via a DatagramSocket object.

To receive data, you receive a DatagramPacket object from a DatagramSocket object.

In UDP, all information about the communication such as addressee, port, and data are contained in the packet.

To send a packet, you simply

To receive a packet, you The thread will block on the receive() method until a physical packet of data is received via the network. That physical packet will then be used to populate your empty packet (or attempt to do so).

A DatagramSocket listening on a specific port will receive packets from any client that happens to send a packet to that port.

If the messages being sent by each different clients consists of multiple packets, the packets that are received may be intermingled between the various clients and it is the responsibility of the application to sort them out.

DatagramPacket Class

There are two constructors for the DatagramPacket class.

One of the constructors is used when you are going to send the packet.

The other constructor is used when you expect to receive a packet.

Both constructors require you to provide a byte array along with the length of the array.

When you construct a packet to receive data, that is all that you provide. The data that is received will be deposited in your array.

If there is more data in the physical packet that is received than will fit in your array, the excess will be lost and an IllegalArgumentException will be thrown. Although you are not required by the compiler to catch this exception, you should consider doing so.

When you construct a packet to send, you need to put the data to be sent into the array before invoking the send() method.

In addition to the array and its length, you also need to provide

To reiterate, the address and port are stored in the packet object rather than being used to construct the socket object as is the case in TCP.

The maximum physical size of a datagram is 65,535 bytes. Some of this space is consumed by header information, so the data portion of a datagram is limited to about 65,450 bytes (different books disagree on the exact figure, so if you need a more accurate figure, look it up in the datagram specifications).

Getting Information from a Datagram Packet

The DatagramPacket class provides four methods by which you can extract information from an existing packet. The information obtained by each method pretty well matches the name of the method. However, in some cases, it is important to know how to interpret that information. The getAddress() method returns an InetAddress object containing the address of the remote host.

Determining which computer this address represents depends on how the datagram was obtained. If the datagram was received from the Internet, the address represents the computer that sent the datagram.

On the other hand, if the datagram was constructed locally, the address represents the computer to which it is intended to be sent.

Similarly, the getPort() method returns the port from which the datagram was sent, or the port to which it is to be sent, depending on how the datagram was obtained.

The getData() method returns a byte array containing the data portion of the datagram. How you interpret this array depends on the type of data involved. The sample programs in this lesson work exclusively with String data, but that is not a requirement. You can use datagrams to communicate any kind of data between computers if you can put that data into a byte array at one end and extract it from a byte array at the other end.

To repeat a concept that has been expressed in earlier lessons, the responsibility of the system is limited to moving the bytes between computers. It is the responsibility of the programmer to assign meaning to those bytes.

The getLength() method returns the number of bytes contained in the data portion of the datagram.

DatagramSocket Class

An object of the DatagramSocket class is used to both send and receive a datagram. The class has two constructors.

One constructor allows you to specify the port on which the socket will operate.

The other constructor doesn't specify the port, but allows the system to pick an available port.

No matter which constructor you use, the port from which the datagram was sent is placed in the datagram header when it is sent so that it will be available at the receiving end.

Typically, server software will use the constructor that allows a predefined port to be specified. If this were not the case, client software would have no way of knowing which port to address the datagram to.

Client software can use either constructor, but for flexibility purposes, it might be best to use the constructor that allows the system to select an available port. The server should then check to determine the actual port that was used to send the datagram and send the reply to that port.

Other than the ability to specify or not specify a port as described above, there is no difference in the datagram sockets used by clients and servers.

To recap, you specify the destination address and port when you construct the datagram packet.

Then you send the datagram by invoking the send() method on an existing datagram socket and passing the packet object as a parameter to the send() method.

When the packet is sent, the address and port number of the source computer is automatically placed in the header portion of the packet so that they can be retrieved by the destination computer.

To listen for a datagram, you instantiate a DatagramSocket object and bind it to a specific port. Then you invoke the receive() method on that socket.

The receive() method blocks the thread until a datagram is received, so if you need to do anything else in the meantime, you should invoke the receive() method on its own thread.

If you are a server, you bind the object to a predefined port number.

If you are a client listening for a reply from a server, you listen on the same port that was used to send the initial datagram.

If you sent the datagram on an anonymous port, you can keep the socket open that was used to send the initial datagram and use that same socket to listen for the reply.

You can also invoke the getLocalPort() method on the socket before closing it to determine and save the port number being used. This allows you to close the original socket and open another socket on the same port if you have a need to do so.

If you sent the initial datagram on a specified port, you can either continue to use the same socket to listen for the reply, or you can close the original socket and get another socket on the same port.

To reply to an incoming datagram, you

Then you invoke the send() method on an existing DatagramSocket object and pass the packet object as a parameter. The address and port number of the sender will be automatically incorporated into the header of the packet when it is sent.

IMPORTANT: TCP and UDP port numbers aren't related.

You can use the same port number for two processes if one communicates via the TCP protocol and the other communicate via the UDP protocol.

It is common for servers to use the same port number and provide similar services using both protocols for some standard services (such as echo). We will see an example of this in a sample program later in this lesson.

Echo Program

This program was written by upgrading the program named Sockets03 to also include UDP echo testing. Sockets03 was discussed in an earlier lesson.

This program performs two different echo tests with the same server by sending a line of text to the standard echo port which is port 7. The first test is a TCP/IP echo test. The second test is a UDP Datagram echo test.

You must be logged onto an appropriate network for this program to run properly. Otherwise, it will throw an exception of type UnknownHostException.

Most of the program is enclosed in a try/catch block to deal with possible exceptions.

The program begins by instantiating a String object containing the name of the server that you are using to test the program.

This is followed by the declaration and initialization of an int variable identifying the standard echo port number. The standard echo port is number is 7 for both the TCP and UDP echo port protocols.

Two String objects are instantiated, one to be used for the TCP echo test, and the other to be used for the UDP echo test.

Then the program does all of those things necessary to conduct the TCP echo test as discussed in the previous lesson that discussed the program named Sockets03. If you just started reading at this lesson, you might want to go back and review that material.

After completing the TCP echo test the program closes the TCP socket and begins the UDP echo test.

First, the message to be used for the UDP echo test is converted to a byte array.

An object of type InetAddress is instantiated containing the address of the server.

A DatagramPacket object is instantiated containing the byte array along with the address of the server and the number of the echo port on that server.

A DatagramSocket object is instantiated that will be used to send the packet to the server. However, with the UDP protocol, there is no guarantee that the packet will successfully arrive at the server.

The send() method is invoked on the DatagramSocket object, passing the DatagramPacket object as a parameter. This causes the local address and port number to be incorporated into the packet and causes the packet to be sent to the address and port number that were encapsulated in the DatagramPacket object when it was constructed.

The same DatagramSocket and DatagramPacket object will be used to receive the packet that is (hopefully) sent back by the server.

A for loop is used to overwrite the data in the packet with the character x so that it can later be confirmed that the received data in the packet is new data and is not simply the residue of the message originally placed in the packet.

The overwritten data in the packet is displayed to confirm that it consists of a string of character x.

Then the receive() method is invoked on the DatagramSocket object passing the DatagramPacket object as a parameter. This causes the thread to block and wait for a packet to be received on the same port from which the packet was originally sent.

When the physical packet is received from the server, the data in that physical packet is extracted and used to populate the DatagramPacket object that was provided as a parameter to the receive() method.

Once the packet is received, the thread is no longer blocked, and program control moves to a statement that displays the data contained in the DatagramPacket object. As expected, the data is an echo of the message originally sent to the echo port on the server.

Then the socket is closed and the program terminates.

This program was tested using JDK 1.1.3 under Win95.

Assuming that you connect to a server that supports both TCP and UDP echo testing, the output from this program should be:
This is a TCP echo test
xxxxxxxxxxxxxxxxxxxxxxx
This is a UDP echo test

Code Fragments from the Echo Program

Because this program is simply an upgrade of an earlier program, much of the code in this program is exactly like the code that we discussed in an earlier lesson on using the TCP protocol to do echo testing. We will skip over all of the code that was covered in that lesson. We will also omit the exception handling code from our discussion. All of this code is available in the complete program listing that follows later in this lesson.

The first code fragment shows the beginning of the main() method along with the declaration and initialization of some important variables. The names and the initialization values of these variables are self explanatory.
class Sockets07{
  public static void main(String[] args){
    String server = "www2.austin.cc.tx.us";
    int port = 7; //echo port
    String msg1 = "This is a TCP echo test";
    String msg2 = "This is a UDP echo test";
Skipping all of the TCP code brings us to the point in the program where we will convert our message to a byte array and instantiate an InetAddress object that identifies the server. We use the getBytes() method of the String class to convert the message to a byte array.
      byte[] udpMsg = msg2.getBytes();
      InetAddress addr = InetAddress.getByName(server);
Next, we instantiate a DatagramPacket object containing the byte array and its length along with the address and port number of the server.
      DatagramPacket packet = 
        new DatagramPacket(udpMsg,udpMsg.length,addr,port);
Following this, we instantiate an anonymous DatagramSocket object. The term anonymous could be confusing here. The object is anonymous because the port number was not specified. Previously we have talked about anonymous objects as being objects for which there was not a named reference variable. That is not the case here.

Then we use that object to send the datagram to the server by invoking the send() method on the DatagramSocket object.
      DatagramSocket datagramSocket = new DatagramSocket();
      datagramSocket.send(packet);
The next few lines of code are used to overwrite the data in the DatagramPacket object with the character x for the purposes described above.

This code fragment also illustrates how you can access the data portion of a DatagramPacket object for other purposes as well.

Following this, we display the overwritten data bytes which simply displays a string of x characters.
      byte[] dataArray = packet.getData();
      for(int cnt = 0; cnt < packet.getLength(); cnt++)
        dataArray[cnt] = 'x';
      System.out.println(new String(packet.getData()));
At this point we assume that there will be a reply from the server so we invoke the receive() method on the same DatagramSocket object that was used to send the original message to the server. This causes the thread to block and wait for the reply.

Although we didn't do so, it is possible to use the setTimeout() method to specify the length of time that the receive() method will block and wait. This is to guard against the possibility that a reply may not be received which would cause the program to be permanently hung.
      datagramSocket.receive(packet);
Following this, we display the contents of the data portion of the received datagram and close the socket.
      System.out.println(new String(packet.getData()));
      datagramSocket.close();
We will skip the remaining code in the program that simply handles exceptions that may have been thrown.

Program Listing for the Echo Program

You may find it helpful to take a look at the following listing of the program to see the above code fragments in context.
/*File Sockets07.java Copyright 1998, R.G.Baldwin
Revised 01/24/98

Upgraded from Sockets03 to include UDP echo testing.

This program performs two echo tests with a server
by sending a line of text to the echo port: port 7.

The first echo text is a TCP/IP echo test.

The second test is a UDP Datagram echo test.

You must be logged onto an appropriate network for this
program to run properly.  Otherwise, it will throw
an exception of type UnknownHostException.

Most of the program is enclosed in a try/catch block to
deal with possible exceptions.

The program begins by instantiating a String object
containing the name of an echo server that you are
using to test the program.

This is followed by the declaration and initialization of
an int variable identifying the standard echo port number.
The standard echo port is number is 7 for both the TCP and
UDP echo port.

Two String objects are instantiated, one to be used for
the TCP echo test, and the other to be used for the UDP
echo test.  They are named msg1 and msg2.

Then the program does all of those things necessary to
conduct the TCP echo test as described in the earlier 
program named Sockets03.

After completing the TCP echo test the program closes the
TCP socket and begins the UDP echo test.

The message to be used for the UDP echo test is converted
to a byte array.

An object of type InetAddress is instantiated containing
the address of the server.

A DatagramPacket object is instantiated containing the
byte array along with the address of the server and the
number of the echo port on that server.

A DatagramSocket object is instantiated that will
(hopefully) be used to send the packet to the server.

The send() method is invoked on the socket, passing the
packet as a parameter.  This causes the packet to be
sent to the address of the server and port number 
previously encapsulated in the packet.

The same DatagramSocket and packet will be used to 
receive the packet that is (hopefully) sent back by the
server.  


The data in the packet is overwritten with the character x
so that it can later be confirmed that the received data
in the packet is new data and is not simply the residue
of the message originally placed in the packet.  The
overwritten data in the package is displayed, consisting
simply of a string of character x.

Then the receive() method is invoked on the DatagramSocket
passing the packet as a parameter.  This causes the thread
to block and wait for a packet to be received on the
same port from which the packet was originally sent.

When the physical packet is received from the server,
the data in that physical packet is extracted and written
into the data portion of the packet object that was
provided as a parameter to the receive() method.

The thread is no longer blocked, and program control 
moves to a statement that displays the data contained in
the packet object.  As expected, the data is an echo of 
the message originally sent to the echo port on the 
server.

Then the socket is closed and the program terminates.

This program was tested using JDK 1.1.3 under Win95.

Assuming that you connect to a server that supports both
TCP and UDP echo testing, the output from this program 
should be:
  
This is a TCP echo test
xxxxxxxxxxxxxxxxxxxxxxx
This is a UDP echo test
**********************************************************/

import java.net.*;
import java.io.*;
import java.util.*;

class Sockets07{
  public static void main(String[] args){
    String server = "www2.austin.cc.tx.us";
    int port = 7; //echo port
    String msg1 = "This is a TCP echo test";
    String msg2 = "This is a UDP echo test";

    //First conduct a TCP echo test    
    try{
      //Get a socket, connected to the specified server
      // on the specified port.
      Socket socket = new Socket(server,port);
      
      //Get an input stream from the socket
      BufferedReader inputStream = 
                 new BufferedReader(new InputStreamReader(
                                 socket.getInputStream()));

      //Get  an output stream to the socket.  Note
      // that this stream will autoflush.
      PrintWriter outputStream = 
                   new PrintWriter(new OutputStreamWriter(
                           socket.getOutputStream()),true);

      //Send line of text to the server
      outputStream.println(msg1);
      //Get echoed line back from server and display it
      System.out.println(inputStream.readLine());
        
      //Close the TCP socket
      socket.close();
    
      //Now conduct a datagram echo test to same port on
      // the same server
    
      //Convert the message to a byte array
      byte[] udpMsg = msg2.getBytes();
      InetAddress addr = InetAddress.getByName(server);
      //Create packet to send to the UDP echo port
      DatagramPacket packet = 
        new DatagramPacket(udpMsg,udpMsg.length,addr,port);
      //Now get a datagram socket to send the message
      DatagramSocket datagramSocket = new DatagramSocket();
      //Now send the message
      datagramSocket.send(packet);
      
      //Now overwrite the msg in the packet to confirm that
      // echo is really received
      byte[] dataArray = packet.getData();
      for(int cnt = 0; cnt < packet.getLength(); cnt++)
        dataArray[cnt] = 'x';
      //Display overwritten version
      System.out.println(new String(packet.getData()));
      //Now receive the echo into the same packet.  Echo
      // will overwrite current contents of the packet
      datagramSocket.receive(packet);
      //Display the echo
      System.out.println(new String(packet.getData()));
      datagramSocket.close();
    }//end try
    catch(UnknownHostException e){
      System.out.println(e);
      System.out.println(
                       "Must be online to run properly.");
    }//end catch UnknownHostException
    
    catch(SocketException e){System.out.println(e);}
    catch(IOException e){System.out.println(e);}
    
  }//end main
}//end class Sockets07
//=======================================================//
-end-