In this chapter you'll learn about Java's support of network programming. You'll learn the basics of client/server computing and TCP/IP socket programming. You'll then examine the classes of the java.net package and learn how to use them to develop client/server applications. This chapter provides an introduction to the java.net package. Part V, "Network Programming," explores the information presented here in greater detail.
The java.net package provides a set of classes that support network programming using the communication protocols employed by the Internet. These protocols are known as the Internet protocol suite and include the Internet Protocol (IP), the Transport Control Protocol (TCP), and the User Datagram Protocol (UDP) as well as other, less-prominent supporting protocols. Although this section cannot provide a full description of the Internet protocols, it gives you the basic information that you need to get started with Java network programming. In order to take full advantage of this chapter, you need an Internet connection.
Asking the question What is the Internet? may bring about a heated discussion in some circles. In this book, the Internet is defined as the collection of all computers that are able to communicate, using the Internet protocol suite, with the computers and networks registered with the Internet Network Information Center (InterNIC). This definition includes all computers to which you can directly (or indirectly through a firewall) send Internet Protocol packets.
Computers on the Internet communicate by exchanging packets of data, known as Internet Protocol, or IP, packets. IP is the network protocol used to send information from one computer to another over the Internet. All computers on the Internet (by our definition in this book) communicate using IP. IP moves information contained in IP packets. The IP packets are routed via special routing algorithms from a source computer that sends the packets to a destination computer that receives them. The routing algorithms figure out the best way to send the packets from source to destination.
In order for IP to send packets from a source computer to a destination computer, it must have some way of identifying these computers. All computers on the Internet are identified using one or more IP addresses. A computer may have more than one IP address if it has more than one interface to computers that are connected to the Internet.
IP addresses are 32-bit numbers. They may be written in decimal, hexadecimal, or other formats, but the most common format is dotted decimal notation. This format breaks the 32-bit address up into four bytes and writes each byte of the address as unsigned decimal integers separated by dots. For example, one of my IP addresses is 0xccD499C1. Because 0xcc = 204, 0xD4 = 212, 0x99 = 153, and 0xC1 = 193, my address in dotted decimal form is 204.212.153.193.
IP addresses are not easy to remember, even using dotted decimal notation. The Internet has adopted a mechanism, referred to as the Domain Name System (DNS), whereby computer names can be associated with IP addresses. These computer names are referred to as domain names. The DNS has several rules that determine how domain names are constructed and how they relate to one another. For the purposes of this chapter, it is sufficient to know that domain names are computer names and that they are mapped to IP addresses.
The mapping of domain names to IP addresses is maintained by a system of domain name servers. These servers are able to look up the IP address corresponding to a domain name. They also provide the capability to look up the domain name associated with a particular IP address, if one exists.
As I mentioned, IP enables communication between computers on the Internet by routing data from a source computer to a destination computer. However, computer-to-computer communication only solves half of the network communication problem. In order for an application program, such as a mail program, to communicate with another application, such as a mail server, there needs to be a way to send data to specific programs within a computer.
Ports are used to enable communication between programs. A port is an address within a computer. Port addresses are 16-bit addresses that are usually associated with a particular application protocol. An application server, such as a Web server or an FTP server, listens on a particular port for service requests, performs whatever service is requested of it, and returns information to the port used by the application program requesting the service.
Popular Internet application protocols are associated with well-known ports. The server programs implementing these protocols listen on these ports for service requests. The well-known ports for some common Internet application protocols are
Port Protocol
21 File Transfer Protocol
23 Telnet Protocol
25 Simple Mail Transfer Protocol
80 Hypertext Transfer Protocol
The well-known ports are used to standardize the location of Internet services.
Transport protocols are used to deliver information from one port to another and thereby enable communication between application programs. They use either a connection-oriented or connectionless method of communication. TCP is a connection-oriented protocol and UDP is a connectionless transport protocol.
The TCP connection-oriented protocol establishes a communication link between a source port/IP address and a destination port/IP address. The ports are bound together via this link until the connection is terminated and the link is broken. An example of a connection-oriented protocol is a telephone conversation. A telephone connection is established, communication takes place, and then the connection is terminated.
The reliability of the communication between the source and destination programs is ensured through error-detection and error-correction mechanisms that are implemented within TCP. TCP implements the connection as a stream of bytes from source to destination. This feature allows the use of the stream I/O classes provided by java.io.
The UDP connectionless protocol differs from the TCP connection-oriented protocol in that it does not establish a link for the duration of the connection. An example of a connectionless protocol is postal mail. To mail something, you just write down a destination address (and an optional return address) on the envelope of the item you're sending and drop it in a mailbox. When using UDP, an application program writes the destination port and IP address on a datagram and then sends the datagram to its destination. UDP is less reliable than TCP because there are no delivery-assurance or error-detection and -correction mechanisms built into the protocol.
Application protocols such as FTP, SMTP, and HTTP use TCP to provide reliable, stream-based communication between client and server programs. Other protocols, such as the Time Protocol, use UDP because speed of delivery is more important than end-to-end reliability.
The Internet provides a variety of services that contribute to its appeal. These services include e-mail, newsgroups, file transfer, remote login, and the Web. Internet services are organized according to a client/server architecture. Client programs, such as Web browsers and file transfer programs, create connections to servers, such as Web and FTP servers. The clients make requests of the server, and the server responds to the requests by providing the service requested by the client.
The Web provides a good example of client/server computing. Web browsers are the clients and Web servers are the servers. Browsers request HTML files from Web servers on your behalf by establishing a connection with a Web server and submitting file requests to the server. The server receives the file requests, retrieves the files, and sends them to the browser over the established connection. The browser receives the files and displays them to your browser window.
Clients and servers establish connections and communicate via sockets. Connections are communication links that are created over the Internet using TCP. Some client/server applications are also built around the connectionless UDP. These applications also use sockets to communicate.
Sockets are the endpoints of Internet communication. Clients create client sockets and connect them to server sockets. Sockets are associated with a host address and a port address. The host address is the IP address of the host where the client or server program is located. The port address is the communication port used by the client or server program. Server programs use the well-known port number associated with their application protocol.
A client communicates with a server by establishing a connection to the socket of the server. The client and server then exchange data over the connection. Connection-oriented communication is more reliable than connectionless communication because the underlying TCP provides message-acknowledgment, error-detection, and error-recovery services.
When a connectionless protocol is used, the client and server communicate by sending datagrams to each other's socket. The UDP is used for connectionless protocols. It does not support reliable communication like TCP.
The java.net package provides several classes that support socket-based client/server communication.
The InetAddress class encapsulates Internet IP addresses and supports conversion between dotted decimal addresses and hostnames.
The Socket, ServerSocket, and DatagramSocket classes implement client and server sockets for connection-oriented and connectionless communication. The SocketImpl class and the SocketImplFactory interface provide hooks for implementing custom sockets.
The URL, URLConnection, and URLEncoder classes implement high-level browser-server Web connections. The ContentHandler and URLStreamHandler classes are abstract classes that provide the basis for the implementation of Web content and stream handlers. They are supported by the ContentHandlerFactory and URLStreamHandlerFactory interfaces.
The InetAddress class encapsulates Internet addresses. It supports both numeric IP addresses and hostnames.
The InetAddress class has no public variables or constructors. It provides eight access methods that support common operations on Internet addresses. Three of these methods are static.
The getLocalHost() method is a static method that returns an InetAddress object representing the Internet address of the local host computer. The static getByName() method returns an InetAddress object for a specified host. The static getAllByName() method returns an array of all Internet addresses associated with a particular host.
The getAddress() method gets the numeric IP address of the host identified by the InetAddress object, and the getHostName() method gets its domain name.
The equals(), hashCode(), and toString() methods override those of the Object class.
The NSLookupApp program illustrates the use of the InetAddress class. It takes a hostname as a parameter and identifies the primary IP address associated with that host. (See Listing 17.1.)
Listing 17.1. The source code of the NSLookupApp program.import java.net.InetAddress;
import java.net.UnknownHostException;
import java.lang.System;
public class NSLookupApp {
public static void main(String args[]) {
try {
if(args.length!=1){
System.out.println("Usage: java NSLookupApp hostName");
return;
}
InetAddress host = InetAddress.getByName(args[0]);
String hostName = host.getHostName();
byte ipAddress[] = host.getAddress();
System.out.println("Host name: "+hostName);
System.out.print("IP address: ");
for(int i=0;i<ipAddress.length;++i)
System.out.print((ipAddress[i]+256)%256+".");
System.out.println();
}catch(UnknownHostException ex) {
System.out.println("Unknown host");
return;
}
}
}
Compile NSLookupApp and run it as follows:
C:\java\jdg\ch17>java NSLookupApp sun.com
Host name: sun.com
IP address: 192.9.9.1.
This code example uses NSLookupApp to look up the primary IP address associated with the sun.com host. Try it with other Internet hostnames to look up their IP addresses.
NSLookupApp consists of a single main() method. A try statement surrounds most of the program's statements. It is used to catch the UnknownHostException, which is generated when an invalid hostname is entered by the user or when a hostname cannot be looked up from a DNS server.
NSLookupApp first checks the number of arguments supplied in the program invocation to make sure that a hostname argument is provided by the user. It then uses the hostname string of the first user argument with the static getByName() method of the InetAddress class to create an InetAddress object based on the user-supplied hostname. This InetAddress object is assigned to the host variable. The getHostName() method gets the host's name from the host variable and assigns it to the hostName variable. The getAddress() method returns the four bytes of the host's IP address. The byte array is assigned to the ipAddress[] array. The hostname and IP address are then printed to the console window. The bytes of the ipAddress[] array are converted to positive 8-bit integers before they are printed.
The Socket class implements client connection-based sockets. These sockets are used to develop applications that utilize services provided by connection-oriented server applications.
The Socket class provides four constructors that create sockets and connect them to a destination host and port. The access methods are used to access the I/O streams and connection parameters associated with a connected socket.
The getInetAddress() and getPort() methods get the IP address of the destination host and the destination host port number to which the socket is connected. The getLocalPort() method returns the source host local port number associated with the socket. The getInputStream() and getOutputStream() methods are used to access the input and output streams associated with a socket. The close() method is used to close a socket.
The setSocketImplFactory() class method is used to switch from the default Java socket implementation to a custom socket implementation.
The PortTalkApp program is used to talk to a particular port on a given host on a line-by-line basis. It provides the option of sending a line to the specified port, receiving a line from the other host, or terminating the connection. Its source code is shown in Listing 17.2.
Listing 17.2. The source code of the PortTalkApp program.import java.lang.System;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class PortTalkApp {
public static void main(String args[]){
PortTalk portTalk = new PortTalk(args);
portTalk.displayDestinationParameters();
portTalk.displayLocalParameters();
portTalk.chat();
portTalk.shutdown();
}
}
class PortTalk {
Socket connection;
DataOutputStream outStream;
DataInputStream inStream;
public PortTalk(String args[]){
if(args.length!=2) error("Usage: java PortTalkApp host port");
String destination = args[0];
int port = 0;
try {
port = Integer.valueOf(args[1]).intValue();
}catch (NumberFormatException ex) error("Invalid port number");
try{
connection = new Socket(destination,port);
}catch (UnknownHostException ex) error("Unknown host");
catch (IOException ex) error("IO error creating socket");
try{
inStream = new DataInputStream(connection.getInputStream());
outStream = new DataOutputStream(connection.getOutputStream());
}catch (IOException ex) error("IO error getting streams");
System.out.println("Connected to "+destination+" at port "+port+".");
}
public void displayDestinationParameters(){
InetAddress destAddress = connection.getInetAddress();
String name = destAddress.getHostName();
byte ipAddress[] = destAddress.getAddress();
int port = connection.getPort();
displayParameters("Destination ",name,ipAddress,port);
}
public void displayLocalParameters(){
InetAddress localAddress = null;
try{
localAddress = InetAddress.getLocalHost();
}catch (UnknownHostException ex) error("Error getting local host information");
String name = localAddress.getHostName();
byte ipAddress[] = localAddress.getAddress();
int port = connection.getLocalPort();
displayParameters("Local ",name,ipAddress,port);
}
public void displayParameters(String s,String name,byte ipAddress[],int port){
System.out.println(s+"host is "+name+".");
System.out.print(s+"IP address is ");
for(int i=0;i<ipAddress.length;++i)
System.out.print((ipAddress[i]+256)%256+".");
System.out.println();
System.out.println(s+"port number is "+port+".");
}
public void chat(){
DataInputStream keyboardInput = new DataInputStream(System.in);
boolean finished = false;
do {
try{
System.out.print("Send, receive, or quit (S/R/Q): ");
System.out.flush();
String line = keyboardInput.readLine();
if(line.length()>0){
line=line.toUpperCase();
switch (line.charAt(0)){
case 'S':
String sendLine = keyboardInput.readLine();
outStream.writeBytes(sendLine);
outStream.write(13);
outStream.write(10);
outStream.flush();
break;
case 'R':
int inByte;
System.out.print("***");
while ((inByte = inStream.read()) != '\n')
System.out.write(inByte);
System.out.println();
break;
case 'Q':
finished=true;
break;
default:
break;
}
}
}catch (IOException ex) error("Error reading from keyboard or socket");
} while(!finished);
}
public void shutdown(){
try{
connection.close();
}catch (IOException ex) error("IO error closing socket");
}
public void error(String s){
System.out.println(s);
System.exit(1);
}
}
To see how PortTalkApp works, run it using the following command line:
C:\java\jdg\ch17>java PortTalkApp jaworski.com 7
Connected to jaworski.com at port 7.
Destination host is jaworski.com.
Destination IP address is 204.212.153.193.
Destination port number is 7.
Local host is athome.jaworski.com.
Local IP address is 204.212.153.194.
Local port number is 1298.
Send, receive, or quit (S/R/Q):
PortTalkApp connects to my server at port 7. This is the port number for the echo server application. It is used to test Internet communication between hosts. It identifies my host's name, IP address, and destination port number. In this example, I am connecting from another computer on my local area network. Its name is athome.jaworski.com and has the 204.212.153.194 IP address. When you run the program, your hostname and IP address will be displayed. The local port number that I am connecting from is port 1298.
PortTalkApp asks you whether you want to send a line, receive a line, or quit the program. Whether you elect to send or receive is important. If you decide to receive a line and the host is not sending any data, your program will block while it waits to receive information from a socket-based stream.
Enter an S to send a line and then enter This is a test! on the following line, like this:
Send, receive, or quit (S/R/Q): s
This is a test!
Send, receive, or quit (S/R/Q):
PortTalkApp will send your line to port 7 on my host and then prompt you for your next command. Enter R to receive a line of text from my server:
Send, receive, or quit (S/R/Q): r
***This is a test!
Send, receive, or quit (S/R/Q):
PortTalkApp reads a line of text from the socket stream and displays it prefixed with three asterisks. Now enter Q to close the connection and terminate the program. You can also use PortTalkApp to talk to other ports. For example, you can use it to talk to port 25 of hosts that support the Simple Mail Transport Protocol to send e-mail to someone who is served by that host.
PortTalkApp consists of a simple main() function that creates an object of class PortTalk, passing it the user-supplied host and port arguments. It invokes the displayDestinationParameters() and displayLocalParameters() methods of the PortTalk class to provide the initial connection-status information. The chat() method is used to send and receive lines of text over an established connection. The shutdown() method terminates the connection.
The PortTalk class implements the bulk of the processing performed by the program. It declares three field variables. The connection variable keeps track of the socket used with the connection. The inStream and outStream variables maintain the input and output streams derived from the socket.
The PortTalk constructor checks the arguments supplied by the user to make sure that a host and port number were supplied and converts the user-supplied port number to an integer. The error() method is used to display any errors to the console window. A new Socket object is created using the specified destination hostname and port number and is assigned to the connection variable. The getInputStream() and getOutputStream() methods of the Socket class are used to attach input and output streams to the socket identified by the connection variable. These streams are then filtered as DataInputStream and DataOutputStream objects and assigned to the inStream and outStream variables. The constructor ends by displaying a connection status message to the console window.
The displayDestinationParameters() method uses the getInetAdress() method of the Socket class to get the InetAddress object associated with the destination host of the connection. It uses the getHostName() and getAddress() methods of the InetAddress class to obtain the name and IP address of the destination host. The getPort() method of the Socket class is used to get the destination port number. These parameters are displayed using the displayParameters() method.
The displayLocalParameters() method uses the getLocalHost(), getHostName(), and getAddress() methods of the InetAddress class to obtain the InetAddress object, name, and IP address of the local host. The getLocalPort() method of the Socket class is used to get the local port number. These parameters are displayed using the displayParameters() method.
The displayParameters() method displays the hostname, IP address, and port number of an end of a socket connection. The s string parameter is used to differentiate between a local and destination host.
The chat() method implements the heart of the PortTalkApp program. It displays the Send, receive, or quit (S/R/Q): prompt to the user and then reads an input line from the user's keyboard.
If the user enters S for send, another line is read from the user's keyboard. This line is then written to the output stream associated with the socket connection. A carriage return and a line-feed character are then written to the output stream to signal an end of line. The carriage return-linefeed combination is the standard end-of-line identifier used with Internet application protocols.
If the user enters R for receive, three asterisks (***) are written to the console window to indicate input from the destination host. One byte at a time is then read from the input stream associated with the socket and displayed to the console window until a newline (\n) character is encountered.
If the user enters Q for quit, the do loop of the chat() method is terminated.
The shutdown() method closes the Socket object referenced by the connection variable.
The error() method prints an error message to the console window and then terminates the program using the exit() method of the System class.
The ServerSocket class implements a TCP server socket. It provides two constructors that specify the port to which the server socket is to listen for incoming connection requests. An optional count parameter may be supplied to specify the amount of time that the socket should listen for an incoming connection.
The accept() method is used to cause the server socket to listen and wait until an incoming connection is established. It returns an object of class Socket once a connection is made. This Socket object is then used to carry out a service for a single client. The getInetAddress() method returns the address of the host to which the socket is connected. The getLocalPort() method returns the port on which the server socket listens for an incoming connection. The toString() method returns the socket's address and port number as a string in preparation for printing.
The close() method closes the server socket.
The static setSocketFactory() method is used to change the default ServerSocket implementation to a custom implementation.
The ReverServerApp program is a simple server that listens on port 1234 for incoming connections from client programs. When ReverServerApp connects to a client, it reads one line of text at a time from the client, reverses the characters in the text line, and sends them back to the client. The source code of ReverServerApp is shown in Listing 17.3.
Listing 17.3. The source code of the ReverServerApp program.import java.lang.System;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class ReverServerApp {
public static void main(String args[]){
try{
ServerSocket server = new ServerSocket(1234);
int localPort = server.getLocalPort();
System.out.println("Reverse Server is listening on port "+localPort+".");
Socket client = server.accept();
String destName = client.getInetAddress().getHostName();
int destPort = client.getPort();
System.out.println("Accepted connection to "+destName+" on port "+
destPort+".");
DataInputStream inStream = new DataInputStream(client.getInputStream());
DataOutputStream outStream = new DataOutputStream(client.getOutputStream());
boolean finished = false;
do {
String inLine = inStream.readLine();
System.out.println("Received: "+inLine);
if(inLine.equalsIgnoreCase("quit")) finished=true;
String outLine=new ReverseString(inLine.trim()).getString();
for(int i=0;i<outLine.length();++i)
outStream.write((byte)outLine.charAt(i));
outStream.write(13);
outStream.write(10);
outStream.flush();
System.out.println("Sent: "+outLine);
} while(!finished);
inStream.close();
outStream.close();
client.close();
server.close();
}catch (IOException ex){
System.out.println("IOException occurred.");
}
}
}
class ReverseString {
String s;
public ReverseString(String in){
int len = in.length();
char outChars[] = new char[len];
for(int i=0;i<len;++i)
outChars[len-1-i]=in.charAt(i);
s = String.valueOf(outChars);
}
public String getString(){
return s;
}
}
To see how ReverServerApp works, you need to run it in a separate window and then use PortTalkApp to feed it lines of text. First, run ReverServerApp using the following command line:
C:\java\jdg\ch17>java ReverServerApp
Reverse Server is listening on port 1234.
ReverServerApp notifies you that it is up and running. Now, in a separate window run PortTalkApp as follows, supplying your hostname instead of athome.jaworski.com:
C:\java\jdg\ch17>java PortTalkApp athome.jaworski.com 1234
Connected to athome.jaworski.com at port 1234.
Destination host is athome.jaworski.com.
Destination IP address is 204.212.153.194.
Destination port number is 1234.
Local host is athome.jaworski.com.
Local IP address is 204.212.153.194.
Local port number is 1302.
Send, receive, or quit (S/R/Q):
PortTalkApp displays all of the parameters of both endpoints of the connection. If you look in the window where ReverServerApp is running, you will see a message similar to the following:
Accepted connection to athome.jaworski.com on port 1302.
The port number reported by ReverServer is consistent with that reported by PortTalkApp. Now switch back to the PortTalkApp window and enter S to send a line of text, followed by the line of text This is a test!, as shown in the following output:
Send, receive, or quit (S/R/Q): s
This is a test!
The ReverServerApp window reports the following:
Received: This is a test!
Sent: !tset a si sihT
Enter an R in the PortTalkApp window, as shown in the following output:
Send, receive, or quit (S/R/Q): r
***!tset a si sihT
Send, receive, or quit (S/R/Q):
PortTalkApp displays the text that it received from ReverServerApp. Enter the S command followed by a quit text line:
Send, receive, or quit (S/R/Q): s
quit
The quit line is read by ReverServerApp, causing it to terminate the connection and exit. It displays the following:
Received: quit
Sent: tiuq
C:\java\jdg\ch17>
In the PortTalkApp window, type Q to terminate PortTalkApp, as shown in the following output:
Send, receive, or quit (S/R/Q): q
C:\java\jdg\ch17>
The ReverServerApp program is smaller in size than PortTalkApp. It consists of a single main() method. The ReverseString class is also declared.
The main() method begins by creating a ServerSocket object on port 1234. It then uses the getLocalPort() method to get the local port number associated with the socket. This is to verify that it is indeed using port 1234. It then displays the fact that it is up and running and the number of the port on which it is listening for connections.
The accept() method is used to accept an incoming client connection and return the Socket object associated with the connection. The getHostName() and getPort() methods are used to get the hostname and port number associated with the client program. These parameters are displayed to the console window. Input and output streams are then associated with the socket.
The main() method enters a loop where it reads a line of text from the input stream and then checks to see if it is the quit termination signal. The ReverseString() constructor and getString() method are used to reverse the line read from the input stream. The reversed line is then written to the output stream. If the quit line is received from the client, the loop is terminated and the input stream, output stream, client socket, and server socket are closed.
The ReverseString class provides a constructor that reverses a string and a getString() method for retrieving the reversed string.
The DatagramSocket class is used to implement client and server sockets using the UDP protocol. UDP is a connectionless protocol that allows application programs (both clients and servers) to exchange information using chunks of data known as datagrams.
DatagramSocket provides two constructors. The default constructor creates a datagram socket for use by client applications. No port number is specified. The second constructor allows a datagram socket to be created using a specified port. This constructor is typically used with server applications.
The send() and receive() methods are used to send and receive datagrams using the socket. The datagrams are objects of class DatagramPacket. The getLocalPort() method returns the local port used in the socket. The close() method closes this socket, and the finalize() method performs additional socket-termination processing when the socket is deallocated during garbage collection.
The DatagramPacket class encapsulates the actual datagrams that are sent and received using objects of class DatagramSocket. Two different constructors are provided: one for datagrams that are received from a datagram socket and one for creating datagrams that are sent over a datagram socket. The arguments to the received datagram constructor are a byte array used as a buffer for the received data and an integer that identifies the number of bytes received and stored in the buffer. The sending datagram constructor adds two additional parameters: the IP address and port where the datagram is to be sent.
Four access methods are provided. The getAddress() and getPort() methods are used to read the destination IP address and port of the datagram. The getLength() and getData() methods are used to get the number of bytes of data contained in the datagram and to read the data into a byte array buffer.
The TimeServerApp and GetTimeApp programs illustrate the use of client/server computing using datagrams. TimeServerApp listens on a UDP socket on port 2345 for incoming datagrams. When a datagram is received, it displays the data contained in the datagram to the console window and returns a datagram with the current date and time to the sending client program. It terminates its operation when it receives a datagram with the text quit as its data.
The GetTimeApp program sends five datagrams with the text time in each datagram to local port 2345. After sending each datagram, it waits for a return datagram from TimeServerApp. It displays the datagrams that it sends and receives to the console window. It then sends a quit datagram to TimeServerApp and terminates its operation.
The TimeServerApp program listing is shown in Listing 17.4. The code for GetTimeApp is in Listing 17.5.
Listing 17.4. The source code of the TimeServerApp program.import java.lang.System;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.io.IOException;
import java.util.Date;
public class TimeServerApp {
public static void main(String args[]){
try{
DatagramSocket socket = new DatagramSocket(2345);
String localAddress = InetAddress.getLocalHost().getHostName().trim();
int localPort = socket.getLocalPort();
System.out.print(localAddress+": ");
System.out.println("Time Server is listening on port "+localPort+".");
int bufferLength = 256;
byte packetBuffer[] = new byte[bufferLength];
DatagramPacket datagram = new DatagramPacket(packetBuffer,bufferLength);
boolean finished = false;
do {
socket.receive(datagram);
InetAddress destAddress = datagram.getAddress();
String destHost = destAddress.getHostName().trim();
int destPort = datagram.getPort();
System.out.println("\nReceived a datagram from "+destHost+" at port "+
destPort+".");
String data = new String(datagram.getData(),0).trim();
System.out.println("It contained the data: "+data);
if(data.equalsIgnoreCase("quit")) finished=true;
String time = new Date().toString();
time.getBytes(0,time.length(),packetBuffer,0);
datagram = new DatagramPacket(packetBuffer,bufferLength,destAddress,
destPort);
socket.send(datagram);
System.out.println("Sent "+time+" to "+destHost+" at port "+destPort+".");
} while(!finished);
}catch (IOException ex){
System.out.println("IOException occurred.");
}
}
}
Listing 17.5. The source code of the GetTimeApp program.import java.lang.System;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.io.IOException;
public class GetTimeApp {
public static void main(String args[]){
try{
DatagramSocket socket = new DatagramSocket();
InetAddress localAddress = InetAddress.getLocalHost();
String localHost = localAddress.getHostName();
int bufferLength = 256;
byte packetBuffer[];
DatagramPacket datagram;
for(int i=0;i<5;++i){
packetBuffer = new byte[bufferLength];
"time".getBytes(0,4,packetBuffer,0);
datagram = new DatagramPacket(packetBuffer,256,localAddress,2345);
socket.send(datagram);
System.out.println("\nSent time request to "+localHost+" at port 2345.");
socket.receive(datagram);
InetAddress destAddress = datagram.getAddress();
String destHost = destAddress.getHostName().trim();
int destPort = datagram.getPort();
System.out.println("Received a datagram from "+destHost+" at port "+
destPort+".");
String data = new String(datagram.getData(),0).trim();
System.out.println("It contained the following data: "+data);
}
packetBuffer = new byte[bufferLength];
"quit".getBytes(0,4,packetBuffer,0);
datagram = new DatagramPacket(packetBuffer,256,localAddress,2345);
socket.send(datagram);
}catch (IOException ex){
System.out.println("IOException occurred.");
}
}
}
TimeServerApp and GetTimeApp should be run in
separate windows. First, start TimeServerApp using the
following command line:
C:\java\jdg\ch17>java TimeServerApp
athome.jaworski.com: Time Server is listening
on port 2345.
TimeServerApp will respond by letting you know that it is up and running and listening on port 2345.
Next start GetTimeApp in a different window, as follows:
C:\java\jdg\ch17>java GetTimeApp
C:\java\jdg\ch17>
GetTimeApp reports the packets it sends
to and receives from TimeServerApp
and then terminates. TimeServerApp
provides a similar display in its window, as shown in the following:
Received a datagram from athome.jaworski.com at port 1306.
It contained the data: time
Sent Tue Mar 19 15:12:23 1996 to athome.jaworski.com at port
1306.
Received a datagram from athome.jaworski.com at port 1306.
It contained the data: time
Sent Tue Mar 19 15:12:23 1996 to athome.jaworski.com at port
1306.
Received a datagram from athome.jaworski.com at port 1306.
It contained the data: time
Sent Tue Mar 19 15:12:23 1996 to athome.jaworski.com at port
1306.
Received a datagram from athome.jaworski.com at port 1306.
It contained the data: time
Sent Tue Mar 19 15:12:24 1996 to athome.jaworski.com at port
1306.
Received a datagram from athome.jaworski.com at port 1306.
It contained the data: time
Sent Tue Mar 19 15:12:24 1996 to athome.jaworski.com at port
1306.
Received a datagram from athome.jaworski.com at port 1306.
It contained the data: quit
Sent Tue Mar 19 15:12:24 1996 to athome.jaworski.com at port
1306.
C:\java\jdg\ch17>
These two simple programs illustrate the basic mechanisms of datagram-based client/server applications. A UDP client sends a datagram to a UDP server at the server's port address. The UDP server listens on its port for a datagram, processes the datagram, and sends back information to the UDP client.
TimeServerApp begins by creating a DatagramSocket object on port 2345 and assigning it to the socket variable. It then obtains the hostname and local port number using the getHostName() and getLocalPort() methods and displays this information to the console window.
TimeServerApp creates a 256-byte buffer and assigns it to the packetBuffer[] array. It then creates a DatagramPacket object using the packetBuffer[] array and assigns it to the datagram variable.
TimeServerApp executes a loop where it receives and processes datagrams received from client programs. It receives datagrams using the receive() method of the DatagramSocket class. It uses the getAddress() and getPort() methods of the DatagramPacket class to get the host address and port of the client program that sent the socket. It displays this information to the console window. It uses the getData() method of the DatagramPacket class to retrieve the data sent by the client program. It converts this data to a string and displays it on the console window. If the received data contains the quit string, it sets the finished flag to true. TimeServerApp processes the client time request by using the Date() constructor of the java.util package to construct a new Date object, converting the Date object to a byte array, and storing the data in packetBuffer[]. It then creates a new DatagramPacket object, using packetBuffer[], with the destination address and port number of the sending client program. It then sends the datagram to the client using the send() method of the DatagramSocket class. The console display is then updated with the data that was sent to the client program.
The GetTimeApp client program creates a DatagramSocket object and assigns it to the socket variable. It then creates a DatagramPacket object in the same manner as the TimeServerApp program. GetTimeApp uses a for statement to loop five times, sending five datagrams to port 2345 of the local host. After each datagram is sent, it waits to receive a return datagram from TimeServerApp. It uses the getAddress(), getPort(), and getData() methods of the DatagramPacket class to report this information to the console window.
After sending and receiving five datagrams, GetTimeApp sends a datagram with the quit text to tell TimeServerApp that it should terminate its processing.
The SocketImpl class is an abstract class that
is used to define custom socket implementations. It is used with
the SocketImplFactory interface that must be implemented
by new socket implementations.
Note |
The setSocketImplFactory() method of the Socket class can be used to set the system SocketImplFactory. Once it is set, it cannot be changed. |
The SocketImpl class provides four variables that are used to define a socket: the destination IP address and port, the local port, and a file descriptor used to create streams. The local IP address of the host is assumed.
Some of the access methods defined by SocketImpl are used to perform lower-level socket operations. These include listening for connections, accepting connections, binding a socket to a port, and implementing the actual connection. Datagram sockets are also supported. Other access methods are used to support stream-based I/O and to provide access to the IP address and port parameters of a socket.
In addition to providing the basic TCP- and UDP-based sockets used by almost all Internet client/server applications, the java.net package provides a very useful set of classes that support higher-level, Web-specific applications. These classes are centered around the URL class, which encapsulates an object on the Web, typically a Web page, by its URL address.
URL stands for uniform resource locator and, as its name states, provides a uniform way to locate resources on the Web. Different types of URLs are used with different application protocols, the most common of which are the Hypertext Transfer Protocol (HTTP) and the File Transfer Protocol (FTP). URLs for these types of protocols are mainly used to identify the location of files, such as Web pages, supporting images, multimedia files, text files, and downloadable programs. HTTP URLs also refer to executable programs, such as CGI scripts, which perform Web-related services. CGI scripts are programs, usually written in a scripting language, that receive input and generate output in accordance with the common gateway interface (CGI) specification.
The URL class encapsulates Web objects by their URL address. It provides a set of constructors that allow URL objects to be easily constructed and a set of access methods that allow high-level read and write operations to be performed using URLs.
Most, but not all, URLs typically consist of a protocol, hostname, and the path and name of a file on the host. For example, the URL http://www.jaworski.com/jdg/index.htm refers to a Web page on my Web server. It specifies the HTTP protocol as the protocol used to access the Web page. It identifies my hostname as www.jaworski.com, and it names the file as /jdg/index.htm where /jdg/ is the directory path to the file (relative to my Web server's directory root) and index.htm is the file's name. In HTTP URLs, the pathname/filename is optional. For example, the URL http://www.jaworski.com/jdg/ is equivalent to the previous URL. My Web server uses the filename index.htm as the default name for a file. The pathname can also be omitted. The URL http://www.jaworski.com would use the index.htm file in the Web server's root directory.
The four URL constructors allow URL objects to be created using a variety of URL parameters such as protocol type, hostname, port, and file path. These parameters may be supplied separately or in text form as part of an URL string. The URL class treats a file's path and name as a single entity to provide a more convenient way of working with URL components.
You can construct an URL using its absolute address or using an address that is relative to another URL. Up to now we have been working with the full, complete, or absolute address of an URL. A relative address is a path/filename or file offset that is specified relative to an absolute URL. For example, the absolute URL http://www.jaworski.com can be combined with the relative URL /jdg/index.htm to produce the URL to http://www.jaworski.com/jdg/index.htm.
The URL access methods provide a full set of URL processing capabilities. The getProtocol(), getHost(), getPort(), getFile(), and getRef() methods allow the individual address components of the URL to be determined. The getContent() and openStream() methods allow reading of the Web object pointed to by the URL. The toExternalForm() and toString() methods enable URLs to be converted into strings to support display and printing. The equals() method compares URLs, and the sameFile() method compares the Web objects pointed to by the URLs. The openConnection() method creates an object of class URLConnection to the Web object pointed to by the URL. This class is discussed after the "URLConnection" section of this chapter.
The GetURLApp program illustrates the power provided by the URL class. This small program implements a primitive Web browser. Just run the program with the name of an URL and it makes a connection to the destination Web server and downloads the referenced document. The program's source code is shown in Listing 17.6.
Listing 17.6. The source code of the GetURLApp program.import java.lang.System;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.DataInputStream;
import java.io.IOException;
public class GetURLApp {
public static void main(String args[]){
try{
if(args.length!=1) error("Usage: java GetURLApp URL");
System.out.println("Fetching URL: "+args[0]);
URL url = new URL(args[0]);
DataInputStream inStream = new DataInputStream(url.openStream());
String line;
while ((line = inStream.readLine())!= null){
System.out.println(line);
}
inStream.close();
}catch (MalformedURLException ex){
error("Bad URL");
}catch (IOException ex){
error("IOException occurred.");
}
}
public static void error(String s){
System.out.println(s);
System.exit(1);
}
}
After compiling the program, try running it with the URL http://www.jaworski.com/java/GetURLApp.htm
as follows. Make sure that you use the correct upper- and lowercase
characters:
C:\java\jdg\ch17>java GetURLApp http://www.jaworski.com/java/GetURLApp.htm
The program will respond by displaying the following Web document
from my Web server:
C:\java\jdg\ch17>java GetURLApp http://www.jaworski.com/java/GetURLApp.htm
Fetching URL: http://www.jaworski.com/java/GetURLApp.htm
<!DOCTYPE HTML PUBLIC "-//SQ//DTD HTML 2.0 HoTMetaL +
extensions//EN">
<HTML><HEAD><TITLE>GetURLApp Test Results</TITLE></HEAD>
<BODY><H1>GetURLApp Test Results</H1>
<P>Congratulations! You were able to successfully compile
and run GetURLApp.
</P>
</BODY></HTML>
C:\java\jdg\ch17>
Try running the program with other URLs to see how they are displayed.
GetURLApp consists of a short main() method and the error() method, used to display error messages to the console window.
The main() method checks the arguments supplied by the user to make sure that the correct number of arguments are present. It then displays a message to the console window identifying the URL that it is trying to fetch. It creates an URL object using the URL name supplied by the user and assigns it to the url variable. It then uses the openStream() method of the URL class to create an input stream from the URL. The input stream is filtered as a DataInputStream object and is assigned to the inStream variable. The inStream variable is used to read and display the input stream one line at a time.
The URLConnnection class is an abstract class that encapsulates an active HTTP connection to a Web object represented by an URL. It provides a number of methods for getting information about the Web object, about the connection to the Web object, and for interacting with the Web object.
URLConnection defines several class variables that specify the connection state and associated parameters. It also supplies numerous methods that provide access to the HTTP-specific fields of the connection. This class is studied, in detail, in Part V of this book. The next programming example covers a few aspects of its use.
The URLEncoder class is a very simple class that provides a single static method, encode(), for converting text strings to a form that is suitable for use as part of an URL. This format is known as xwwwformurlencoded and is typically used to encode form data that is sent to a CGI script.
The encode() method converts spaces to plus signs (+) and uses the percent character (%) as an escape code to encode special characters. The two characters that immediately follow a percent sign are interpreted as hexadecimal digits that are combined to produce an eight-bit value.
Listing 17.7 illustrates the use of the encode() method and the URLConnection class. It accesses the echo-query CGI program on my Web server, passing it the "/this/is/extra/path/ information" query string and the "Query string with some special characters: @#$%?&+" query string. The query string is encoded using the encode() method of the URLEncoder class. The echo-query CGI program creates an HTML file that describes the parameters passed to it by my Web server and returns this file to the QueryURLApp program. This file shows how the query string was encoded by the encode() method.
Listing 17.7. The source code of the QueryURLApp program.import java.lang.System;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.net.MalformedURLException;
import java.net.UnknownServiceException;
import java.io.DataInputStream;
import java.io.IOException;
public class QueryURLApp {
public static void main(String args[]){
try{
String urlString = "http://www.jaworski.com/cgi-bin/echo-query";
String extraPathInfo = "/this/is/extra/path/information";
String queryString =
URLEncoder.encode("Query string with some special characters: @#$%?&+");
URL url = new URL(urlString+extraPathInfo+"?"+queryString);
URLConnection connection = url.openConnection();
DataInputStream fromURL = new DataInputStream(url.openStream());
String line;
while ((line = fromURL.readLine())!= null){
System.out.println(line);
}
fromURL.close();
}catch (MalformedURLException ex){
error("Bad URL");
}catch (UnknownServiceException ex){
error("UnknownServiceException occurred.");
}catch (IOException ex){
error("IOException occurred.");
}
}
public static void error(String s){
System.out.println(s);
System.exit(1);
}
}
To run QueryURLApp, just type the following command line:
C:\java\jdg\ch17>java QueryURLApp
QueryURLApp queries the echo-query program on my Web server and displays the HTML file generated by the echo-query program. Notice how the query string was encoded:
<HTML>
<HEAD>
<TITLE>Echo CGI Request</TITLE>
</HEAD>
<BODY>
<H1>CGI Request</H1>
<H2>Command Line Arguments</H2>
<P>Number of command line arguments: 7</P>
<P>Command line arguments: Query string with some special characters:
 @#\$%\?\&+
</P>
<H2>Environment Variables</H2>
<PRE>
AUTH_TYPE =
CONTENT_LENGTH =
CONTENT_TYPE =
GATEWAY_INTERFACE = CGI/1.1
HTTP_AccEPT = text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
HTTP_USER_AGENT = Javainternal_build
PATH_INFO = /this/is/extra/path/information
PATH_TRANSLATED = /usr/local/etc/httpd/htdocs/this/is/extra/path/information
QUERY_STRING =
 Query+string+with+some+special+characters%3a+%40%23%24%25%3f%26%2b
REMOTE_ADDR = 204.212.153.194
REMOTE_HOST = athome.jaworski.com
REMOTE_IDENT =
REMOTE_USER =
REQUEST_METHOD = GET
SCRIPT_NAME = /cgi-bin/echo-query
SERVER_NAME = www.jaworski.com
SERVER_PORT = 80
SERVER_PROTOCOL = HTTP/1.0
SERVER_SOFTWARE = ncSA/1.4.2
</PRE>
<H2>Standard Input</H2>
</BODY>
</HTML>
C:\java\jdg\ch17>
QueryURLApp creates an URL by concatenating the URL for the echo-query program, the extra path information, and the encoded query string. It then uses the openConnection() method of the URL class to create an URLConnection object, which it assigns to the connection variable. The connection is then read and displayed in the same manner as the GetURLApp program.
The ContentHandler class is an abstract class that is used to develop specialized objects that are able to extract and process data associated with new MIME types.
MIME, or multipurpose Internet mail extension, is a general method by which the content of different types of Internet objects can be identified. MIME was originally developed to include different types of objects, such as sounds, images, and videos, in Internet e-mail messages. It was also adopted and popularized by the Web and is used to identify multimedia and other types of Web objects so that appropriate external viewers or plug-in modules can be used to process and display these objects.
The ContentHandler class provides the basis for developing new viewers for processing MIME types that are not currently supported by Java. It consists of a single method, getContent(), that extracts an object of a particular MIME type from an URL connection. The ContentHandlerFactory interface provides a standard method of associating a content handler with a MIME type.
Chapter 28, "Content Handlers," provides a detailed description of how content handlers are developed.
The URLStreamHandler class is an abstract class that is used to develop specialized objects that are able to communicate with Web resources using protocols that are currently not supported by Java. For example, suppose you develop a new protocol for a custom client/server application and you want that protocol to be accessible to Web browsers. You would develop an URLStreamHandler for that protocol. The URLStreamHandlerFactory interface is used to associate a stream handler with a particular protocol. Chapter 28 provides a detailed description of how protocol stream handlers are developed.
In this chapter you have learned about Java's support of network programming and covered the basics of client/server computing and TCP/IP socket programming. You have toured the classes of the java.net package and learned how to use them to develop client/server applications. You have also covered the URL-centric classes that support Web-based applications. You have now completed your introduction to the Java API. The next three parts of this book will show you how to use the Java API to develop Java application programs and applets.