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.
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.
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 |
UdpEchoServer(){//constructor start(); //start the thread and invoke the run() method }//end constructor |
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); |
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 |
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 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()); |
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(); |
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. //=======================================================// |
/*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 //=======================================================// |