Chapter 26

Client Programs


CONTENTS


In this chapter you will learn how to write client programs that support networked client/server applications. You'll learn about the typical client programs found on the Internet and how they are structured. You'll develop simple client programs that support remote login and the sending of mail, and that fetch a list of Web pages. This chapter builds on the material presented in Chapter 17, "Network Programming with the java.net Package." You might want to review Chapter 17 before continuing with the material presented in this chapter.

Types of Clients

Of the client/server applications that are found on the Internet, only a small group is typically used. These include e-mail, the Web, FTP, Usenet news groups, and telnet. Gopher and WAIS, both precursors of the Web, have declined in popularity, having been subsumed by the Web. Typical Internet client programs include e-mail programs, Web browsers, FTP programs, and telnet clients.

E-mail programs provide an easy-to-use interface by which mail can be created and sent, retrieved and displayed, and managed. Popular window-based clients include Eudora and Pegasus. UNIX systems provide a number of popular e-mail clients, including Pine, Elm, and mh.

Web browsers provide a window on the World Wide Web and support the display of Web pages, including, of course, Java programs. The Netscape browser is the most popular browser on the Web and is Java capable. It is supported on UNIX, Windows, and Macintosh systems.

FTP programs provide a convenient way to retrieve files from public Internet file servers or from private file directories. Although a number of user-friendly FTP client programs are available, the simple text-based FTP client is still the most popular and most widely supported.

Newsreader programs simplify the process of working with messages that are posted to Usenet news groups. A number of netnews client programs are available for Windows, Macintosh, UNIX, and other operating system platforms.

Telnet clients are used to remotely log into other systems. These systems are usually UNIX or other operating systems that are powerful enough to provide the underlying capabilities needed to implement multiuser support. Windows and Macintosh systems, because of their inherent limitations, do not support telnet server applications.

Client Responsibilities

Client programs perform a service for their users by connecting with their server counterparts, forwarding service requests based on user inputs, and providing the service results back to the user.

In most cases, the client must initiate the connection. Typically, the server listens on a well-known port for a client connection. The client initiates the connection, which is accepted by the server. The client sends a service request to the server, based on user inputs. The server receives the service request, performs the service, and returns the results of the service to the client. The client receives the service results and displays them to the user.

A Simple Telnet Client

A telnet client program provides users with the capability to log into remote systems. It connects to a telnet server (called a telnet daemon) that listens on port 23 for an incoming connection. The telnet client connects to the daemon, which usually runs a login program and, upon successful login, runs a shell program.

The telnet client must be capable of simultaneously exchanging data with both the user and the remote system. The protocol used for communication between the client and the server is specified in RFC 854, the Telnet Protocol Specification. RFC 854 identifies three basic elements of the telnet protocol: the concept of a network virtual terminal, the principle of negotiated options, and the symmetry between terminals and processes.

The Network Virtual Terminal

The network virtual terminal, or NVT, is a very simple device that forms the basis for establishing telnet-based communication. All telnet clients and servers are required to support the NVT as a minimum capability. It is an abstract device that consists of a printer and a keyboard. The user types characters on the keyboard that are forwarded to the server. The server returns data to the user and the NVT displays it on the printer. The NVT provides local character echoing and half-duplex operation, although remote echoing and full-duplex operation can be used as negotiated options. Lines are terminated using a standard carriage-return-line-feed combination.

The NVT also provides for control operations that support process interruption and the discarding of excessive output. These operations are signaled by using the Interpret as Command (IAC) code as described in the next section.

The Interpret as Command Code

The IAC code is sent from a client or server to a program on the other end of a telnet connection to send a control code or to negotiate an option, as described in the next section. The IAC is a single byte consisting of the value 255 or hex 0xFF. The IAC may be followed by a single byte to send a control code, or by two or more bytes to negotiate an option. For example, the IAC followed by a byte with the decimal value of 243 is used to send a break command.

Because the IAC is used to indicate a command or option negotiated, a special byte sequence is needed to send the byte value 255 used for the IAC. This is accomplished by sending two IACs in succession.

Negotiated Options

Because all telnet clients and servers are required to implement the NVT, they all have a common, but primitive, basis from which to begin operation. Additional options, such as full- duplex operation and character echoing, can be used based on the principle of negotiated options.

Options are negotiated when either the client or server program sends an IAC code to the other. The IAC code is followed by a WILL or DO code and an option code. The WILL code informs the program on the other side of the connection that it intends to use a particular option. The other program may respond with a DO or a DONT response, consisting of the IAC, followed by the DO or DONT code, followed by the option.

A program can also request that the program on the other side of the connection implement an option. This is accomplished by sending the IAC code, the DO code, and the option code. The other program can respond with a WILL or WONT response. A WILL response is indicated by sending the IAC, followed by the WILL code, followed by the option code. A WONT response is sent in the same manner, with the WONT code being used instead of the WILL code.

Symmetry Between Terminals and Processes

As you probably have surmised from reading the previous sections, the communication between client and server is highly symmetrical. Either the client or server can initiate option negotiation. The use of symmetry between client and host simplifies the implementation of the telnet protocol and allows client and host software to be developed from a common base. The TelnetApp program, presented in the next section, makes use of two I/O filters, NVTInputStream and NVTOutputStream, that implement some of the basic elements of the telnet protocol. These streams do not support control characters or additional options. Option negotiation is handled by refusing any additional options other than those provided by the basic NVT.

The TelnetApp Program

The TelnetApp program implements a minimum set of features of the telnet protocol in order to accomplish a remote login to a telnet server. The purpose of the program is not to provide you with a telnet client, but to show you the basics of how these clients work. More sophisticated and powerful telnet client programs can be retrieved from the Internet. The source code of the TelnetApp program is shown in Listing 26.1.


Listing 26.1. The source code for the TelnetApp program.

import java.lang.*;
import java.net.*;
import java.io.*;
import jdg.ch26.NVTInputStream;
import jdg.ch26.NVTOutputStream;
import jdg.ch26.NVTPrinter;

public class TelnetApp {
  public static void main(String args[]){
    PortTalk portTalk = new PortTalk(args);
    portTalk.start();
  }
 }

class PortTalk extends Thread {
  Socket connection;
  PrintStream outStream;
  NVTInputStream inStream;
  NVTPrinter printer;
  public PortTalk(String args[]){
    if(args.length!=2) error("Usage: java TelnetApp 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{
       outStream = new PrintStream(connection.getOutputStream());
       inStream = new NVTInputStream(connection.getInputStream(),outStream);
    }catch (IOException ex) { error("IO error getting streams"); }
    System.out.println("Connected to "+destination+" at port "+port+".");
  }
  public void run() {
    printer = new NVTPrinter(inStream);
    printer.start();
    yield();
    processUserInput();
    shutdown();
  }
  public void processUserInput() {
    try {
     String line;
     boolean finished = false;
     DataInputStream userInputStream = new DataInputStream(System.in);
     do {
       line = userInputStream.readLine();
       if(line == null) finished = true;
       else outStream.println(line);
      } while(!finished);
     } catch(IOException ex) {
     error("Error reading user input");
   }
  }
  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);
  }
 }


Note
The TelnetApp class uses the NVTPrinter, NVTInputStream, and NVTOutputStream classes that are supplied in the following sections. You must type in the NVTPrinter.java, NVTInputStream.java, and NVTOutputStream.java files before compiling TelnetApp.java. The Java compiler will automatically compile these files when TelnetApp.java is compiled.

You use the TelnetApp program in the same way as any other telnet program. But bear in mind that it is only a minimal telnet client. Run the program by invoking it with the hostname of a computer that supports telnet and the well-known telnet port number, port 23.

In the following example, I use the program to log into my account at CTS. Note that the program operates in half-duplex mode, so characters are echoed locally. I substituted asterisks (*) for my password. Take caution when using this program because it will display your password characters in the same manner as any other text that you type.

Also, notice that commands that I type are echoed by my cts.com host:

C:\java\jdg\ch26>java TelnetApp cts.com 23
Connected to cts.com at port 23.


UNIX System V Release 3.2 (crash.cts.com) (ttyp2)

 login: jaworski
Password:****

Last successful login for jaworski: Tue Apr 09 23:17:46 PDT 1996 on ttyp34
Last unsuccessful login for jaworski: Fri Apr 05 09:56:34 PST 1996 on ttyp9

              Welcome to CTSNET!

        Enter 'help' for assistance and information.


1% l
l
total 16
drwx------ 2 jaworski guest 272 Sep 08 1995 Mail
drwxr-xr-x 2 jaworski guest 208 Dec 07 15:09 News
drwxr-xr-x 2 jaworski guest 224 Sep 08 1995 bin
drwxr-xr-x 2 jaworski guest 384 Apr 04 08:43 download
lrwxrwxrwx 1 root root 15 Mar 15 02:56 dropbox -> /ftp/j/jaworski

drwx------ 2 jaworski guest 160 Dec 08 10:35 ga
drwx------ 2 jaworski guest 288 Apr 08 09:49 mail
drwxr-xr-x 3 jaworski guest 112 Dec 01 12:20 writing
2% exit
exit
3% logout

Connection broken.

C:\java\jdg\ch26>

The TelnetApp program creates an object of class PortTalk to perform its processing. This class extends the Thread class in order to implement multithreading capabilities. Its constructor uses the parameters passed in the TelnetApp command-line invocation to set up the connection to the specified host and port.

The run() method creates an object of the NVTPrinter class to interface with the destination host and invokes the processUserInput() method to interface with the user. The processUserInput() method reads a line at a time from the user's console and sends it to the telnet server.

The NVTPrinter Class

The NVTPrinter class performs most of the interesting processing because it interfaces with the server. It does this using the NVTInputStream class covered in the next section. NVTPrinter is also implemented as a subclass of Thread. Its source code is shown in Listing 26.2.


Listing 26.2. The source code for the NVTPrinter class.

package jdg.ch26;

import java.io.*;

public class NVTPrinter extends Thread {
 NVTInputStream inStream;
 public NVTPrinter(NVTInputStream in) {
  super();
  inStream = in;
 }
 public void run() {
  boolean finished = false;
  try {
   do {
    int i = inStream.read();
    if(i == -1) finished = true;
    else{
     System.out.print((char) i);
     System.out.flush();
     yield();
    }
   } while(!finished);
   System.out.println("\nConnection broken.");
   System.exit(0);
  } catch (IOException ex) {
   System.out.println("NVTPrinter error");
   System.exit(1);
  }
 }
}


The NVTInputStream Class

The NVTInputStream class implements the network virtual terminal input interface. Its source code is shown in Listing 26.3.


Listing 26.3. The source code for the NVTInputStream class.

package jdg.ch26;

import java.io.*;

public class NVTInputStream extends FilterInputStream {
 byte IAC = (byte) 0xff;
 byte DO = (byte) 0xfd;
 byte WILL = (byte) 0xfb;
 byte CR = 13;
 byte LF = 10;
 int WONT = 252;
 int DONT = 254;
 int BUFFER_SIZE = 1024;
 OutputStream out;
 byte lineBuffer[] = new byte[BUFFER_SIZE];
 int numBytes = 0;
 public NVTInputStream(InputStream inStream,OutputStream outStream) {
  super(inStream);
  out = outStream;
 }
 public int read() throws IOException {
  boolean recIAC;
  int i;
  do {
   recIAC = false;
   i = in.read();
   if(i == -1) return i;
   byte b = (byte) i;
   if(b == IAC) {
    recIAC = true;
    int cmd = in.read();
    if(cmd == -1) return cmd;
    byte b2 = (byte) cmd;
    if(b2 == IAC) return 255;
    else if(b2 == DO) {
     int opt = in.read();
     if(opt == -1) return opt;
     out.write(255);
     out.write(WONT);
     out.write(opt);
     out.flush();
    }else if(b2 == WILL) {
     int opt = in.read();
     if(opt == -1) return opt;
     out.write(255);
     out.write(DONT);
     out.write(opt);
     out.flush();
    }
   }
  } while(recIAC);
  return i;
 }
 public String readLine() throws IOException {
  numBytes = 0;
  boolean finished = false;
  do {
   int i = read();
   if(i == -1) return null;
   byte b = (byte) i;
   if(b == LF) {
    if(numBytes>0) {
     if(lineBuffer[numBytes-1] == 13)
      return new String(lineBuffer,0,0,numBytes-1);
    }
   }
   lineBuffer[numBytes] = b;
   ++numBytes;
  } while (!finished);
  return null;
 }
}


NVTInputStream uses the network virtual terminal conventions, covered earlier in this chapter, to filter the input stream associated with the connection. It implements the basic read() method and also a convenient readLine() method.

The NVTOutputStream Class

The NVTOutputStream class provides an output analog to the NVTInputStream class. It implements the basic write() method according to the NVT conventions. It also provides a println() method that uses the carriage-return-line-feed (CR-LF) end-of-line conventions. Its source code is shown in Listing 26.4.


Listing 26.4. The source code for the NVTOutputStream class.

package jdg.ch26;

import java.io.*;

public class NVTOutputStream extends PrintStream {
  int IAC = 255;
  byte CR = 13;
  byte LF = 10;
  public NVTOutputStream(OutputStream outStream) {
    super(outStream);
  }
  public void write(int i) {
    if(i == IAC) super.write(i);
    super.write(i);
  }
  public void println(String s) {
    super.print(s);
    super.write(CR);
    super.write(LF);
    super.flush();
  }
 }


A Mail Client

Although mail is sent on the Internet using a variety of protocols, the Simple Message Transfer Protocol (SMTP), described in Request for Comments (RFC) 821, is the basic protocol used to move mail from one host to another. SMTP consists of a mail sender, a mail receiver, and a set of line-oriented commands used to send mail from the sender to the receiver.

Note
Requests for Comments are numbered notes of the Internet community that are usually, but not always, used to describe some aspect of the protocols or services used on the Internet.

RFC 821 describes the complete set of commands used by mail senders and receivers. Here I am using only a minimal subset of these commands to illustrate the development of an SMTP client, the mail sender.

An SMTP client connects to an SMTP server by establishing a connection to port 25 of the server's host. The server accepts the connection, sends a one-line ready notification to the client, and awaits client commands.

The client sends the HELO command with its hostname to introduce itself to the server. The server responds by sending a code that indicates that it is OK to initiate a mail transmission.

The client sends the MAIL command to the server to indicate that it has mail from a specific user. The server responds with a notification to proceed.

The client sends the RCPT command to identify the recipient of the e-mail. The server responds by telling the client whether or not the recipient is valid.

The client sends the DATA command to indicate that it is ready to send the message. The server responds by telling the client that it is OK to begin sending message data.

The client sends the message, a line at a time, terminating the message with a line containing a single period (.). A line of message text beginning with a period is sent by prepending an extra initial period to the message line.

The server acknowledges receiving the last line of text by sending an OK command to the client.

The client then terminates the connection by sending a QUIT command to the server. The server then responds by notifying the client that it is closing the connection.

The MailClientApp Program

The MailClientApp program illustrates the basic operation of a mail client program. It implements the basic SMTP commands described in the previous section. Its source code is shown in Listing 26.5.


Listing 26.5. The source code for the MailClientApp program.

import java.lang.*;
import java.net.*;
import java.io.*;
import java.util.Vector;
import jdg.ch26.NVTInputStream;
import jdg.ch26.NVTOutputStream;
import jdg.ch26.NVTPrinter;

public class MailClientApp {
  public static void main(String args[]){
    MessageInterface messageInterface = new MessageInterface();
    Message msg = messageInterface.getMsg();
    MailTalk mailTalk = new MailTalk(msg);
    mailTalk.run();
  }
 }

class Message {
  String source;
  String destination;
  String subject;
  String text[];
  public Message() {
  super();
  }
  public void setDestination(String dest) {
    destination = dest;
  }
  public String getDestination() {
    return destination;
  }
  public void setSource(String src) {
    source = src;
  }
  public String getSource() {
    return source;
  }
  public String getDestinationHost() {
    return destination.substring(destination.indexOf('@')+1);
  }
    public void setSubject(String subj) {
  subject = subj;
  }
  public String getSubject() {
    return subject;
  }
  public void setText(Vector txt) {
    int n = txt.size();
    text = new String[n];
    for(int i = 0; i< n; ++i) {
    text[i] = (String) txt.elementAt(i);
   }
  }
  public String[] getText() {
   return text;
  }
}

class MessageInterface {
  Message msg;
  public MessageInterface() {
    msg = new Message();
  }
 public Message getMsg() {
   try {
   System.out.print("From: ");
   System.out.flush();
   DataInputStream inStream = new DataInputStream(System.in);
   msg.setSource(inStream.readLine());
   System.out.print("To: ");
   System.out.flush();
   msg.setDestination(inStream.readLine());
   System.out.print("Subject: ");
   System.out.flush();
   msg.setSubject(inStream.readLine());
   System.out.println("Enter message text.");
   System.out.println("Terminate message text with an initial period.");
   Vector text = new Vector();
   boolean finished = false;
   do {
     String line = inStream.readLine();
     if(endOfText(line)) finished = true;
     else text.addElement(line);
   } while(!finished);
   msg.setText(text);
   System.out.println("End of message read.");
   }catch (IOException ex) {
    System.out.println("IO Exception");
    System.exit(1);
   }
   return msg;
 }
 boolean endOfText(String s) {
   if(s.length() == 0) return false;
   if(s.charAt(0) == '.') return true;
   return false;
 }
}

class MailTalk {
   // Communication states
   static final int START = 0;
   static final int HELO = 1;
   static final int MAIL = 2;
   static final int RCPT = 3;
   static final int DATA = 4;
   static final int TEXT = 5;
   static final int QUIT = 6;
   static final int FINISHED = 9;
   Socket connection;
   String localHostName;
   NVTOutputStream outStream;
   NVTInputStream inStream;
   Message msg;
   public MailTalk(Message msg){
     this.msg = msg;
     String destination = msg.getDestinationHost();
     int port = 25;
     try{
       connection = new Socket(destination,port);
       localHostName = InetAddress.getLocalHost().getHostName();
     }catch (UnknownHostException ex) { error("Unknown host"); }
     catch (IOException ex) { error("IO error creating socket"); }
     try{
       outStream = new NVTOutputStream(connection.getOutputStream());
       inStream = new NVTInputStream(connection.getInputStream(),outStream);
    }catch (IOException ex) { error("IO error getting streams"); }
    System.out.println("Connected to "+destination+" at port "+port+".");
  }
  public void run() {
   sendMail();
   shutdown();
 }
 public void sendMail() {
   try {
   int state = START;
   String line;
   do {
    line = inStream.readLine();
    if(line == null) state = FINISHED;
    else{
      System.out.println(line);
      switch(state) {
      case START:
       if(gotResponse(220,line)){
        outStream.println("HELO "+localHostName);
        System.out.println(">>>HELO "+localHostName);
        state = HELO;
      }else state=FINISHED;
     break;
   case HELO:
   if(gotResponse(250,line)){
     outStream.println("MAIL FROM:<"+msg.getSource()+">");
     System.out.println(">>>MAIL FROM:<"+msg.getSource()+">");
     state = MAIL;
   }else state=FINISHED;
   break;
  case MAIL:
   if(gotResponse(250,line)){
    outStream.println("RCPT TO:<"+msg.getDestination()+">");
    System.out.println(">>>RCPT TO:<"+msg.getDestination()+">");
    state = RCPT;
  }else state=FINISHED;
  break;
 case RCPT:
   if(gotResponse(250,line)){
    outStream.println("DATA");
    System.out.println(">>>DATA");
    state = DATA;
  }else state=FINISHED;
  break;
 case DATA:
  if(gotResponse(354,line)){
    String text[] = msg.getText();
    int len = text.length;
    outStream.println("Subject: "+msg.getSubject());
    outStream.println("");
    System.out.println("Subject: "+msg.getSubject());
    System.out.println("");
    for(int i=0;i<len;++i) {
      if(text[i].length() > 0 && text[i].charAt(0) == '.') {
      outStream.println("."+text[i]);
      System.out.println("."+text[i]);
    }else{
      outStream.println(text[i]);
      System.out.println(">>>"+text[i]);
     }
    }
    outStream.println(".");
    System.out.println(">>>.");
    state = TEXT;
   }else state=FINISHED;
   break;
  case TEXT:
    if(gotResponse(250,line)){
      outStream.println("QUIT");
      System.out.println(">>>QUIT");
      state = QUIT;
    }else state=FINISHED;
     break;
    case QUIT:
      state=FINISHED;
      break;
     }
    }
   } while(state != FINISHED);
  } catch(IOException ex) {
    error("IO Exception while sending mail");
  }
 }
 boolean gotResponse(int n,String s) {
  try {
  int responseCode = Integer.valueOf(s.trim().substring(0,3)).intValue();
  String line = s;
  while(line.charAt(3) == '-') {
    line = inStream.readLine();
    System.out.println(line);
   }
   if(responseCode == n) return true;
  }catch(NumberFormatException ex) {
  }catch(IOException ex){
  }
  return false;
 }
 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);
 }
}


The MailClientApp program prompts you for the from: name that you want associated with the sent message. SMTP is inherently insecure and will allow you to send e-mail using the e-mail address of another person as the from: address. In the example, I send a message using my daughter's e-mail address to myself. The subject of the message is Test Message and it contains a mere two lines of text. The following output shows a sample dialog with the MailClientApp program:

C:\java\jdg\ch26>java MailClientApp
From: emily@jaworski.com
To: jamie@jaworski.com
Subject: Test Message
Enter message text.
Terminate message text with an initial period.
This is a test.
It is only a test.
.
End of message read.
Connected to jaworski.com at port 25.
220-jaworski.com Sendmail 8.6.12/8.6.9 ready at Wed, 10 Apr 1996 00:33:31 -0700
220 ESMTP spoken here
>>>HELO athome.jaworski.com
250 jaworski.com Hello athome.jaworski.com [204.212.153.194], pleased to meet you
>>>MAIL FROM:<emily@jaworski.com>
250 <emily@jaworski.com>... Sender ok
>>>RCPT TO:<jamie@jaworski.com>
250 <jamie@jaworski.com>... Recipient ok
>>>DATA
354 Enter mail, end with "." on a line by itself
Subject: Test Message

>>>This is a test.
>>>It is only a test.
>>>.
250 AAA02243 Message accepted for delivery
>>>QUIT
221 jaworski.com closing connection

C:\java\jdg\ch26>

After the message is received by the e-mail client, it connects to my SMTP server and sends the message using the SMTP commands summarized earlier in this chapter.

The >>> arrows indicate commands that were sent by the program.

The Web Fetcher Program

Web browsers are the most popular client programs found on the Internet. They allow users to download and display Web pages, usually one at a time. The program shown in Listing 26.6 allows the user to specify a list of Web pages to be retrieved, and retrieves these Web pages and stores them on the local file system. This is an example of how custom Web clients can be implemented in Java.


Listing 26.6. The source code for the WebFetchApp program.

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

public class WebFetchApp {
  public static void main(String args[]){
   WebFetch fetch = new WebFetch();
   fetch.run();
  }
 }

class WebFetch {
  String urlList = "url-list.txt";
  Vector URLs = new Vector();
  Vector fileNames = new Vector();
  public WebFetch() {
   super();
 }
 public void getURLList() {
  try {
    DataInputStream inStream = new DataInputStream(new FileInputStream(urlList));
    String inLine;
    while((inLine = inStream.readLine()) != null) {
     inLine = inLine.trim();
     if(!inLine.equals("")) {
      int tabPos = inLine.lastIndexOf('\t');
      String url = inLine.substring(0,tabPos).trim();
      String fileName = inLine.substring(tabPos+1).trim();
      URLs.addElement(url);
      fileNames.addElement(fileName);
    }
   }
  }catch(IOException ex){
  error("Error reading "+urlList);
 }
}
public void run() {
  getURLList();
  int numURLs = URLs.size();
  for(int i=0;i<numURLs;++i)
  fetchURL((String) URLs.elementAt(i),(String) fileNames.elementAt(i));
 System.out.println("Done.");
}
public void fetchURL(String urlName,String fileName) {
  try{
    URL url = new URL(urlName);
    System.out.println("Getting "+urlName+"...");
    File outFile = new File(fileName);
    PrintStream outStream = new PrintStream(new FileOutputStream(outFile));
    DataInputStream inStream = new DataInputStream(url.openStream());
    String line;
    while ((line = inStream.readLine())!= null) outStream.println(line);
    inStream.close();
    outStream.close();
  }catch (MalformedURLException ex){
   System.out.println("Bad URL");
  }catch (IOException ex){
   System.out.println("IOException occurred.");
 }
}
public void error(String s){
  System.out.println(s);
  System.exit(1);
 }
}


To use the program, create a file named url-list.txt that contains the names of the URLs you want to retrieve and the names of the files in which you want them stored. The following url-list.txt file was used to retrieve some pretty famous Web pages; it is included on the CD, in the \jdg\ch26 directory:

http://www.yahoo.com yahoo.htm
http://www.cnn.com cnn.htm
http://home.netscape.com netscape.htm

The output generated for the WebFetchApp program is as follows:

C:\java\jdg\ch26>java WebFetchApp
Getting http://www.yahoo.com...
Getting http://www.cnn.com...
Getting http://home.netscape.com...
Done.

C:\java\jdg\ch26>

Summary

In this chapter you have learned how to write client programs that implement the client end of Internet client/server applications. You have learned about the common client programs found on the Internet and how they are structured. You have developed a simple telnet client, an e-mail program, and the Web fetcher program. In Chapter 27, "Server Programs," you'll learn how to write simple server applications.