The Java Platform

The Java Platform

Threads

Java makes it easy to define and work with multiple threads of execution within a program. java.lang.Thread is the fundamental thread class in the Java API. There are two ways to define a thread. One is to subclass Thread, override the run() method, and then instantiate your Thread subclass. The other is to define a class that implements the Runnable method (i.e., define a run() method) and then pass an instance of this Runnable object to the Thread() constructor. In either case, the result is a Thread object, where the run() method is the body of the thread. When you call the start() method of the Thread object, the interpreter creates a new thread to execute the run() method. This new thread continues to run until the run() method exits, at which point it ceases to exist. Meanwhile, the original thread continues running itself, starting with the statement following the start() method. The following code demonstrates:



final List list;  // Some long unsorted list of objects; initialized elsewhere



/** A Thread class for sorting a List in the background */

class BackgroundSorter extends Thread {

  List l;

  public BackgroundSorter(List l) { this.l = l; }    // Constructor

  public void run() { Collections.sort(l); }         // Thread body

}



// Create a BackgroundSorter thread 

Thread sorter = new BackgroundSorter(list);

// Start it running; the new thread runs the run() method above, while 

// the original thread continues with whatever statement comes next. 

sorter.start();  



// Here's another way to define a similar thread

Thread t = new Thread(new Runnable() {          // Create a new thread

  public void run() { Collections.sort(list); } // to sort the list of objects.

});

t.start();                                      // Start it running

Thread Priorities

Threads can run at different priority levels. A thread at a given priority level does not typically run unless there are no higher-priority threads waiting to run. Here is some code you can use when working with thread priorities:



// Set a thread t to lower-than-normal priority

t.setPriority(Thread.NORM_PRIORITY-1);



// Set a thread to lower priority than the current thread

t.setPriority(Thread.currentThread().getPriority() - 1);



// Threads that don't pause for I/O should explicitly yield the CPU 

// to give other threads with the same priority a chance to run. 

Thread t = new Thread(new Runnable() {

  public void run() {

    for(int i = 0; i < data.length; i++) {  // Loop through a bunch of data

      process(data[i]);                     // Process it

      if ((i % 10) == 0)                    // But after every 10 iterations,

        Thread.yield();                     // pause to let other threads run. 

    }

  }

});

Making a Thread Sleep

Often, threads are used to perform some kind of repetitive task at a fixed interval. This is particularly true when doing graphical programming that involves animation or similar effects. The key to doing this is making a thread sleep, or stop running for a specified amount of time. This is done with the static Thread.sleep() method:



public class Clock extends Thread {

  java.text.DateFormat f =      // How to format the time for this locale

    java.text.DateFormat.getTimeInstance(java.text.DateFormat.MEDIUM);

  volatile boolean keepRunning = true;



  public Clock() {         // The constructor

    setDaemon(true);       // Daemon thread: interpreter can exit while it runs

    start();               // This thread starts itself

  }



  public void run() {      // The body of the thread

    while(keepRunning) {   // This thread runs until asked to stop

      String time = f.format(new java.util.Date()); // Current time

      System.out.println(time);                     // Print the time

      try { Thread.sleep(1000); }                   // Wait 1,000 milliseconds

      catch (InterruptedException e) {}             // Ignore this exception 

    }

  }



  // Ask the thread to stop running

  public void pleaseStop() { keepRunning = false; }

}

Notice the pleaseStop() method in this example. You can forcefully terminate a thread by calling its stop() method, but this method has been deprecated because a thread that is forcefully stopped can leave objects it is manipulating in an inconsistent state. If you need a thread that can be stopped, you should define a method such as pleaseStop() that stops the thread in a controlled way.

Timers

In Java 1.3, the java.util.Timer and java.util.TimerTask classes make it even easier to run repetitive tasks. Here is some code that behaves much like the previous Clock class:



import java.util.*;



// How to format the time for this locale

final java.text.DateFormat timeFmt =         

  java.text.DateFormat.getTimeInstance(java.text.DateFormat.MEDIUM);

// Define the time-display task

TimerTask displayTime = new TimerTask() {

  public void run() { System.out.println(timeFmt.format(new Date())); }

};

// Create a timer object to run the task (and possibly others)

Timer timer = new Timer();

// Now schedule that task to be run every 1,000 milliseconds, starting now

Timer.schedule(displayTime, 0, 1000);



// To stop the time-display task

displayTime.cancel();

Waiting for a Thread to Finish

Sometimes one thread needs to stop and wait for another thread to complete. You can accomplish this with the join() method:



List list;  // A long list of objects to be sorted; initialized elsewhere



// Define a thread to sort the list: lower its priority, so it runs only 

// when the current thread is waiting for I/O, and then start it running. 

Thread sorter = new BackgroundSorter(list);               // Defined earlier

sorter.setPriority(Thread.currentThread.getPriority()-1); // Lower priority

sorter.start();                                           // Start sorting



// Meanwhile, in this original thread, read data from a file

byte[] data = readData();  // Method defined elsewhere



// Before we can proceed, we need the list to be fully sorted, so

// we must wait for the sorter thread to exit, if it hasn't already. 

try { sorter.join(); } catch(InterruptedException e) {}

Thread Synchronization

When using multiple threads, you must be very careful if you allow more than one thread to access the same data structure. Consider what would happen if one thread was trying to loop through the elements of a List while another thread was sorting those elements. Preventing this problem is called thread synchronization and is one of the central problems of multithreaded computing. The basic technique for preventing two threads from accessing the same object at the same time is to require a thread to obtain a lock on the object before the thread can modify it. While any one thread holds the lock, another thread that requests the lock has to wait until the first thread is done and releases the lock. Every Java object has the fundamental ability to provide such a locking capability.

The easiest way to keep objects thread-safe is to declare all sensitive methods synchronized. A thread must obtain a lock on an object before it can execute any of its synchronized methods, which means that no other thread can execute any other synchronized method at the same time. (If a static method is declared synchronized, the thread must obtain a lock on the class, and this works in the same manner.) To do finer-grained locking, you can specify synchronized blocks of code that hold a lock on a specified object for a short time:



// This method swaps two array elements in a synchronized block

public static void swap(Object[] array, int index1, int index2) {

  synchronized(array) {

    Object tmp = array[index1];

    array[index1] = array[index2];

    array[index2] = tmp;

  }

}



// The Collection, Set, List, and Map implementations in java.util do

// not have synchronized methods (except for the legacy implementations

// Vector and Hashtable). When working with multiple threads, you can

// obtain synchronized wrapper objects. 

List synclist = Collections.synchronizedList(list);

Map syncmap = Collections.synchronizedMap(map);

Deadlock

When you are synchronizing threads, you must be careful to avoid deadlock, which occurs when two threads end up waiting for each other to release a lock they need. Since neither can proceed, neither one can release the lock it holds, and they both stop running:



// When two threads try to lock two objects, deadlock can occur unless

// they always request the locks in the same order.

final Object resource1 = new Object();   // Here are two objects to lock

final Object resource2 = new Object();

Thread t1 = new Thread(new Runnable() {  // Locks resource1 then resource2

  public void run() {

    synchronized(resource1) { 

      synchronized(resource2) { compute(); }

    }

  }

});



Thread t2 = new Thread(new Runnable() {  // Locks resource2 then resource1

  public void run() {

    synchronized(resource2) { 

      synchronized(resource1) { compute(); }

    }

  }

});



t1.start();  // Locks resource1

t2.start();  // Locks resource2 and now neither thread can progress!

Coordinating Threads with wait( ) and notify( )

Sometimes a thread needs to stop running and wait until some kind of event occurs, at which point it is told to continue running. This is done with the wait() and notify() methods. These aren't methods of the Thread class, however; they are methods of Object. Just as every Java object has a lock associated with it, every object can maintain a list of waiting threads. When a thread calls the wait() method of an object, any locks the thread holds are temporarily released, and the thread is added to the list of waiting threads for that object and stops running. When another thread calls the notify() method of the same object, the object wakes up one of the waiting threads and allows it to continue running:



/** 

 * A queue. One thread calls push() to put an object on the queue. 

 * Another calls pop() to get an object off the queue. If there is no

 * data, pop() waits until there is some, using wait()/notify(). 

 * wait() and notify() must be used within a synchronized method or

 * block. 

 */

import java.util.*;



public class Queue {

  LinkedList q = new LinkedList();  // Where objects are stored

  public synchronized void push(Object o) {

    q.add(o);      // Append the object to the end of the list

    this.notify(); // Tell waiting threads that data is ready

  }

  public synchronized Object pop() {

    while(q.size() == 0) {

      try { this.wait(); }

      catch (InterruptedException e) { /* Ignore this exception */ }

    }

    return q.remove(0);

  }

}

Thread Interruption

In the examples illustrating the sleep(), join(), and wait() methods, you may have noticed that calls to each of these methods are wrapped in a try statement that catches an InterruptedException. This is necessary because the interrupt() method allows one thread to interrupt the execution of another. Interrupting a thread is not intended to stop it from executing, but to wake it up from a blocking state.

If the interrupt() method is called on a thread that is not blocked, the thread continues running, but its "interrupt status" is set to indicate that an interrupt has been requested. A thread can test its own interrupt status by calling the static Thread.interrupted() method, which returns true if the thread has been interrupted and, as a side effect, clears the interrupt status. One thread can test the interrupt status of another thread with the instance method isInterrupted(), which queries the status but does not clear it.

If a thread calls sleep(), join(), or wait() while its interrupt status is set, it does not block but immediately throws an InterruptedException (the interrupt status is cleared as a side effect of throwing the exception). Similarly, if the interrupt() method is called on a thread that is already blocked in a call to sleep(), join(), or wait(), that thread stops blocking by throwing an InterruptedException.

One of the most common times that threads block is while doing input/output; a thread often has to pause and wait for data to become available from the filesystem or from the network. (The java.io, java.net, and java.nio APIs for performing I/O operations are discussed later in this chapter.) Unfortunately, the interrupt() method does not wake up a thread blocked in an I/O method of the java.io package. This is one of the shortcomings of java.io that is cured by the New I/O API in java.nio. If a thread is interrupted while blocked in an I/O operation on any channel that implements java.nio.channels.InterruptibleChannel, the channel is closed, the thread's interrupt status is set, and the thread wakes up by throwing a java.nio.channels.ClosedByInterruptException. The same thing happens if a thread tries to call a blocking I/O method while its interrupt status is set. Similarly, if a thread is interrupted while it is blocked in the select() method of a java.nio.channels.Selector (or if it calls select() while its interrupt status is set), select() will stop blocking (or will never start) and will return immediately. No exception is thrown in this case; the interrupted thread simply wakes up, and the select() call returns.

Files and Directories

The java.io.File class represents a file or a directory and defines a number of important methods for manipulating files and directories. Note, however, that none of these methods allow you to read the contents of a file; that is the job of java.io.FileInputStream, which is just one of the many types of I/O streams used in Java and discussed in the next section. Here are some things you can do with File:



import java.io.*;

import java.util.*;



// Get the name of the user's home directory and represent it with a File

File homedir = new File(System.getProperty("user.home"));

// Create a File object to represent a file in that directory

File f = new File(homedir, ".configfile");



// Find out how big a file is and when it was last modified

long filelength = f.length();

Date lastModified = new java.util.Date(f.lastModified());



// If the file exists, is not a directory, and is readable, 

// move it into a newly created directory.

if (f.exists() && f.isFile() && f.canRead()) {       // Check config file

  File configdir = new File(homedir, ".configdir");  // A new config directory

  configdir.mkdir();                                 // Create that directory

  f.renameTo(new File(configdir, ".config"));        // Move the file into it

}



// List all files in the home directory

String[] allfiles = homedir.list();



// List all files that have a ".java" suffix

String[] sourcecode = homedir.list(new FilenameFilter() {

  public boolean accept(File d, String name) { return name.endsWith(".java"); }

});

The File class provides some important additional functionality as of Java 1.2:



// List all filesystem root directories; on Windows, this gives us

// File objects for all drive letters (Java 1.2 and later). 

File[] rootdirs = File.listRoots();



// Atomically, create a lock file, then delete it (Java 1.2 and later)

File lock = new File(configdir, ".lock");

if (lock.createNewFile()) {

  // We successfully created the file, so do something

  ... 

  // Then delete the lock file

  lock.delete();

}

else {

  // We didn't create the file; someone else has a lock

  System.err.println("Can't create lock file; exiting.");

  System.exit(1);

}



// Create a temporary file to use during processing (Java 1.2 and later)

File temp = File.createTempFile("app", ".tmp");  // Filename prefix and suffix



// Make sure file gets deleted when we're done with it (Java 1.2 and later)

temp.deleteOnExit();

RandomAccessFile

The java.io package also defines a RandomAccessFile class that allows you to read binary data from arbitrary locations in a file. This can be a useful thing to do in certain situations, but most applications read files sequentially, using the stream classes described in the next section. Here is a short example of using Random-AccessFile:



// Open a file for read/write ("rw") access

File datafile = new File(configdir, "datafile");

RandomAccessFile f = new RandomAccessFile(datafile, "rw");  

f.seek(100);                   // Move to byte 100 of the file

byte[] data = new byte[100];   // Create a buffer to hold data

f.read(data);                  // Read 100 bytes from the file

int i = f.readInt();           // Read a 4-byte integer from the file

f.seek(100);                   // Move back to byte 100

f.writeInt(i);                 // Write the integer first

f.write(data);                 // Then write the 100 bytes

f.close();                     // Close file when done with it

Input and Output Streams

The java.io package defines a large number of classes for reading and writing streaming, or sequential, data. The InputStream and OutputStream classes are for reading and writing streams of bytes, while the Reader and Writer classes are for reading and writing streams of characters. Streams can be nested, meaning you might read characters from a FilterReader object that reads and processes characters from an underlying Reader stream. This underlying Reader stream might read bytes from an InputStream and convert them to characters.

Reading Console Input

There are a number of common operations you can perform with streams. One is to read lines of input the user types at the console:



import java.io.*;



BufferedReader console = new BufferedReader(new InputStreamReader(System.in));

System.out.print("What is your name: ");

String name = null;

try { 

  name = console.readLine(); 

} 

catch (IOException e) { name = "<" + e + ">"; }  // This should never happen

System.out.println("Hello " + name);

Reading Lines from a Text File

Reading lines of text from a file is a similar operation. The following code reads an entire text file and quits when it reaches the end:



String filename = System.getProperty("user.home") + File.separator + ".cshrc";

try {

  BufferedReader in = new BufferedReader(new FileReader(filename));

  String line;

  while((line = in.readLine()) != null) {  // Read line, check for end-of-file

    System.out.println(line);              // Print the line

  }

  in.close();    // Always close a stream when you are done with it

}

catch (IOException e) {

  // Handle FileNotFoundException, etc. here

}

Writing Text to a File

Throughout this book, you've seen the use of the System.out.println() method to display text on the console. System.out simply refers to an output stream. You can print text to any output stream using similar techniques. The following code shows how to output text to a file:



try {

  File f = new File(homedir, ".config");

  PrintWriter out = new PrintWriter(new FileWriter(f));

  out.println("## Automatically generated config file. DO NOT EDIT!");

  out.close();  // We're done writing

}

catch (IOException e) { /* Handle exceptions */ }

Reading a Binary File

Not all files contain text, however. The following lines of code treat a file as a stream of bytes and read the bytes into a large array:



try {

  File f;                             // File to read; initialized elsewhere

  int filesize = (int) f.length();    // Figure out the file size

  byte[] data = new byte[filesize];   // Create an array that is big enough

  // Create a stream to read the file

  DataInputStream in = new DataInputStream(new FileInputStream(f));

  in.readFully(data);  // Read file contents into array

  in.close();

}

catch (IOException e) { /* Handle exceptions */ }

Compressing Data

Various other packages of the Java platform define specialized stream classes that operate on streaming data in some useful way. The following code shows how to use stream classes from java.util.zip to compute a checksum of data and then compress the data while writing it to a file:



import java.io.*;

import java.util.zip.*;



try {

  File f;                           // File to write to; initialized elsewhere

  byte[] data;                      // Data to write; initialized elsewhere

  Checksum check = new Adler32();   // An object to compute a simple checksum



  // Create a stream that writes bytes to the file f

  FileOutputStream fos = new FileOutputStream(f);

  // Create a stream that compresses bytes and writes them to fos

  GZIPOutputStream gzos = new GZIPOutputStream(fos);

  // Create a stream that computes a checksum on the bytes it writes to gzos

  CheckedOutputStream cos = new CheckedOutputStream(gzos, check);  



  cos.write(data);             // Now write the data to the nested streams

  cos.close();                 // Close down the nested chain of streams

  long sum = check.getValue(); // Obtain the computed checksum

}

catch (IOException e) { /* Handle exceptions */ }

Reading ZIP Files

The java.util.zip package also contains a ZipFile class that gives you random access to the entries of a ZIP archive and allows you to read those entries through a stream:



import java.io.*;

import java.util.zip.*;



String filename;     // File to read; initialized elsewhere

String entryname;    // Entry to read from the ZIP file; initialized elsewhere

ZipFile zipfile = new ZipFile(filename);        // Open the ZIP file

ZipEntry entry = zipfile.getEntry(entryname);   // Get one entry

InputStream in = zipfile.getInputStream(entry); // A stream to read the entry

BufferedInputStream bis = new BufferedInputStream(in);  // Improves efficiency

// Now read bytes from bis... 

// Print out contents of the ZIP file

for(java.util.Enumeration e = zipfile.entries(); e.hasMoreElements();) {

  ZipEntry zipentry = (ZipEntry) e.nextElement();

  System.out.println(zipentry.getName());

}

Computing Message Digests

If you need to compute a cryptographic-strength checksum (also knows as a message digest), use one of the stream classes of the java.security package. For example:



import java.io.*;

import java.security.*;

import java.util.*;





File f;          // File to read and compute digest on; initialized elsewhere

List text = new ArrayList();  // We'll store the lines of text here



// Get an object that can compute an SHA message digest

MessageDigest digester = MessageDigest.getInstance("SHA");

// A stream to read bytes from the file f

FileInputStream fis = new FileInputStream(f);

// A stream that reads bytes from fis and computes an SHA message digest

DigestInputStream dis = new DigestInputStream(fis, digester);

// A stream that reads bytes from dis and converts them to characters

InputStreamReader isr = new InputStreamReader(dis);

// A stream that can read a line at a time

BufferedReader br = new BufferedReader(isr);

// Now read lines from the stream

for(String line; (line = br.readLine()) != null; text.add(line)) ;

// Close the streams

br.close();

// Get the message digest

byte[] digest = digester.digest();

Streaming Data to and from Arrays

So far, we've used a variety of stream classes to manipulate streaming data, but the data itself ultimately comes from a file or is written to the console. The java.io package defines other stream classes that can read data from and write data to arrays of bytes or strings of text:



import java.io.*;



// Set up a stream that uses a byte array as its destination

ByteArrayOutputStream baos = new ByteArrayOutputStream();

DataOutputStream out = new DataOutputStream(baos);

out.writeUTF("hello");            // Write some string data out as bytes

out.writeDouble(Math.PI);         // Write a floating-point value out as bytes

byte[] data = baos.toByteArray(); // Get the array of bytes we've written

out.close();                      // Close the streams



// Set up a stream to read characters from a string

Reader in = new StringReader("Now is the time!");

// Read characters from it until we reach the end

int c;

while((c = in.read()) != -1) System.out.print((char) c);

Other classes that operate this way include ByteArrayInputStream, StringWriter, CharArrayReader, and CharArrayWriter.

Thread Communication with Pipes

PipedInputStream and PipedOutputStream and their character-based counterparts, PipedReader and PipedWriter, are another interesting set of streams defined by java.io. These streams are used in pairs by two threads that want to communicate. One thread writes bytes to a PipedOutputStream or characters to a PipedWriter, and another thread reads bytes or characters from the corresponding PipedInputStream or PipedReader:



// A pair of connected piped I/O streams forms a pipe. One thread writes

// bytes to the PipedOutputStream, and another thread reads them from the

// corresponding PipedInputStream. Or use PipedWriter/PipedReader for chars. 

final PipedOutputStream writeEndOfPipe = new PipedOutputStream();

final PipedInputStream readEndOfPipe = new PipedInputStream(writeEndOfPipe);



// This thread reads bytes from the pipe and discards them

Thread devnull = new Thread(new Runnable() {

  public void run() { 

    try { while(readEndOfPipe.read() != -1); }

    catch (IOException e) {}  // ignore it

  }

});

devnull.start();

Serialization

One of the most important features of the java.io package is the ability to serialize objects: to convert an object into a stream of bytes that can later be deserialized back into a copy of the original object. The following code shows how to use serialization to save an object to a file and later read it back:



Object o;  // The object we are serializing; it must implement Serializable

File f;    // The file we are saving it to



try {

  // Serialize the object

  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));

  oos.writeObject(o);

  oos.close();



  // Read the object back in

  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));

  Object copy = ois.readObject();

  ois.close();

} 

catch (IOException e) { /* Handle input/output exceptions */ }

catch (ClassNotFoundException cnfe) { /* readObject() can throw this */ }

The previous example serializes to a file, but remember, you can write serialized objects to any type of stream. Thus, you can write an object to a byte array, then read it back from the byte array, creating a deep copy of the object. You can write the object's bytes to a compression stream or even write the bytes to a stream connected across a network to another program!

JavaBeans Persistence

Java 1.4 introduces a new serialization mechanism intended for use with JavaBeans components. java.io serialization works by saving the state of the internal fields of an object. java.beans persistence, on the other hand, works by saving a bean's state as a sequence of calls to the public methods defined by the class. Since it is based on the public API rather than on the internal state, the JavaBeans persistence mechanism allows interoperability between different implementations of the same API, handles version skew more robustly, and is suitable for longer-term storage of serialized objects.

A bean and any descendant beans or other objects serialized with java.beans.XMLEncoder and can be deserialized with java.beans.XMLDecoder. These classes write to and read from specified streams, but they are not stream classes themselves. Here is how you might encode a bean:



// Create a JavaBean, and set some properties on it

javax.swing.JFrame bean = new javax.swing.JFrame("PersistBean");

bean.setSize(300, 300);

// Now save its encoded form to the file bean.xml

BufferedOutputStream out =                 // Create an output stream

    new BufferedOutputStream(new FileOutputStream("bean.xml"));

XMLEncoder encoder = new XMLEncoder(out);  // Create encoder for stream

encoder.writeObject(bean);                 // Encode the bean

encoder.close();                           // Close encoder and stream

Here is the corresponding code to decode the bean from its serialized form:


BufferedInputStream in =                   // Create input stream

    new BufferedInputStream(new FileInputStream("bean.xml"));

XMLDecoder decoder = new XMLDecoder(in);   // Create decoder for stream

Object b = decoder.readObject();           // Decode a bean

decoder.close();                           // Close decoder and stream

bean = (javax.swing.JFrame) b;             // Cast bean to proper type

bean.setVisible(true);                     // Start using it

Networking

The java.net package defines a number of classes that make writing networked applications surprisingly easy. Various examples follow.

Networking with the URL Class

The easiest networking class to use is URL, which represents a uniform resource locator. Different Java implementations may support different sets of URL protocols, but, at a minimum, you can rely on support for the http://, ftp://, and file:// protocols. In Java 1.4, secure HTTP is also supported with the https:// protocol. Here are some ways you can use the URL class:



import java.net.*;  

import java.io.*;   



// Create some URL objects 

URL url=null, url2=null, url3=null;

try {

  url = new URL("http://www.oreilly.com");        // An absolute URL

  url2 = new URL(url, "catalog/books/javanut4/"); // A relative URL

  url3 = new URL("http:", "www.oreilly.com", "index.html");

} catch (MalformedURLException e) { /* Ignore this exception */ }



// Read the content of a URL from an input stream

InputStream in = url.openStream();



// For more control over the reading process, get a URLConnection object

URLConnection conn = url.openConnection();



// Now get some information about the URL

String type = conn.getContentType();

String encoding = conn.getContentEncoding();

java.util.Date lastModified = new java.util.Date(conn.getLastModified());

int len = conn.getContentLength();



// If necessary, read the contents of the URL using this stream

InputStream in = conn.getInputStream();

Working with Sockets

Sometimes you need more control over your networked application than is possible with the URL class. In this case, you can use a Socket to communicate directly with a server. For example:



import java.net.*;

import java.io.*;



// Here's a simple client program that connects to a web server, 

// requests a document, and reads the document from the server.

String hostname = "java.oreilly.com";  // The server to connect to

int port = 80;                         // Standard port for HTTP 

String filename = "/index.html";       // The file to read from the server

Socket s = new Socket(hostname, port); // Connect to the server



// Get I/O streams we can use to talk to the server

InputStream sin = s.getInputStream();

BufferedReader fromServer = new BufferedReader(new InputStreamReader(sin));

OutputStream sout = s.getOutputStream();

PrintWriter toServer = new PrintWriter(new OutputStreamWriter(sout));



// Request the file from the server, using the HTTP protocol

toServer.print("GET " + filename + " HTTP/1.0\r\n\r\n");

toServer.flush();



// Now read the server's response, assume it is a text file, and print it out

for(String l = null; (l = fromServer.readLine()) != null; )

  System.out.println(l);



// Close everything down when we're done

toServer.close();

fromServer.close();

s.close();

Secure Sockets with SSL

In Java 1.4, the Java Secure Socket Extension, or JSSE, has been added to the core Java platform in the packages javax.net and javax.net.ssl.[1] This API enables encrypted network communication over sockets that use the SSL (Secure Sockets Layer, also known as TLS) protocol. SSL is widely used on the Internet: it is the basis for secure web communication using the https:// protocol. In Java 1.4 and later, you can use https:// with the URL class as previously shown to securely download documents from web servers that support SSL.

[1] An earlier version of JSSE using different package names is available as a separate download for use with Java 1.2 and Java 1.3. See http://java.sun.com/products/jsse/.

Like all Java security APIs, JSSE is highly configurable and gives low-level control over all details of setting up and communicating over an SSL socket. The javax.net and javax.net.ssl packages are fairly complex, but in practice, there are only a few classes you need to use to securely communicate with a server. The following program is a variant on the preceding code that uses HTTPS instead of HTTP to securely transfer the contents of the requested URL:



import java.io.*;

import java.net.*;

import javax.net.ssl.*;

import java.security.cert.*;



/**

 * Get a document from a web server using HTTPS. Usage:

 *   java HttpsDownload <hostname> <filename>

 **/

public class HttpsDownload {

    public static void main(String[] args) throws IOException {

	// Get a SocketFactory object for creating SSL sockets

	SSLSocketFactory factory =

	    (SSLSocketFactory) SSLSocketFactory.getDefault();



	// Use the factory to create a secure socket connected to the 

	// HTTPS port of the specified web server.

	SSLSocket sslsock=(SSLSocket)factory.createSocket(args[0], // Hostname

                                                            443); // HTTPS port



	// Get the certificate presented by the web server

	SSLSession session = sslsock.getSession();

	X509Certificate cert;	

	try { cert = (X509Certificate)session.getPeerCertificates()[0]; }

	catch(SSLPeerUnverifiedException e) { // If no or invalid certificate

	    System.err.println(session.getPeerHost() +

			       " did not present a valid certificate.");

	    return;

	}



	// Display details about the certificate

	System.out.println(session.getPeerHost() + 

			   " has presented a certificate belonging to:");

	System.out.println("\t[" + cert.getSubjectDN().getName() + "]");

	System.out.println("The certificate bears the valid signature of:");

	System.out.println("\t[" + cert.getIssuerDN().getName() + "]");



	// If the user does not trust the certificate, abort

	System.out.print("Do you trust this certificate (y/n)? ");

	System.out.flush();

	BufferedReader console =

	    new BufferedReader(new InputStreamReader(System.in));

	if (Character.toLowerCase(console.readLine().charAt(0)) != 'y') return;



	// Now use the secure socket just as you would use a regular socket

	// First, send a regular HTTP request over the SSL socket

	PrintWriter out = new PrintWriter(sslsock.getOutputStream());



	out.print("GET " + args[1] + " HTTP/1.0\r\n\r\n");

	out.flush();



	// Next, read the server's response and print it to the console

	BufferedReader in = 

	 new BufferedReader(new InputStreamReader(sslsock.getInputStream()));

	String line;

	while((line = in.readLine()) != null) System.out.println(line);

	

	// Finally, close the socket

	sslsock.close(); 

    }

}

Servers

A client application uses a Socket to communicate with a server. The server does the same thing: it uses a Socket object to communicate with each of its clients. However, the server has an additional task, in that it must be able to recognize and accept client connection requests. This is done with the ServerSocket class. The following code shows how you might use a ServerSocket. The code implements a simple HTTP server that responds to all requests by sending back (or mirroring) the exact contents of the HTTP request. A dummy server like this is useful when debugging HTTP clients:



import java.io.*;

import java.net.*;



public class HttpMirror {

  public static void main(String[] args) {

    try {

      int port = Integer.parseInt(args[0]);        // The port to listen on

      ServerSocket ss = new ServerSocket(port);    // Create a socket to listen

      for(;;) {                                    // Loop forever

        Socket client = ss.accept();               // Wait for a connection

        ClientThread t = new ClientThread(client); // A thread to handle it

        t.start();                                 // Start the thread running

      }                                            // Loop again

    } 

    catch (Exception e) {

      System.err.println(e.getMessage());

      System.err.println("Usage: java HttpMirror <port>;");

    }

  }



  static class ClientThread extends Thread {

    Socket client;

    ClientThread(Socket client) { this.client = client; }

    public void run() {

      try {

        // Get streams to talk to the client

        BufferedReader in = 

          new BufferedReader(new InputStreamReader(client.getInputStream()));

        PrintWriter out =

          new PrintWriter(new OutputStreamWriter(client.getOutputStream()));

        

        // Send an HTTP response header to the client

        out.print("HTTP/1.0 200\r\nContent-Type: text/plain\r\n\r\n");

        

        // Read the HTTP request from the client and send it right back

        // Stop when we read the blank line from the client that marks 

        // the end of the request and its headers. 

        String line;

        while((line = in.readLine()) != null) {

          if (line.length() == 0) break;

          out.println(line);

        }

        

        out.close();

        in.close();

        client.close();

      }

      catch (IOException e) { /* Ignore exceptions */ }

    }

  }

}

This server code could be modified using JSSE to support SSL connections. Making a server secure is more complex than making a client secure, however, because a server must have a certificate it can present to the client. Therefore, server-side JSSE is not demonstrated here.

Datagrams

Both URL and Socket perform networking on top of a stream-based network connection. Setting up and maintaining a stream across a network takes work at the network level, however. Sometimes you need a low-level way to speed a packet of data across a network, but you don't care about maintaining a stream. If, in addition, you don't need a guarantee that your data will get there or that the packets of data will arrive in the order you sent them, you may be interested in the DatagramSocket and DatagramPacket classes:



import java.net.*;



// Send a message to another computer via a datagram

try {

  String hostname = "host.example.com";     // The computer to send the data to

  InetAddress address =                     // Convert the DNS hostname

    InetAddress.getByName(hostname);        // to a lower-level IP address. 

  int port = 1234;                          // The port to connect to

  String message = "The eagle has landed."; // The message to send

  byte[] data = message.getBytes();         // Convert string to bytes

  DatagramSocket s = new DatagramSocket();  // Socket to send message with

  DatagramPacket p =                        // Create the packet to send

    new DatagramPacket(data, data.length, address, port);

  s.send(p);                                // Now send it!

  s.close();                                // Always close sockets when done

}

catch (UnknownHostException e) {}  // Thrown by InetAddress.getByName()

catch (SocketException e) {}       // Thrown by new DatagramSocket()

catch (java.io.IOException e) {}   // Thrown by DatagramSocket.send()



// Here's how the other computer can receive the datagram

try {

  byte[] buffer = new byte[4096];               // Buffer to hold data

  

  DatagramSocket s = new DatagramSocket(1234);  // Socket that receives it 

                                                // through

  DatagramPacket p = 

    new DatagramPacket(buffer, buffer.length);  // The packet that receives it

  s.receive(p);                                 // Wait for a packet to arrive

  String msg =                                  // Convert the bytes from the

    new String(buffer, 0, p.getLength());       // packet back to a string. 

  s.close();                                    // Always close the socket

}

catch (SocketException e) {}       // Thrown by new DatagramSocket()

catch (java.io.IOException e) {}   // Thrown by DatagramSocket.receive()

Properties and Preferences

java.util.Properties is a subclass of java.util.Hashtable, a legacy collections class that predates the Collections API of Java 1.2. A Properties object maintains a mapping between string keys and string values and defines methods that allow the mappings to be written to and read from a simply formatted text file. This makes the Properties class ideal for configuration and user preference files. The Properties class is also used for the system properties returned by System.get- Property():



import java.util.*;

import java.io.*;



// Note: many of these system properties calls throw a security exception if

// called from untrusted code such as applets.

String homedir = System.getProperty("user.home");  // Get a system property

Properties sysprops = System.getProperties();      // Get all system properties



// Print the names of all defined system properties

for(Enumeration e = sysprops.propertyNames(); e.hasMoreElements();) 

  System.out.println(e.nextElement());



sysprops.list(System.out);  // Here's an even easier way to list the properties



// Read properties from a configuration file

Properties options = new Properties();             // Empty properties list

File configfile = new File(homedir, ".config");    // The configuration file

try {

  options.load(new FileInputStream(configfile));   // Load props from the file

} catch (IOException e) { /* Handle exception here */ }



// Query a property ("color"), specifying a default ("gray") if undefined

String color = options.getProperty("color", "gray");  



// Set a property named "color" to the value "green"

options.setProperty("color", "green");  



// Store the contents of the Properties object back into a file

try {

  options.store(new FileOutputStream(configfile),  // Output stream

                "MyApp Config File");              // File header comment text

} catch (IOException e) { /* Handle exception */ }

Preferences

Java 1.4 introduces a new Preferences API, which is specifically tailored for working with user and systemwide preferences and is more useful than Properties for this purpose. The Preferences API is defined by the java.util.prefs package. The key class in that package is Preferences. You can obtain a Preferences object that contains user-specific preferences with the static method Preferences.userNodeForPackage() and obtain a Preferences object that contains systemwide preferences with Preferences.systemNodeForPackage(). Both methods take a java.lang.Class object as their sole argument and return a Preferences object shared by all classes in that package. (This means that the preference names you use must be unique within the package.) Once you have a Preferences object, use the get() method to query the string value of a named preference, or use other type-specific methods such as getInt(), getBoolean(), and getByteArray(). Note that to query preference values, a default value must be passed for all methods. This default value is returned if no preference with the specified name has been registered or if the file or database that holds the preference data cannot be accessed. A typical use of Preferences is the following:



package com.davidflanagan.editor;

import java.util.prefs.Preferences;



public class TextEditor {

  // Fields to be initialized from preference values

  public int width;             // Screen width in columns

  public String dictionary;     // Dictionary name for spell checking



  public void initPrefs() {

    // Get Preferences objects for user and system preferences for this package

    Preferences userprefs = Preferences.userNodeForPackage(TextEditor.class);

    Preferences sysprefs = Preferences.systemNodeForPackage(TextEditor.class);



    // Look up preference values. Note that you always pass a default value.

    width = userprefs.getInt("width", 80);

    // Look up a user preference using a system preference as the default

    dictionary = userprefs.get("dictionary"

                               sysprefs.get("dictionary",

                                            "default_dictionary"));

  }

}

In addition to the get() methods for querying preference values, there are corresponding put() methods for setting the values of named preferences:



// User has indicated a new preference, so store it

userprefs.putBoolean("autosave", false);

If your application wants to be notified of user or system preference changes while the application is in progress, it may register a PreferenceChangeListener with addPreferenceChangeListener(). A Preferences object can export the names and values of its preferences as an XML file and can read preferences from such an XML file. (See importPreferences(), exportNode(), and exportSubtree() in java.util.pref.Preferences in .) Preferences objects exist in a hierarchy that typically corresponds to the hierarchy of package names. Methods for navigating this hierarchy exist but are not typically used by ordinary applications.

Logging

Another new feature of Java 1.4 is the Logging API, defined in the java.util.logging package. Typically, the application developer uses a Logger object with a name that corresponds to the class or package name of the application to generate log messages at any of seven severity levels (see the Level class in ). These messages may report errors and warnings or provide informational messages about interesting events in the application's life cycle. They can include debugging information or even trace the execution of important methods within the program.

The system administrator or end user of the application is responsible for setting up a logging configuration file that specifies where log messages are directed (the console, a file, a network socket, or a combination of these), how they are formatted (as plain text or XML documents), and at what severity threshold they are logged (log messages with a severity below the specified threshold are discarded with very little overhead and should not significantly impact the performance of the application). The logging level severity threshold can be configured independently for each named Logger. This end-user configurability means that you can write programs to output diagnostic messages that are normally discarded but can be logged during program development or when a problem arises in a deployed application. Logging is particularly useful for applications such as servers that run unattended and do not have a graphical user interface.

For most applications, using the Logging API is quite simple. Obtain a named Logger object whenever necessary by calling the static Logger.getLogger() method, passing the class or package name of the application as the logger name. Then, use one of the many Logger instance methods to generate log messages. The easiest methods to use have names that correspond to severity levels, such as severe(), warning(), and info():



import java.util.logging.*;



// Get a Logger object named after the current package

Logger logger = Logger.getLogger("com.davidflanagan.servers.pop");

logger.info("Starting server.");       // Log an informational message

ServerSocket ss;                       // Do some stuff

try { ss = new ServerSocket(110); }

catch(Exception e) {                   // Log exceptions

  logger.log(Level.SEVERE, "Can't bind port 110", e);  // Complex log message

  logger.warning("Exiting");                           // Simple warning 

  return;

}

logger.fine("got server socket");  // Low-severity (fine-detail) debug message

Prev  [1] [2] [3] Next

Close    To Top
  • Prev Article-Java:
  • Next Article-Java:
  • Now: Tutorial for Web and Software Design > Java > Java Basic > Java Content
    Photoshop Tutorial
     

    Special Effect

      3D Effect
      Photoshop Articles
    Programming Tutorial
     

    C/C++ Tutorial

      Visual Basic
      C# Tutorial
    Database Tutorial
     

    MySQL Tutorial

      MS SQL Tutorial
      Oracle Tutorial
    Geek Tutorial
     

    Blogging Tutorial

      RSS Tutorial
      Podcasting Tutorial
    Graphic Design Tutorial
      Coreldraw Tutorial
      Illustrator Tutorial
      3D Tutorials
    Webmaster Articles
     

    Domain Service

      Web Hosting
      Site Promotion
    Java Tutorial/ Articles
     

    Java Servlets

      JavaEE Tutorial
     

    JavaBeans Tutorial

    XML Tutorial/ Articles
     

    XML Style

      AJAX Tutorial
      XML Mobile
    Flash Tutorial/ Articles
     

    Flash Video

      Action Script
      Flash Articles
    OS Tutorial/ Articles
      Linux Tutorial
      Symbian Tutorial
      MacOS Tutorial
    Personal Tech
      Hardware Tutorial
      Software Tutorial
      Online Auction