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:
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
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.
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
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).
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.
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
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.
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 |
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"; |
byte[] udpMsg = msg2.getBytes(); InetAddress addr = InetAddress.getByName(server); |
DatagramPacket packet = new DatagramPacket(udpMsg,udpMsg.length,addr,port); |
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); |
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())); |
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); |
System.out.println(new String(packet.getData())); datagramSocket.close(); |
/*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 //=======================================================// |