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

Network Programming - Datagram Servers

Java Programming, Lesson # 566, Revised 01/27/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

In one of our previous lessons, we learned how to transfer data between a client and a server using the TCP protocol. We used that information to build a server program named Server01 that supported both the HTTP application protocol and an echo protocol.

In another lesson, we learned how to transfer datagrams between computers using the UDP protocol.

In this lesson, we will expand our previous server program to support echo processing of datagrams using the UDP protocol. This will give us a single program that supports three different servers at the same time on different threads.

Server Program

This program is an upgrade of the program named Server01. The purpose of the upgrade is to support a UDP Datagram echo port on port 7.

This program uses sockets to implement three different servers in a single program on an IP network. Two of the servers support the TCP protocol and one supports the UDP protocol.

The program is intended for illustration purposes only. If you use this program for any purpose, you use it at your own risk.

If you use this program, you should tighten the security manager to the degree needed by your situation. You can tighten the security manager by removing the overridden methods that begin with the word check (such as checkAccept) in the class named ServerSecurityManager.

This program implements three different servers.

One of the servers is a UDP "echo" server implemented by a thread monitoring a DatagramSocket on port 7. This server echoes the byte array contained in a datagram received on this port and sends that data back to the client where it originated.

Port 7 is the standard echo port for both UDP and TCP echo servers.

The second server is a TCP "echo" server implemented by a thread monitoring a ServerSocket object on port 7. This server also echoes the data that it receives and sends it back to the client that requested the connection.

The third server is an abbreviated HTTP server implemented by a TCP thread monitoring port 80. Port 80 is the standard HTTP port. This server has the ability to respond to a GET command from a web browser and deliver a file as a stream of bytes.

The different servers were implemented on different ports to illustrate the manner in which threads can be used in Java to service multiple ports. Also, this program illustrates that a single program can provide a mixture of TCP and UDP servers.

A custom security manager is implemented which attempts to prevent the HTTP server from delivering files other than those in the current directory or in a subdirectory of the current directory. Otherwise, the security manager is wide open and doesn't enforce any security at all.

Please DO NOT install this server on your network and leave it unattended because client computers could then connect and have broad access to your computer.

This program was tested using JDK 1.1.3 under Win95.

Both the UDP echo portion of this program and the TCP echo portion can be tested using the program named Sockets08 which was designed specifically for testing the echo portion of the program. A listing of Sockets08 is provided at the end of the lesson.

The HTTP portion of this program can be tested using an ordinary browser with the server name localhost. It can also be tested using the program named Sockets06 which was designed specifically for testing the security manager installed in this program. You saw this program in an earlier lesson. You can also test the security manager using an ordinary browser.

Code Fragments

We will begin with the code fragment that instantiates a security manager and instantiates the three server objects. The boldface line shows the material added in the upgraded version of this program.

Each of these server objects are thread objects. Thus, the three servers operate concurrently and asynchronously on different threads.

In addition, whenever a server objects needs to service a client, it spawns another thread at a lower priority to service that client. Then the server thread goes back to listening for other clients that may be requesting service.
public class Server02{
  public static void main(String[] argv){
    System.setSecurityManager(new ServerSecurityManager());
    HttpServer httpServerThread = new HttpServer();
    EchoServer echoServerThread = new EchoServer();
    UdpEchoServer udpEchoServerThread = new UdpEchoServer();
  }//end main
}//end class Server02
The next fragment shows the constructor for the class used to instantiate an object to provide UDP echo services on port 7. This constructor simply invokes its own start() method to get the thread up and running. The start() method in turn invokes the run() method.
  UdpEchoServer(){//constructor
    start(); //start the thread and invoke the run() method
  }//end constructor
The next code fragment begins the discussion of the run() method of the thread that provides UDP echo services on port 7.

For brevity, exception handling code has been removed from the discussion, as has some of the code whose only purpose is to display status while the server program is running. That code is available in the program listing that is provided near the end of the lesson.

We begin by instantiating a DatagramSocket object and binding it to port 7.
    DatagramSocket datagramSocket = new DatagramSocket(7);
Following this, we enter an infinite loop where we first instantiate an empty DatagramPacket object and then invoke the receive() method on the DatagramSocket object passing the DatagramPacket object as a parameter.

Note that this program is limited to an incoming data length of 1024 bytes because that is the size of the byte array in the empty DatagramPacket object. If you need it to be larger, simply increase the values in the constructor call for the DatagramPacket object.

The receive() method blocks the thread and waits for a datagram packet to arrive. When a packet does arrive, it is used to populate the empty DatagramPacket object that was passed to the receive() method as a parameter.

Then a new thread of type UdpEchoConnection is instantiated to handle the client request and the newly populated DatagramPacket object is passed as a parameter to the constructor for that thread.

Having disposed of this requirement to service a client, the UdpEchoServer thread goes back to the top of the loop, instantiates another empty DatagramPacket object, and blocks again waiting for the arrival of the next datagram packet.
      while(true){
        DatagramPacket packet = 
                   new DatagramPacket(new byte[1024],1024);
        datagramSocket.receive(packet);
        new UdpEchoConnection(packet);
      }//end while loop
That brings us to the UdpEchoConnection class from which the object is instantiated to service the client. All of the information available regarding this client is contained in the DatagramPacket object that the constructor for this thread receives as a parameter.

This object is saved by the constructor for later use. Then the constructor sets the thread priority to a level below the level of the threads monitoring the ports so that the activity of the UdpEchoConnection thread cannot interfere with the ability of the other threads to properly service those ports.

Once the datagram object is saved and the priority is adjusted, the constructor invokes the start() method which in turn invokes the run() method to get the thread up and running.
class UdpEchoConnection extends Thread{
  DatagramPacket packet;

  UdpEchoConnection(DatagramPacket packet){//constructor
    this.packet = packet;
    setPriority( NORM_PRIORITY-1 );
    start();//start this thread and invoke the run method
  }//end constructor
The only requirement of this thread is to send a copy of the data in the packet that was just received back to the client that sent it. The client's address and port number are already contained in the packet, having been placed there by the client's DatagramSocket when the packet was sent.

The DatagramPacket object that was populated from that packet is directly suitable for sending back to the client as an echo with no modifications required.

However, to illustrate some of the other methods of the DatagramPacket class, I elected to use that object to construct a new object and to send the new object back to the client. This is closer to what might be needed in a server with requirements more complex than simply to echo the incoming packet.

The code for extracting information from one such object for the construction of a new object is shown below. As an exercise, you might consider inserting a date stamp into the data portion of the object so that the packet that is returned is not exactly like the packet that was received but contains a date stamp in addition to the original data.

Note that the following code fragment is simply one long statement.
  public void run(){
    DatagramPacket packetToSend = new DatagramPacket(
                                      packet.getData(),
                                      packet.getLength(),
                                      packet.getAddress(),
                                      packet.getPort());
The next step is to instantiate a new DatagramSocket object and use that object to send the new DatagramPacket object to the client by invoking the send() method on the socket object and passing the packet object as a parameter.

Then, assuming that no exceptions have been thrown, the socket is closed and the thread terminates normally. If an exception has been thrown, there are several exception handlers provided to deal with the situation. You can view those exception handlers in the listing of the program that follows in the next section.
    DatagramSocket datagramSocket = null;
    datagramSocket = new DatagramSocket();
    datagramSocket.send(packetToSend);
    datagramSocket.close();

Program Listing

This program is an upgrade of the previous program named Server01 and much of the code is the same. To save space, we will remove much of the duplicated code and replace it by comments.

In addition to the program listing of the server program, this section also contains a listing for a short client program that was designed to test the UDP and TCP echo server portions of this program.
/*File Server02.java Copyright 1998, R.G.Baldwin
This program is an upgrade of the program named Server01
to support a UDP Datagram echo port on port 7.

Also a couple of corrections were made to the earlier
program where a socket wasn't being properly closed
in an exception handler.

This program uses sockets to implement three  different 
servers in a single program on an IP network.  The program
is intended for illustration and experimentation purposes
only.  

If you use this program for any purpose, you use it at 
your own risk.

If you use this program, you should tighten the security 
manager to the degree needed by your situation.  You can
tighten the security manager by removing the overridden
methods that begin with the word check (such as 
checkAccept) in the class named ServerSecurityManager.

This program implements three  different servers.  

One of the servers is a UDP "echo" server implemented by
a thread monitoring a DatagramSocket on port 7. This
server echoes the byte array contained in a datagram
received on this port back to the sender of the datagram.
Port 7 is the standard echo port for both UDP and TCP
echo servers.

The second server is a TCP "echo" server implemented by
a thread monitoring a ServerSocket object on port 7.
This server also echoes the string that it receives back
to the the client that requested the connection. 

The third server is an abbreviated HTTP server implemented
by a thread monitoring port 80.  Port 80 is the standard
HTTP port.  This server has the ability to respond to a
GET command from a web browser and serve a file as a stream
of bytes.

The different servers were implemented on different
ports to illustrate the manner in which threads can be 
used in Java to service multiple ports. Also, this 
program illustrates that a single program can provide
a mixture of TCP and UDP communications.

A custom security manager is implemented which attempts
to prevent the server from serving files other than those
in the current directory or in a subdirectory of the 
current directory.  Otherwise, the security manager is 
wide open and doesn't enforce any security at all.

DO NOT install this server on your network and leave it
unattended because client computers could connect and
have broad access to your computer.

This program was tested using JDK 1.1.3 under Win95.

Both the UDP echo portion of this program and the TCP
echo portion can be tested using the program named
Sockets08 which was designed specifically for testing
the echo portion of this program.

The HTTP portion of this program can be tested using an 
ordinary browser with the server name localhost.  It can 
also be tested using the program named Sockets06 which was
designed specifically for testing the security manager
installed in this program.  However, you can also test
the security manager using an ordinary browser.
**********************************************************/

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

public class Server02{
  public static void main(String[] argv){
    //Instantiate a new custom security manager.
    System.setSecurityManager(new ServerSecurityManager());
    //Instantiate a server object to listen on port 80
    HttpServer httpServerThread = new HttpServer();
    //Instantiate a TCP server object to listen on port 7
    EchoServer echoServerThread = new EchoServer();
    //Instantiate a UDP server object to listen on port 7
    UdpEchoServer udpEchoServerThread = new UdpEchoServer();
  }//end main
}//end class Server02
//=======================================================//
//=======================================================//
//This class is used to instantiate a security manager
class ServerSecurityManager extends SecurityManager{
//See the program named Server01 for the security manager
// code that was removed from this section.

//=======================================================//
//=======================================================//

//This class is used to instantiate a UDP server thread
// that listens on port 7 which is the echo port.
class UdpEchoServer extends Thread{
  
  UdpEchoServer(){//constructor
    start(); //start the thread and invoke the run() method
  }//end constructor
  //-----------------------------------------------------//
  
  public void run(){
    try{
      //Instantiate a DatagramSocket on port 7 (echo port)
      DatagramSocket datagramSocket = 
                                     new DatagramSocket(7);
      System.out.println(
                    "UDP Echo Server Listening on port 7");
      //Loop and listen to port 7.  If a call is
      // received, spawn a UdpEchoConnection thread to
      // deal with it.
      while(true){
        //This program is limited to echo string lengths
        // of 1024 bytes.
        DatagramPacket packet = 
                   new DatagramPacket(new byte[1024],1024);
        //This statement blocks on the receive() method
        // and populates the packet if a call is received.
        // The populated packet is passed as a parameter
        // to a new thread that is spawned to handle the
        // client. Then this thread instantiates a new 
        // empty packet and goes back to listening.
        datagramSocket.receive(packet);
        new UdpEchoConnection(packet);
      }//end while loop
    }catch(IOException e){System.out.println(e);}
  }//end run
  
}//end class UdpEchoServer
//=======================================================//

//This class is used to spawn a thread to deal with a UDP
// call that is received on port 7 which is the echo
// port.
class UdpEchoConnection extends Thread{
  DatagramPacket packet;

  UdpEchoConnection(DatagramPacket packet){//constructor
    System.out.println("Received a call on port 7");
    this.packet = packet;
    //Operate at a priority that is below the threads
    // listening on the ports.
    setPriority( NORM_PRIORITY-1 );
    start();//start this thread and invoke the run method
  }//end constructor
  //-----------------------------------------------------//
  
  public void run(){
    System.out.println("Running UDP thread for port 7");
    
    //Create a packet to echo based on the data in the
    // packet that was received as a parameter.
    DatagramPacket packetToSend = new DatagramPacket(
                                      packet.getData(),
                                      packet.getLength(),
                                      packet.getAddress(),
                                      packet.getPort());

    //Declare datagram socket outside of try block
    DatagramSocket datagramSocket = null;
    
    try{
      //Open a new datagram socket
      datagramSocket = new DatagramSocket();
      //Use the new datagram socket to send the message
      // and close the socket.
      datagramSocket.send(packetToSend);
      datagramSocket.close();
      System.out.println("UDP Socket closed");
    }//end 
    catch(UnknownHostException e){
      System.out.println(e);
      datagramSocket.close();
      System.out.println("UDP Socket closed");
    }//end catch UnknownHostException
    
    catch(SocketException e){
      System.out.println(e);
      datagramSocket.close();
      System.out.println("UDP Socket closed");
    }//end catch SocketException
    
    catch( IOException e){
      System.out.println( "I/O error " + e );
      datagramSocket.close();
      System.out.println("UDP Socket closed");
    }//end catch IOException
  }//end run method
}//end class UdpEchoConnection

//=======================================================//
//=======================================================//
 
//This class is used to instantiate a server thread that
// listens on port 7 which is the echo port.
class EchoServer extends Thread{
//See the program named Server01 for the code that was
// removed from this section.  

//=======================================================//
//=======================================================//

//This class is used to instantiate a server thread that
// listens on port 80 which is the http port.
class HttpServer extends Thread{
//See the program named Server01 for the code that was 
// removed from this section.  

//=======================================================//
The following client program was designed specifically to test the server program listed above.
/*File Sockets08.java Copyright 1998, R.G.Baldwin
Revised 01/24/98

This program is identical to the program named Sockets08
except that the server name is localhost.  This program
was created for the sole purpose of testing the server
program named Server02.

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 Sockets08{
  public static void main(String[] args){
    String server = "localhost";
    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 Sockets08
//=======================================================//
-end-