Clients and servers that communicate via a reliable channel, such as a TCP socket, have a dedicated point-to-point channel between themselves, or at least the illusion of one. To communicate, they establish a connection, transmit the data, and then close the connection. All data sent over the channel is received in the same order in which it was sent. This is guaranteed by the channel.In contrast, applications that communicate via datagrams send and receive completely independent packets of information. These clients and servers do not have and do not need a dedicated point-to-point channel. The delivery of datagrams to their destinations is not guaranteed. Nor is the order of their arrival.
Definition: A datagram is an independent, self-contained message sent over the network whose arrival, arrival time, and content are not guaranteed.The
java.net
package contains three classes to help you write Java programs that use datagrams to send and receive packets over the network:DatagramSocket
,DatagramPacket
, andMulticastSocket
An application can send and receiveDatagramPacket
s through aDatagramSocket
. In addition,DatagramPacket
s can be broadcast to multiple recipients all listening to aMulticastSocket
.
The example featured in this section consists of two applications: a client and a server. The server continuously receives datagram packets over a datagram socket. Each datagram packet received by the server indicates a client request for a quotation. When the server receives a datagram, it replies by sending a datagram packet that contains a one-line "quote of the moment" back to the client.The client application in this example is fairly simple. It sends a single datagram packet to the server indicating that the client would like to receive a quote of the moment. The client then waits for the server to send a datagram packet in response.
Two classes implement the server application:
QuoteServer
andQuoteServerThread
. A single class implements the client application:QuoteClient
.Let's investigate these classes, starting with the class that contains the
main
method for the server application. Working With a Server-Side Application contains an applet version of theQuoteClient
class.
TheQuoteServer
class, shown here in its entirety, contains a single method: themain
method for the quote server application. Themain
method simply creates a newQuoteServerThread
object and starts it:Theimport java.io.*;
public class QuoteServer {
public static void main(String[] args) throws IOException {
new QuoteServerThread().start();
}
}QuoteServerThread
class implements the main logic of the quote server.
QuoteServerThread
ClassWhen created, theQuoteServerThread
creates aDatagramSocket
on port 4445 (arbitrarily chosen). This is theDatagramSocket
through which the server communicates with all of its clients.Remember that certain ports are dedicated to well-known services and you cannot use them. If you specify a port that is in use, the creation of thepublic QuoteServerThread() throws IOException {
this("QuoteServer");
}
public QuoteServerThread(String name) throws IOException {
super(name);
socket = new DatagramSocket(4445);
try {
in = new BufferedReader(
new FileReader("one-liners.txt"));
} catch (FileNotFoundException e)
System.err.println("Couldn't open quote file. " +
"Serving time instead.");
}
}DatagramSocket
will fail.The constructor also opens a
BufferedReader
on a file named one-liners.txt which contains a list of quotes. Each quote in the file is on a line by itself.Now for the interesting part of the
QuoteServerThread
: itsrun
method. Therun
method overridesrun
in theThread
class and provides the implementation for the thread. For information about threads, see Defining and Starting a Thread.The
run
method contains awhile
loop that continues as long as there are more quotes in the file. During each iteration of the loop, the thread waits for aDatagramPacket
to arrive over theDatagramSocket
. The packet indicates a request from a client. In response to the client's request, theQuoteServerThread
gets a quote from the file, puts it in aDatagramPacket
and sends it over theDatagramSocket
to the client that asked for it.Let's look first at the section that receives the requests from clients:
The first statement creates an array of bytes which is then used to create abyte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);DatagramPacket
. TheDatagramPacket
will be used to receive a datagram from the socket because of the constructor used to create it. This constructor requires only two arguments: a byte array that contains client-specific data and the length of the byte array. When constructing aDatagramPacket
to send over theDatagramSocket
, you also must supply the Internet address and port number of the packet's destination. You'll see this later when we discuss how the server responds to a client request.The last statement in the previous code snippet receives a datagram from the socket (the information received from the client gets copied into the packet). The receive method waits forever until a packet is received. If no packet is received, the server makes no further progress and just waits.
Now assume that, the server has received a request from a client for a quote. Now the server must respond. This section of code in the run method constructs the response:
If the quote file did not get opened for some reason, thenString dString = null;
if (in == null)
dString = new Date().toString();
else
dString = getNextQuote();
buf = dString.getBytes();in
equals null. If this is the case, the quote server serves up the time of day instead. Otherwise, the quote server gets the next quote from the already opened file. Finally, the code converts the string to an array of bytes.Now, the
run
method sends the response to the client over theDatagramSocket
with this code:The first two statements in this code segment get the Internet address and the port number, respectively, from the datagram packet received from the client. The Internet address and port number indicate where the datagram packet came from. This is where the server must send its response. In this example, the byte array of the datagram packet contains no relevant information. The arrival of the packet itself indicates a request from a client that can be found at the Internet address and port number indicated in the datagram packet.InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);The third statement creates a new
DatagramPacket
object intended for sending a datagram message over the datagram socket. You can tell that the newDatagramPacket
is intended to send data over the socket because of the constructor used to create it. This constructor requires four arguments. The first two arguments are the same required by the constructor used to create receiving datagrams: a byte array containing the message from the sender to the receiver and the length of this array. The next two arguments are different: an Internet address and a port number. These two arguments are the complete address of the destination of the datagram packet and must be supplied by the sender of the datagram. The last line of code sends theDatagramPacket
on its way.When the server has read all the quotes from the quote file, the
while
loop terminates and therun
method cleans up:socket.close();
TheQuoteClient
class implements a client application for theQuoteServer
. This application sends a request to theQuoteServer
, waits for the response, and, when the response is received, displays it to the standard output. Let's look at the code in detail.The
QuoteClient
class contains one method, themain
method for the client application. The top of themain
method declares several local variables for its use:First, theint port;
InetAddress address;
DatagramSocket socket = null;
DatagramPacket packet;
byte[] sendBuf = new byte[256];main
method processes the command-line arguments used to invoke theQuoteClient
application:Theif (args.length != 1) {
System.out.println("Usage: java QuoteClient <hostname>");
return;
}QuoteClient
application requires one command-line arguments: the name of the machine on which theQuoteServer
is running.Next, the
main
method creates aDatagramSocket
:The client uses a constructor that does not require a port number. This constructor just binds theDatagramSocket socket = new DatagramSocket();DatagramSocket
to any available local port. It doesn't matter what port the client is bound to because theDatagramPacket
s contain the addressing information. The server gets the port number from theDatagramPacket
s and send its response to that port.Next, the
QuoteClient
program sends a request to the server:The code segment gets the Internet address for the host named on the command line (presumably the name of the machine on which the server is running). Thisbyte[] buf = new byte[256];
InetAddress address = InetAddress.getByName(args[0]);
DatagramPacket packet = new DatagramPacket(buf, buf.length,
address, 4445);
socket.send(packet);InetAddress
and the port number 4445 (the port number that the server used to create itsDatagramSocket
) are then used to createDatagramPacket
destined for that Internet address and port number. Therefore theDatagramPacket
will be delivered to the quote server.Note that the code creates a
DatagramPacket
with an empty byte array. The byte array is empty because this datagram packet is simply a request to the server for information. All the server needs to know to send a response--the address and port number to which reply--is automatically part of the packet.Next, the client gets a response from the server and displays it:
To get a response from the server, the client creates a "receive" packet and uses thepacket = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(packet.getData(), 0, packet.getLength());
System.out.println("Quote of the Moment: " + received);DatagramSocket
receive method to receive the reply from the server. The receive method waits until a datagram packet destined for the client comes through the socket. Note that if the server's reply is somehow lost, the client will wait forever because of the no-guarantee policy of the datagram model. Normally, a client sets a timer so that it doesn't wait forever for a reply; if no reply arrives, the timer goes off and the client retransmits.When the client receives a reply from the server, the client uses the getData method to retrieve that data from the packet. The client then converts the data to a string and displays it.
After you've successfully compiled the server and the client programs, you run them. You have to run the server program first. Just use the Java interpreter and specify theQuoteServer
class name.Once the server has started, you can run the client program. Remember to run the client program with one command-line argument: the name of the host on which the
QuoteServer
is running.After the client sends a request and receives a response from the server, you should see output similar to this:
Quote of the Moment:
Good programming is 99% sweat and 1% coffee.
DatagramSocket
, which lets programs send
packets to one another, java.net includes a class called
MulticastSocket
. This kind of socket is used on the
client-side to listen for packets that the server broadcasts to
multiple clients.
Let's rewrite the quote server so that it broadcasts
DatagramPacket
s to multiple recipients. Instead of sending
quotes to a specific client that makes a request, the new server now
needs to broadcast quotes at a regular interval. The client needs to be
modified so that it passively listens for quotes and does so on a
MulticastSocket
.
This example is comprised of three classes which are modifications
of
the three classes from the previous example:
MulticastServer
,
MulticastServerThread
,
and
MulticastClient
.
This
discussion
highlights
the
interesting
parts
of
these
classes.
Here is the new version of the server's main program.
The differences between this code and the previous version,
QuoteServer
, are shown in bold:
Basically, the server got a new name and creates aimport java.io.*;
public class MulticastServer {
public static void main(String[] args) throws IOException {
new MulticastServerThread().start();
}
}
MulticastServerThread
instead of a QuoteServerThread
.
Now let's look at the MulticastServerThread
which contains the heart of the server. Here's its class declaration:
We've made this class a subclass ofpublic class MulticastServerThread extends QuoteServerThread {
...
}
QuoteServerThread
so that it can use the constructor,
and inherit some member variable and the getNextQuote
method.
Recall that QuoteServerThread
creates a
DatagramSocket
bound to port 4445 and opens the quote
file.
The DatagramSocket
's port number doesn't actually matter
in this example because the client never send anything to the server.
The only method explicitly implemented in MulticastServerThread
is its run
method. The differences between this run
method and the one in QuoteServerThread
are shown in
bold:
The interesting change is how thepublic void run() {
while (moreQuotes) {
try {
byte[] buf new byte[256];
// don't wait for request...just send a quote
String dString = null;
if (in == null)
dString = new Date().toString();
else
dString = getNextQuote();
buf = dString.getBytes();
InetAddress group = InetAddress.getByName(
"230.0.0.1");
DatagramPacket packet;
packet = new DatagramPacket(buf, buf.length,
group, 4446);
socket.send(packet);
try {
sleep((long)Math.random() * FIVE_SECONDS);
} catch (InterruptedException e) { }
} catch (IOException e) {
e.printStackTrace();
moreQuotes = false;
}
}
socket.close();
}
DatagramPacket
is
constructed, in particular, the InetAddress
and port used
to construct
the DatagramPacket
. Recall that the previous example
retrieved the InetAddress
and port number from the packet
sent to the
server from the client. This was because the server needed to reply
directly to the client. Now, the server needs to address multiple
clients. So this time both the InetAddress
and the port
number are
hard-coded.
The hard-coded port number is 4446 (the client must have a
MulticastSocket
bound to this port). The hard-coded
InetAddress
of the DatagramPacket
is
"230.0.0.1" and is a
group identifier (rather than the Internet address of the machine on
which a single client is running). This particular address was
arbitrarily chosen from the reserved for this purpose.
Created in this way, the DatagramPacket
is destined
for
all clients listening to port number 4446 who are member of the
"230.0.0.1" group.
To listen to port number 4446, the new client program just created
its
MulticastSocket
with that port number. To become a member
of the "230.0.0.1" group, the client calls the
MulticastSocket
's joinGroup
method with the
InetAddress
that identifies the group. Now, the client is
set up to
receive DatagramPacket
s destined for the port and group
specified. Here's the relevant code from the new client program (which
was also rewritten to passively receive quotes rather than actively
request them). The bold statements are the ones that interact with the
MulticastSocket
:
Notice that the server uses aMulticastSocket socket = new MulticastSocket(4446);
InetAddress group = InetAddress.getByName("230.0.0.1");
socket.joinGroup(group);
DatagramPacket packet;
for (int i = 0; i < 5; i++) {
byte[] buf = new byte[256];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(packet.getData());
System.out.println("Quote of the Moment: " + received);
}
socket.leaveGroup(group);
socket.close();
DatagramSocket
to broadcast
packet received by the client over a MulticastSocket
.
Alternatively, it could have used a MulticastSocket
. The
socket used by the server to send the DatagramPacket
is
not important. What's important when broadcasting packets is the
addressing information contained in the DatagramPacket
,
and the socket used by the client to listen for it
MulticastServer
and several clients.
Watch how the clients all get the same quotes.