Java 1.2 Unleashed

Previous chapterNext chapterContents


- 31 -

Client Programs


In this chapter you'll 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 fetch a list of Web pages. This chapter builds on the material presented in Chapter 30, "Network Programming with the java.net Package."

Types of Clients

Of the client/server applications that are found on the Internet, only a small group is typically used. These include email, the Web, FTP, Usenet newsgroups, 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 email programs, Web browsers, FTP programs, news reader programs, and Telnet clients.

Some client programs, such as Netscape Communicator, consist of an integrated suite of popular programs. For example, Netscape Communicator includes a Web browser, a mail client, and a news reader, among other clients.

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 in to 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 network virtual terminal, negotiated options, and the symmetry between terminals and processes.

The Network Virtual Terminal

The network virtual terminal (NVT) is a 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 used to send a control code or to negotiate an option from a client or server to a program on the other end of a Telnet connection, 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 the program on the other side of the connection to 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've probably 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. In addition, many operating systems supply Telnet client programs. The source code of the TelnetApp program is shown in Listing 31.1.

LISTING 31.1. THE SOURCE CODE FOR THE TelnetApp PROGRAM.

import java.lang.*;

import java.net.*;

import java.io.*;

import ju.ch31.NVTInputStream;

import ju.ch31.NVTOutputStream;

import ju.ch31.NVTPrinter;

public class TelnetApp {

 public static void main(String args[]){

  PortTalk portTalk = new PortTalk(args);

  portTalk.start();

 }

}

class PortTalk extends Thread {

 Socket connection;

 OutputStream 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 = 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;

   BufferedReader userInputStream = new BufferedReader(

    new InputStreamReader(System.in));

   do {

    line = userInputStream.readLine();

    if(line == null) finished = true;

    else{

     try {

      for(int i=0;i<line.length();++i)

       outStream.write(line.charAt(i));

      outStream.write(`\n');

     } catch (IOException ex) {

     }

    }

   } 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. These files must be placed in the ju\ch31 directory, since they are part of the ju.ch31 package.

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 host name 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 in to 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. In addition, like other Telnet client programs, it sends unencrypted passwords to the system to which a Telnet connection is being made.

Notice that commands that I type were echoed by my cts.com host:

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: Sun Dec 28 22:51:04 PST 1997 on ttyp6

Last unsuccessful login for jaworski: Mon Nov 17 02:57:41 PST 1997 on ttyp5

                            Welcome to CTSNET!

                Enter `help' for assistance and information.

TERM = (vt100)

 Terminal type is vt100

1% l

l

total 426

-rw-rw-r--   1 jaworski guest       4216 Dec 10 21:40 HIST_S90.TXT

drwx------   2 jaworski guest        272 Sep 08  1995 Mail

drwxr-xr-x   2 jaworski guest        208 Dec 07  1995 News

drwxr-xr-x   2 jaworski guest        224 Sep 08  1995 bin

-rw-------   1 jaworski guest      47682 Oct 06 17:30 cute.jpg

drwxr-xr-x   2 jaworski guest        384 Apr 04  1996 download

lrwxrwxrwx   1 root     root          15 Mar 15  1996 dropbox -> /ftp/j/jaworski

drwxrwxr-x   2 jaworski guest        128 Dec 14 19:15 finished

drwx------   2 jaworski guest        160 Dec 08  1995 ga

drwxrwxr-x   2 jaworski guest        144 Dec 08 13:12 java_unleashed

drwx------   2 jaworski guest        288 Nov 11 01:30 mail

drwx------   2 jaworski guest        720 Nov 07 13:42 temp

-rw-------   1 jaworski guest         18 Oct 11 19:28 test

-rw-rw-r--   1 jaworski guest     150528 Nov 25 11:30 whmc.doc

drwxr-xr-x   3 jaworski guest        112 Dec 01  1995 writing

2% exit

exit

3% logout

Connection broken.

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 31.2.

LISTING 31.2. THE SOURCE CODE FOR THE NVTPrinter CLASS.

package ju.ch31;

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 NVT input interface. Its source code is shown in Listing 31.3.

LISTING 31.3. THE SOURCE CODE FOR THE NVTInputStream CLASS.

package ju.ch31;

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,numBytes-1);

    }

   }

   lineBuffer[numBytes] = b;

   ++numBytes;

  } while (!finished);

  return null;

 }

}

NVTInputStream uses the NVT 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 31.4.

LISTING 31.4. THE SOURCE CODE FOR THE NVTOutputStream CLASS.

package ju.ch31;

import java.io.*;

public class NVTOutputStream extends FilterOutputStream {

 int IAC = 255;

 byte CR = 13;

 byte LF = 10;

 public NVTOutputStream(OutputStream outStream) {

  super(outStream);

 }

 public void write(int i) throws IOException {

  if(i == IAC) super.write(i);

  super.write(i);

 }

 public void println(String s) {

  try {

   byte[] sBytes = s.getBytes();

   for(int i=0;i<sBytes.length;++i)

    super.write(sBytes[i]);

   super.write(CR);

   super.write(LF);

   super.flush();

  } catch(IOException ex) {

  }

 }

}

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 time. The program shown in Listing 31.5 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 31.5. 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 {

   BufferedReader inStream = new BufferedReader(new FileReader(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);

   PrintWriter outStream = new PrintWriter(new FileWriter(outFile));

   BufferedReader inStream = new BufferedReader(

    new InputStreamReader(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-ROM that comes with this book, in the \ju\ch31 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 was as follows:

java WebFetchApp

Getting http://www.yahoo.com...

Getting http://www.cnn.com...

Getting http://home.netscape.com...

Done.

Note that only the HTML file associated with each Web site is retrieved. Supporting graphics files are not downloaded unless they are identified in url-list.txt.

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 and the Web fetcher program. In Chapter 32, "Server Programs," you'll learn how to write simple server applications.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.