打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Java in a Nutshell, 4th Edition(2/3)

Threads

Java makes it easy todefine and work with multiple threads of execution within aprogram. java.lang.Thread is thefundamental thread class in the Java API. There are two ways to define athread. One is to subclass Thread, overridethe run() method, and then instantiate yourThread subclass. The other is to define aclass that implements the Runnable method(i.e., define a run() method) and then passan instance of this Runnable object to theThread() constructor. In either case, theresult is a Thread object, where therun() method is the body of the thread. When you call the start() method of theThread object, the interpreter creates anew thread to execute the run() method. This new thread continues to run until therun() method exits, at which point itceases to exist. Meanwhile, the originalthread continues running itself, starting with thestatement following the start() method. Thefollowing 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 givenpriority level does not typically run unless there are no higher-prioritythreads waiting to run. Here is some code you can usewhen 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 repetitivetask at a fixed interval. This is particularly true when doinggraphical programming that involves animation or similareffects. The key to doing this is making a thread sleep,or stop running for a specified amount of time. This is done withthe 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 thisexample. You can forcefully terminate a thread by calling itsstop() method, but this method has beendeprecated because a thread that is forcefully stopped can leaveobjects it is manipulating in an inconsistent state. If you needa thread that can be stopped, you should define a method such aspleaseStop() that stops the threadin a controlled way.

Timers

In Java 1.3, the java.util.Timer andjava.util.TimerTask classes make it even easierto run repetitive tasks. Here is some code that behaves much likethe 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 foranother thread to complete. You can accomplish this with thejoin() 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 carefulif you allow more than one thread to access the same datastructure. Consider what would happen if one thread was tryingto loop through the elements of a List whileanother thread was sorting those elements. Preventing thisproblem is called thread synchronization and is one of thecentral problems of multithreaded computing. The basic techniquefor preventing two threads from accessing the same object at thesame time is to require a thread to obtain a lock on theobject before the thread can modify it. While any one thread holds thelock, another thread that requests the lock has to wait until thefirst thread is done and releases the lock. Every Java object has the fundamental ability to provide sucha locking capability.

The easiest way to keep objects thread-safe is to declare allsensitive methods synchronized. A thread mustobtain a lock on an object before it can execute any of itssynchronized methods, which means that no otherthread can execute any other synchronizedmethod at the same time. (If a static method is declaredsynchronized, the thread must obtain alock on the class, and this works in the same manner.)To do finer-grained locking, you can specifysynchronized blocks of code that hold a lockon 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 avoiddeadlock, which occurs when two threads endup waiting for each other to release a lock they need. Since neither can proceed, neither one can release the lock itholds, 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 kindof event occurs, at which point it is told to continuerunning. This is done with the wait() andnotify() methods. These aren't methods of theThread class, however; they are methods ofObject. Just as every Java object has a lockassociated with it, every object can maintain a list of waitingthreads. When a thread calls the wait() methodof an object, any locks the thread holds are temporarily released,and the thread is added to the list of waiting threads for thatobject and stops running. When another thread calls thenotify() method of the same object, the objectwakes up one of the waiting threads and allows it to continuerunning:


/**
* 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 methodsare wrapped in a try statement that catches anInterruptedException. This is necessarybecause the interrupt() method allows onethread to interrupt the execution of another. Interrupting athread is not intended to stop it from executing, but towake it up from a blocking state.

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

If a thread calls sleep(),join(), or wait() while itsinterrupt status is set, it does not block butimmediately throws an InterruptedException (theinterrupt status is cleared as a side effect of throwing theexception). Similarly, if the interrupt()method is called on a thread that is already blocked in a call tosleep(), join(), orwait(), that thread stops blocking bythrowing an InterruptedException.

One of the most common times that threads block is while doinginput/output; a thread often has to pause andwait for data to become available from the filesystem or fromthe network. (The java.io,java.net, and java.nio APIsfor performing I/O operations are discussed later in thischapter.) Unfortunately, the interrupt()method does not wake up a thread blocked in an I/O method ofthe java.io package. This is one of theshortcomings of java.io that is cured by theNew I/O API in java.nio. If a thread isinterrupted while blocked in an I/O operation on any channel thatimplementsjava.nio.channels.InterruptibleChannel,the channel is closed, the thread's interrupt status is set, andthe thread wakes up by throwing ajava.nio.channels.ClosedByInterruptException.The same thing happens if a thread tries to call a blocking I/Omethod while its interrupt status is set. Similarly, if a threadis interrupted while it is blocked in theselect() method of ajava.nio.channels.Selector (or if it callsselect() while its interrupt status is set),select() will stop blocking (or will neverstart) and will return immediately. No exception is thrown in thiscase; the interrupted thread simply wakes up, and theselect() call returns.

Files and Directories

The java.io.File class represents a file or adirectory and defines a number of important methods formanipulating files and directories. Note, however, thatnone of these methods allow you to read the contents of a file;that is the job of java.io.FileInputStream, which isjust one of the many types of I/O streams used in Javaand discussed in the next section. Here are some things youcan 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 additionalfunctionality 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

Thejava.io package also defines aRandomAccessFile class that allows you to readbinary data from arbitrary locations in a file. This can bea useful thing to do in certain situations,but most applications read filessequentially, using the stream classes described in the nextsection. Here is a short example of usingRandom-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 packagedefines a large number of classes for reading and writingstreaming, or sequential, data. TheInputStream and OutputStreamclasses are for reading and writing streams of bytes, while theReader and Writer classesare for reading and writing streams of characters. Streams can benested, meaning you might read characters from aFilterReader object that reads and processescharacters from an underlying Reader stream. This underlying Reader stream might read bytes froman InputStream and convert them tocharacters.

Reading Console Input

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


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. Thefollowing code reads an entire text file and quits when it reachesthe 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 theSystem.out.println() method to display text onthe console. System.out simply refers to an outputstream. You can print text to any output stream using similartechniques. The following codeshows 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 codetreat a file as a stream of bytes and read the bytes into alarge 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 streamclasses that operate on streaming data in some useful way. Thefollowing code shows how to use stream classes fromjava.util.zip to compute achecksum of data and then compress the data while writing it to afile:


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 aZipFile class that gives you random access tothe entries of a ZIP archive and allows you to read those entriesthrough 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 (alsoknows as a message digest), use one of the stream classes of thejava.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 classesto manipulate streaming data, but the data itself ultimately comesfrom a file or is written to the console. Thejava.io package defines otherstream classes that canread data from and write data to arrays of bytes or strings oftext:


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 includeByteArrayInputStream, StringWriter,CharArrayReader, and CharArrayWriter.

Thread Communication with Pipes

PipedInputStream andPipedOutputStream and their character-basedcounterparts, PipedReader andPipedWriter, areanother interesting set of streams defined byjava.io. These streams are used in pairsby two threads that want to communicate. One thread writes bytes to aPipedOutputStream or characters to aPipedWriter, and another thread reads bytes orcharacters from the correspondingPipedInputStream orPipedReader:


// 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 thejava.io package is the ability toserializeobjects: to convert an object into a stream of bytes that can later bedeserialized back into a copy of the original object. The followingcode shows how to use serialization to save an object to a file andlater 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 writeserialized objects to any type of stream. Thus, you can write anobject to a byte array, then read it back from the byte array,creating a deep copy of the object. You can write theobject's bytes to a compression stream or even writethe bytes to a stream connected across a network to anotherprogram!

JavaBeans Persistence

Java 1.4 introduces a new serialization mechanism intended foruse with JavaBeans components. java.ioserialization works by saving the state of the internal fieldsof an object. java.beans persistence, on theother hand, works by saving a bean's state as a sequence of callsto the public methods defined by the class. Since it is basedon the public API rather than on the internal state, the JavaBeanspersistence mechanism allows interoperability between differentimplementations of the same API, handles version skew morerobustly, and is suitable for longer-term storage of serializedobjects.

A bean and any descendant beans or other objectsserialized with java.beans.XMLEncoder andcan be deserialized withjava.beans.XMLDecoder. These classes writeto and read from specified streams, but they are not streamclasses 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 itsserialized 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 ofclasses that make writing networked applications surprisinglyeasy. Various examples follow.

Networking with the URL Class

The easiest networking class to use isURL, which represents a uniform resourcelocator. Different Java implementations may support differentsets of URL protocols, but, at a minimum, you can rely on supportfor the http://, ftp://, andfile:// protocols. In Java 1.4, secure HTTP isalso supported with the https:// protocol.Here are some ways you canuse 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 applicationthan is possible with the URL class. In thiscase, you can use a Socket to communicatedirectly 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 beenadded to the core Java platform in the packagesjavax.net and javax.net.ssl.[1]This API enables encrypted network communication over sockets thatuse the SSL (Secure Sockets Layer, also known as TLS) protocol.SSL is widely used on the Internet: it is the basis for secure webcommunication using the https:// protocol. InJava 1.4 and later, you can use https:// withthe URL class as previously shown to securelydownload documents from web servers that support SSL.

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

Like all Java security APIs, JSSE is highly configurable andgives low-level control over all details of setting up andcommunicating over an SSL socket. Thejavax.net and javax.net.sslpackages are fairly complex, but in practice, there are only a fewclasses you need to use to securely communicate with aserver. The following program is a variant on the preceding code thatuses HTTPS instead of HTTP to securely transfer the contents ofthe 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 tocommunicate with a server. The server does the same thing: it usesa Socket object to communicate with each of itsclients. However, the server has an additional task, in that itmust be able to recognize and accept client connectionrequests. This is done with the ServerSocketclass. The following code shows how you might use aServerSocket. The code implements a simple HTTPserver that responds to all requests by sending back (ormirroring) the exact contents of the HTTP request. A dummy serverlike 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 SSLconnections. Making a server secure is more complex than making aclient secure, however, because a server must have acertificate it can present to the client. Therefore, server-sideJSSE is not demonstrated here.

Datagrams

Both URL andSocket performnetworking on top of a stream-based network connection. Setting up andmaintaining a stream across a network takes work at the network level,however. Sometimes you need a low-level way to speed a packet of dataacross a network, but you don't care about maintaining a stream. If, inaddition, you don't need a guarantee that your data will get there orthat the packets of data will arrive in the order you sent them, youmay be interested in the DatagramSocket andDatagramPacket 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 ofjava.util.Hashtable, a legacy collections classthat predates the Collections API of Java 1.2. AProperties object maintains a mapping betweenstring keys and string values and defines methods that allow themappings to be written to and read from a simply formatted textfile. This makes the Properties class ideal forconfiguration and user preference files. TheProperties class is also used for the systemproperties 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 specificallytailored for working with user and systemwide preferences and ismore useful than Properties for this purpose. The Preferences APIis defined by the java.util.prefs package. Thekey class in that package is Preferences.You can obtain a Preferences object that containsuser-specific preferences with the static methodPreferences.userNodeForPackage() and obtain aPreferences object that contains systemwidepreferences withPreferences.systemNodeForPackage(). Bothmethods take a java.lang.Class object as theirsole argument and return a Preferences objectshared by all classes in that package. (This means thatthe preference names you use must be unique within the package.)Once you have a Preferences object, use theget() method to query the string value of anamed preference, or use other type-specific methods such asgetInt(), getBoolean(), andgetByteArray(). Note that to querypreference values, a default value must be passed for all methods. Thisdefault value is returned if no preference with the specified namehas been registered or if the file or database that holds thepreference data cannot be accessed. A typical useof 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 queryingpreference values, there are correspondingput() methods for setting the values of namedpreferences:


// User has indicated a new preference, so store it
userprefs.putBoolean("autosave", false);
If your application wants to be notified of user orsystem preference changes while the application is in progress, itmay register a PreferenceChangeListener withaddPreferenceChangeListener(). APreferences object can export the names andvalues of its preferences as an XML file and can readpreferences from such an XML file. (SeeimportPreferences(), exportNode(), andexportSubtree() in java.util.pref.Preferences in .)Preferences objects exist in a hierarchy thattypically corresponds to the hierarchy of package names. Methodsfor navigating this hierarchy exist but are not typically used byordinary applications.

Logging

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

The system administratoror end user of the application is responsible for setting up alogging configuration file that specifies where log messages aredirected (the console, a file, a network socket, or a combinationof these), how they are formatted (as plain text or XML documents),and at what severity threshold they are logged (log messages witha severity below the specified threshold are discarded with verylittle overhead and should not significantly impact theperformance of the application). The logging levelseverity threshold can be configured independently for each namedLogger. This end-user configurability meansthat you can write programs to output diagnostic messages thatare normally discarded but can be logged during programdevelopment or when a problem arises in a deployed application.Logging is particularly useful for applications such as servers thatrun 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 necessaryby calling the static Logger.getLogger()method, passing the class or package name of the application asthe logger name. Then, use one of the manyLogger instance methods to generate logmessages. The easiest methods to use have names that correspondto 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
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C# Socket服务端和客户端通话
The Architecture of Open Source Applications ...
loader runner Vuser说明.
C# Tutorial
Felix Colibri
使用jstack检测Java应用的死锁(deadlock)状态
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服