Chapter 17. Communicating Across the Internet

Java was developed initially as a language that would control a network of interactive consumer devices. Connecting machines was one of the main purposes of the language when it was designed, and that remains true today.

The java.net package makes it possible to communicate over a network, providing cross-platform abstractions to make connections, transfer files using common web protocols, and create sockets.

Used in conjunction with input and output streams, reading and writing files over the network becomes as easy as reading or writing files on disk.

The java.nio package expands Java’s input and output classes.

Today, you write networking Java programs, creating applications that load a document over the Web, mimic a popular Internet service, and serve information to clients.

Networking in Java

Networking is the capability of different computers to make connections with each other and to exchange information. In Java, basic networking is supported by classes in the java.net package, including support for connecting and retrieving files by Hypertext Transfer Protocol (HTTP) and File Transfer Protocol (FTP), as well as working at a lower level with sockets.

You can communicate with systems on the Net in three simple ways:

  • Load a web page and any other resource with a uniform resource locator (URL) from an applet.

  • Use the socket classes, Socket and ServerSocket, which open standard socket connections to hosts and read to and write from those connections.

  • Call getInputStream(), a method that opens a connection to a URL and can extract data from that connection.

Opening a Stream over the Net

As you learned during Day 15, “Working with Input and Output,” you can pull information through a stream into your Java programs in several ways. The classes and methods you choose depend on the format of the information and what you want to do with it.

One of the resources you can reach from your Java programs is a text document on the web, whether it’s a Hypertext Markup Language (HTML) file, Extensible Markup Language (XML) file, or some other kind of plain text document.

You can use a four-step process to load a text document off the Web and read it line by line:

  1. Create a URL object that represents the resource’s web address.

  2. Create a HttpURLConnection object that can load the URL and make a connection to the site hosting it.

  3. Use the getContent() method of that HttpURLConnection object to create an InputStreamReader that can read a stream of data from the URL.

  4. Use that input stream reader to create a BufferedReader object that can efficiently read characters from an input stream.

There’s a lot of interaction going on between the web document and your Java program. The URL is used to set up a URL connection, which is used to set up an input stream reader, which is used to set up a buffered input stream reader. The need to catch any exceptions that occur along the way adds more complexity to the process.

Before you can load anything, you must create a new instance of the class URL that represents the address of the resource you want to load. URL is an acronym for uniform resource locator, and it refers to the unique address of any document or other resource accessible on the Internet.

URL is part of the java.net package, so you must import the package or refer to the class by its full name in your programs.

To create a new URL object, use one of four constructors:

  • URL(String)Creates a URL object from a full web address such as http://www.java21days.com or ftp://ftp.netscape.com.

  • URL(URL, String)Creates a URL object with a base address provided by the specified URL and a relative path provided by the String.

  • URL(String, String, int, String)Creates a new URL object from a protocol (such as “http” or “ftp”), hostname (such as “www.cnn.com” or “web.archive.org”), port number (80 for HTTP), and a filename or pathname.

  • URL(String, String, String)The same as the previous constructor minus the port number.

When you use the URL(String) constructor, you must deal with MalformedURLException objects, which are thrown if the String does not appear to be a valid URL. These objects can be handled in a try-catch block:

try {
    URL load = new URL("http://www.samspublishing.com");
} catch (MalformedURLException e) {
    System.out.println("Bad URL");
}

The WebReader application in Listing 17.1 uses the four-step technique to open a connection to a website and read a text document from it. When the document is fully loaded, it is displayed in a text area.

Example 17.1. The Full Text of WebReader.java

 1: import javax.swing.*;
 2: import java.awt.*;
 3: import java.awt.event.*;
 4: import java.net.*;
 5: import java.io.*;
 6:
 7: public class WebReader extends JFrame {
 8:     JTextArea box = new JTextArea("Getting data ...");
 9:
10:     public WebReader() {
11:         super("Get File Application");
12:         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
13:         setSize(600, 300);
14:         JScrollPane pane = new JScrollPane(box);
15:         add(pane);
16:         setVisible(true);
17:     }
18:
19:     void getData(String address) throws MalformedURLException {
20:         setTitle(address);
21:         URL page = new URL(address);
22:         StringBuffer text = new StringBuffer();
23:         try {
24:             HttpURLConnection conn = (HttpURLConnection)
25:                 page.openConnection();
26:             conn.connect();
27:             InputStreamReader in = new InputStreamReader(
28:                   (InputStream) conn.getContent());
29:             BufferedReader buff = new BufferedReader(in);
30:             box.setText("Getting data ...");
31:             String line;
32:             do {
33:                 line = buff.readLine();
34:                 text.append(line + "
");
35:             } while (line != null);
36:             box.setText(text.toString());
37:         } catch (IOException ioe) {
38:             System.out.println("IO Error:" + ioe.getMessage());
39:         }
40:     }
41:
42:     public static void main(String[] arguments) {
43:         if (arguments.length < 1) {
44:             System.out.println("Usage: java WebReader url");
45:             System.exit(1);
46:         }
47:         try {
48:             WebReader app = new WebReader();
49:             app.getData(arguments[0]);
50:         } catch (MalformedURLException mue) {
51:             System.out.println("Bad URL: " + arguments[0]);
52:         }
53:     }
54: }

To run the WebReader application, specify a URL as the only command-line argument. For example:

java WebReader http://www.rssboard.org/rss-feed

Any URL can be chosen; try http://tycho.usno.navy.mil/cgi-bin/timer.pl for the U.S. Naval Observatory timekeeping site or http://random.yahoo.com/bin/ryl for a random link from the Yahoo! directory. The preceding example loads a page from an RSS file, as shown in Figure 17.1.

Running the WebReader application.

Figure 17.1. Running the WebReader application.

Two thirds of the WebReader class is devoted to running the application, creating the user interface, and creating a valid URL object. The web document is loaded over a stream and displayed in a text area in the getData() method.

Four objects are used: URL, HttpURLConnection, InputStreamReader, and BufferedReader objects. These objects work together to pull the data from the Internet to the Java application. In addition, two objects are created to hold the data when it arrives: a String and a StringBuffer.

Lines 24–26 open an HTTP URL connection, which is necessary to get an input stream from that connection.

Lines 27–28 use the connection’s getContent() method to create a new input stream reader. The method returns an input stream representing the connection to the URL.

Line 29 uses that input stream reader to create a new buffered input stream reader—a BufferedReader object called buff.

After you have this buffered reader, you can use its readLine() method to read a line of text from the input stream. The buffered reader puts characters in a buffer as they arrive and pulls them out of the buffer when requested.

The do-while loop in lines 32–35 reads the web document line by line, appending each line to the StringBuffer object created to hold the page’s text.

After all the data has been read, line 36 converts the string buffer into a string with the toString() method and then puts that result in the program’s text area by calling the component’s setText(String) method.

The HttpUrlConnection class includes several methods that affect the HTTP request or provide more information:

  • getHeaderField(int)Returns a string containing an HTTP header such as “Server” (the web server hosting the document) or “Last-Modified” (the date the document was last changed)

    Headers are numbered from 0 upward. When the end of the headers is reached, this method returns null

  • getHeaderFieldKey(int)Returns a string containing the name of the numbered header (such as “Server” or “Last-Modified”) or null

  • getResponseCode()Returns an integer containing the HTTP response code for the request, such as 200 (for valid requests) or 404 (for documents that could not be found)

  • getResponseMessage()Returns a string containing the HTTP response code and an explanatory message (for example: “HTTP/1.0 200 OK”)

    The HttpUrlConnection class contains integer class variables for each of the valid response codes, including HTTP_OK, HTTP_NOT_FOUND, and HTTP_MOVED_PERM

  • getContentType()Returns a string containing the MIME type of the web document; some possible types are text/html for web pages and text/xml for XML files

  • setFollowRedirects(boolean)Determines whether URL redirection requests should be followed (true) or ignored (false); when redirection is supported, a URL request can be forwarded by a web server from an obsolete URL to its correct address

The following code could be added to the getData() method of WebReader to display headers along with the text of a document:

String key;
String header;
int i = 0;
do {
    key = conn.getHeaderFieldKey(i);
    header = conn.getHeaderField(i);
    if (key == null) {
        key = "";
    } else {
        key = key + ": ";
    }
    if (header != null) {
        text.append(key + header + "
");
    }
    i++;
} while (header != null);
text.append("
");

Sockets

For networking applications beyond what the URL and URLConnection classes offer (for example, for other protocols or for more general networking applications), Java provides the Socket and ServerSocket classes as an abstraction of standard Transmission Control Protocol (TCP) socket programming techniques.

The Socket class provides a client-side socket interface similar to standard UNIX sockets. Create a new instance of Socket to open a connection (where hostName is the host to connect to and portNumber is the port number):

Socket connection = new Socket(hostName, portNumber);

After you create a socket, set its timeout value, which determines how long the application waits for data to arrive. This is handled by calling the socket’s setSoTimeOut(int) method with the number of milliseconds to wait as the only argument:

connection.setSoTimeOut(50000);

By using this method, any effort to read data from the socket represented by connection waits for only 50,000 milliseconds (50 seconds). If the timeout is reached, an InterruptedIOException is thrown, which gives you an opportunity in a try-catch block to either close the socket or try to read from it again.

If you don’t set a timeout in a program that uses sockets, it might hang indefinitely waiting for data.

Tip

This problem is usually avoided by putting network operations in their own thread and running them separately from the rest of the program, a technique used with animation on Day 7, “Exceptions, Assertions, and Threads.”

After the socket is open, you can use input and output streams to read and write from that socket:

BufferedInputStream bis = new
    BufferedInputStream(connection.getInputStream());
DataInputStream in = new DataInputStream(bis);

BufferedOutputStream bos = new
    BufferedOutputStream(connection.getOutputStream());
DataOutputStream out= new DataOutputStream(bos);

You don’t need names for all these objects; they are used only to create a stream or stream reader. For an efficient shortcut, combine several statements as in this example using a Socket object named sock:

DataInputStream in = new DataInputStream(
    new BufferedInputStream(
        sock.getInputStream()));

In this statement, the call to sock.getInputStream() returns an input stream associated with that socket. This stream is used to create a BufferedInputStream, and the buffered input stream is used to create a DataInputStream.

The only variables you are left with are sock and in, the two objects needed as you receive data from the connection and close it afterward. The intermediate objects—a BufferedInputStream and an InputStream—are needed only once.

After you’re finished with a socket, don’t forget to close it by calling the close() method. This also closes all the input and output streams you might have set up for that socket. For example:

connection.close();

Socket programming can be used for many services delivered using TCP/IP networking, including telnet, Simple Mail Transfer Protocol (SMTP) for incoming mail, Network News Transfer Protocol (NNTP) for Usenet news, and finger.

The last of these, finger, is a protocol for asking a system about one of its users. By setting up a finger server, a system administrator enables an Internet-connected machine to answer requests for user information. Users can provide information about themselves by creating .plan files, which are sent to anyone who uses finger to find out more about them.

Although it has fallen into disuse in recent years because of security concerns, finger was the most popular way that Internet users published facts about themselves and their activities before weblogs were introduced. You could use finger on a friend’s account at another college to see whether that person was online and read the person’s current .plan file.

Note

Today, you can still find some people writing personal diaries over finger—the game-programming community. The Blues News website has links to several of these throwbacks at http://www.bluesnews.com/plans/476/.

As an exercise in socket programming, the Finger application is a rudimentary finger client (see Listing 17.2).

Example 17.2. The Full Text of Finger.java

 1: import java.io.*;
 2: import java.net.*;
 3: import java.util.*;
 4:
 5: public class Finger {
 6:     public static void main(String[] arguments) {
 7:         String user;
 8:         String host;
 9:         if ((arguments.length == 1) && (arguments[0].indexOf("@") > -1)) {
10:             StringTokenizer split = new StringTokenizer(arguments[0],
11:                 "@");
12:             user = split.nextToken();
13:             host = split.nextToken();
14:         } else {
15:             System.out.println("Usage: java Finger user@host");
16:             return;
17:         }
18:         try {
19:             Socket digit = new Socket(host, 79);
20:             digit.setSoTimeout(20000);
21:             PrintStream out = new PrintStream(digit.getOutputStream());
22:             out.print(user + "1512");
23:             BufferedReader in = new BufferedReader(
24:                 new InputStreamReader(digit.getInputStream()));
25:             boolean eof = false;
26:             while (!eof) {
27:                 String line = in.readLine();
28:                 if (line != null)
29:                     System.out.println(line);
30:                 else
31:                     eof = true;

32:             }
33:             digit.close();
34:         } catch (IOException e) {
35:             System.out.println("IO Error:" + e.getMessage());
36:         }
37:     }
38: }

When making a finger request, specify a username followed by an at sign (“@”) and a hostname, the same format as an email address. One famous real-life example is [email protected], the finger address of id Software founder John Carmack. You can request his .plan file by running the Finger application as follows:

java Finger [email protected]

If johnc has an account on the idsoftware.com finger server, the output of this program is his .plan file and perhaps other information (though it appears he’s no longer updating it). The server also lets you know whether a user can’t be found.

Blues News includes addresses for other game designers who provide .plan updates such as id Software developer Timothee Besset ([email protected]).

The Finger application uses the StringTokenizer class to convert an address in user@host format into two String objects: user and host (lines 10–13).

The following socket activities are taking place:

  • Lines 19–20—A new Socket is created using the hostname and port 79, the port traditionally reserved for finger services, and a timeout of 20 seconds is set.

  • Line 21—The socket is used to get an OutputStream, which feeds into a new PrintStream object.

  • Line 22—The finger protocol requires that the username be sent through the socket, followed by a carriage return (‘15’) and linefeed (‘12’). This is handled by calling the print() method of the new PrintStream.

  • Lines 23–24—After the username has been sent, an input stream must be created on the socket to receive input from the finger server. A BufferedReader stream, in, is created by combining several stream-creation expressions together. This stream is well suited for finger input because it can read a line of text at a time.

  • Lines 26–32—The program loops as lines are read from the buffered reader. The end of output from the server causes in.readLine() to return null, ending the loop.

The same techniques used to communicate with a finger server through a socket can be used to connect to other popular Internet services. You could turn it into a telnet or web-reading client with a port change in line 19 and little other modification.

Socket Servers

Server-side sockets work similarly to client sockets, with the exception of the accept() method. A server socket listens on a TCP port for a connection from a client; when a client connects to that port, the accept() method accepts a connection from that client. By using both client and server sockets, you can create applications that communicate with each other over the network.

To create a server socket and bind it to a port, create a new instance of ServerSocket with a port number as an argument to the constructor, as in the following example:

ServerSocket servo = new ServerSocket(8888);

Use the accept() method to listen on that port (and to accept a connection from any clients if one is made):

servo.accept();

After the socket connection is made, you can use input and output streams to read from and write to the client.

To extend the behavior of the socket classes—for example, to allow network connections to work across a firewall or a proxy—you can use the abstract class SocketImpl and the interface SocketImplFactory to create a new transport-layer socket implementation. This design fits with the original goal of Java’s socket classes: to allow those classes to be portable to other systems with different transport mechanisms. The problem with this mechanism is that although it works for simple cases, it prevents you from adding other protocols on top of TCP (for example, to implement an encryption mechanism such as Secure Sockets Layer [SSL]) and from having multiple socket implementations per Java runtime.

For these reasons, sockets were extended after Java 1.0, so the Socket and ServerSocket classes are not final and extendable. You can create subclasses of these classes that use either the default socket implementation or your own implementation. This allows much more flexible network capabilities.

Designing a Server Application

Here’s an example of a Java program that uses the Socket classes to implement a simple network-based server application.

The TimeServer application makes a connection to any client that connects to port 4415, displays the current time, and then closes the connection.

For an application to act as a server, it must monitor at least one port on the host machine for client connections. Port 4415 was chosen arbitrarily for this project, but it could be any number from 1024 to 65,535.

Note

The Internet Assigned Numbers Authority controls the usage of ports 0 to 1023, but claims are staked to the higher ports on a more informal basis. When choosing port numbers for your own client/server applications, it’s a good idea to do research on what ports are being used by others. Search the Web for references to the port you want to use and plug the terms “Registered Port Numbers” and “Well-Known Port Numbers” into search engines to find lists of in-use ports. A good guide to port usage is available on the Web at http://www.sockets.com/services.htm.

When a client is detected, the server creates a Date object that represents the current date and time and then sends it to the client as a String.

In this exchange of information between the server and client, the server does almost all the work. The client’s only responsibility is to establish a connection to the server and display messages received from the server.

Although you could develop a simple client for a project like this, you also can use any telnet application to act as the client, as long as it can connect to a port you designate. Windows includes a command-line application called telnet that you can use for this purpose.

Listing 17.3 contains the full source code for the server application.

Example 17.3. The Full Text of TimeServer.java

 1: import java.io.*;
 2: import java.net.*;
 3: import java.util.*;
 4:
 5: public class TimeServer extends Thread {
 6:     private ServerSocket sock;
 7:
 8:     public TimeServer() {
 9:         super();
10:         try {
11:         sock = new ServerSocket(4415);
12:         System.out.println("TimeServer running ...");
13:     } catch (IOException e) {
14:         System.out.println("Error: couldn't create socket.");
15:         System.exit(1);
16:     }
17: }
18:
19: public void run() {
20:     Socket client = null;
21:
22:     while (true) {
23:         if (sock == null)
24:            return;
25:         try {
26:             client = sock.accept();
27:             BufferedOutputStream bos = new BufferedOutputStream(
28:                   client.getOutputStream());
29:             PrintWriter os = new PrintWriter(bos, false);
30:             String outLine;
31:
32:             Date now = new Date();
33:             os.println(now);
34:             os.flush();
35:
36:             os.close();
37:             client.close();
38:         } catch (IOException e) {
39:             System.out.println("Error: couldn't connect to client.");
40:             System.exit(1);
41:         }
42:     }
43:   }
44:
45:   public static void main(String[] arguments) {
46:       TimeServer server = new TimeServer();
47:       server.start();
48:   }
49:
50: }

The TimeServer application creates a server socket on port 4415. When a client connects, a PrintWriter object is constructed from buffered output stream so that a string can be sent to the client—the current time.

After the string has been sent, the flush() and close() methods of the writer end the data exchange and close the socket to await new connections.

Testing the Server

The TimeServer application must be running for a client to be able to connect to it. To get things started, you must first run the server:

java TimeServer

The server displays only one line of output if it is running successfully:

TimeServer running ...

With the server running, you can connect to it on port 4415 of your computer using a telnet program.

To run telnet on Windows:

  • On Windows 95, 98, Me, NT, or 2000, click Start, Run to open the Run dialog box, and then type telnet in the Open field and press Enter. A telnet window opens.

    To make a telnet connection using this program, choose the menu command Connect, Remote System. A Connect dialog box opens. Enter localhost in the Host Name field, enter 4415 in the Port field, and leave the default value—vt100—in the TermType field.

  • On Windows XP or 2003, choose Start, Run to open the Run dialog box; then type telnet to run that program, then type the command open localhost 4415 in the Open field and press Enter.

  • The hostname localhost represents your own machine—the system running the application. You can use it to test server applications before deploying them permanently on the Internet.

Depending on how Internet connections have been configured on your system, you might need to log on to the Internet before a successful socket connection can be made between a telnet client and the TimeServer application.

If the server is on another computer connected to the Internet, you would specify that computer’s hostname or IP address rather than localhost.

When you use telnet to make a connection with the TimeServer application, it displays the server’s current time and closes the connection. The output of the telnet program should be something like the following:

Thu Feb 15 22:13:58 EST 2007
Connection to host lost.
Press any key to continue...

The java.nio Package

The java.nio package expands the networking capabilities of the language with classes useful for reading and writing data; working with files, sockets, and memory; and handling text.

Two related packages also are used often when you are working with the new input/output features: java.nio.channels and java.nio.charset.

Buffers

The java.nio package includes support for buffers, objects that represent data streams stored in memory.

Buffers are often used to improve the performance of programs that read input or write output. They enable a program to put a lot of data in memory, where it can be read, written, and modified more quickly.

A buffer corresponds with each of the primitive data types in Java:

  • ByteBuffer

  • CharBuffer

  • DoubleBuffer

  • FloatBuffer

  • IntBuffer

  • LongBuffer

  • ShortBuffer

Each of these classes has a static method called wrap() that can be used to create a buffer from an array of the corresponding data type. The only argument to the method should be the array.

For example, the following statements create an array of integers and an IntBuffer that holds the integers in memory as a buffer:

int[] temperatures = { 90, 85, 87, 78, 80, 75, 70, 79, 85, 92, 99 };
IntBuffer tempBuffer = IntBuffer.wrap(temperatures);

A buffer keeps track of how it is used, storing the position where the next item will be read or written. After the buffer is created, its get() method reads the data at the current position in the buffer. The following statements extend the previous example and display everything in the integer buffer:

for (int i = 0; tempBuffer.remaining() > 0; i++)
    System.out.println(tempBuffer.get());

Another way to create a buffer is to set up an empty buffer and then put data into it. To create the buffer, call the static method allocate(int) of the desired buffer class with the size of the buffer as an argument.

You can use five put() methods to store data in a buffer (or replace the data already there). The arguments used with these methods depend on the kind of buffer you’re working with. For an integer buffer:

  • put(int)Stores the integer at the current position in the buffer and then increments the position.

  • put(int, int)Stores an integer (the second argument) at a specific position in the buffer (the first argument).

  • put(int[])Stores all the elements of the integer array in the buffer, beginning at the first position of the buffer.

  • put(int[], int, int)Stores all or a portion of an integer array in the buffer. The second argument specifies the position in the buffer where the first integer in the array should be stored. The third argument specifies the number of elements from the array to store in the buffer.

  • put(IntBuffer)Stores the contents of an integer buffer in another buffer, beginning at the first position of the buffer.

As you put data in a buffer, you must often keep track of the current position so that you know where the next data will be stored.

To find out the current position, call the buffer’s position() method. An integer is returned that represents the position. If this value is 0, you’re at the start of the buffer.

Call the position(int) method to change the position to the argument specified as an integer.

Another important position to track when using buffers is the limit—the last place in the buffer that contains data.

It isn’t necessary to figure out the limit when the buffer is always full; in that case, you know the last position of the buffer has something in it.

However, if there’s a chance your buffer might contain less data than you have allocated, you should call the buffer’s flip() method after reading data into the buffer. This sets the current position to the start of the data you just read and sets the limit to the end.

Later today, you’ll use a byte buffer to store data loaded from a web page on the Internet. This is a place where flip() becomes necessary because you don’t know how much data the page contains when you request it.

If the buffer is 1,024 bytes in size and the page contains 1,500 bytes, the first attempt to read data loads the buffer with 1,024 bytes, filling it.

The second attempt to read data loads the buffer with only 476 bytes, leaving the rest empty. If you call flip() afterward, the current position is set to the beginning of the buffer, and the limit is set to 476.

The following code creates an array of Fahrenheit temperatures, converts them to Celsius, and then stores the Celsius values in a buffer:

int[] temps = { 90, 85, 87, 78, 80, 75, 70, 79, 85, 92, 99 };
IntBuffer tempBuffer = IntBuffer.allocate(temps.length);
for (int i = 0; i < temps.length; i++) {
    float celsius = ( (float)temps[i] - 32 ) / 9 * 5;
    tempBuffer.put( (int)celsius );
};
tempBuffer.position(0);
for (int i = 0; tempBuffer.remaining() > 0; i++)
    System.out.println(tempBuffer.get());

After the buffer’s position is set back to the start, the contents of the buffer are displayed.

Byte Buffers

You can use the buffer methods introduced so far with byte buffers, but byte buffers also offer additional useful methods.

For starters, byte buffers have methods to store and retrieve data that isn’t a byte:

  • putChar(char)Stores 2 bytes in the buffer that represent the specified char value

  • putDouble(double)Stores 8 bytes in the buffer that represent a double value

  • putFloat(float)Stores 4 bytes in the buffer that represent a float value

  • putInt(int)Stores 4 bytes in the buffer that represent an int value

  • putLong(long)Stores 8 bytes in the buffer that represent a long value

  • putShort(short)Stores 2 bytes in the buffer that represent a short value

Each of these methods puts more than one byte in the buffer, moving the current position forward the same number of bytes.

There also are methods to retrieve nonbytes from a byte buffer: getChar(), getDouble(), getFloat(), getInt(), getLong(), and getShort().

Character Sets

Character sets, which are offered in the java.nio.charset package, are a set of classes used to convert data between byte buffers and character buffers.

The three main classes are

  • CharsetA Unicode character set with a different byte value for each different character in the set

  • DecoderA class that transforms a series of bytes into a series of characters

  • EncoderA class that transforms a series of characters into a series of bytes

Before you can perform any transformations between byte and character buffers, you must create a CharSet object that maps characters to their corresponding byte values.

To create a character set, call the forName(String) static method of the Charset class, specifying the name of the set’s character encoding.

Java includes support for six character encodings:

  • US-ASCII—The 128-character ASCII set that makes up the Basic Latin block of Unicode (also called ISO646-US)

  • ISO-8859-1—The 256-character ISO Latin Alphabet No. 1.a. character set (also called ISO-LATIN-1)

  • UTF-8—A character set that includes US-ASCII and the Universal Character Set (also called Unicode), a set comprising thousands of characters used in the world’s languages

  • UTF-16BE—The Universal Character Set represented as 16-bit characters with bytes stored in big-endian byte order

  • UTF-16LE—The Universal Character Set represented as 16-bit characters with bytes stored in little-endian byte order

  • UTF-16—The Universal Character Set represented as 16-bit characters with the order of bytes indicated by an optional byte-order mark

The following statement creates a Charset object for the ISO-8859-1 character set:

Charset isoset = Charset.forName("ISO-8859-1");

After you have a character set object, you can use it to create encoders and decoders. Call the object’s newDecoder() method to create a CharsetDecoder and the newEncoder() method to create an CharsetEncoder.

To transform a byte buffer into a character buffer, call the decoder’s decode(ByteBuffer) method, which returns a CharBuffer containing the bytes transformed into characters.

To transform a character buffer into a byte buffer, call the encoder’s encode(CharBuffer) method. A ByteBuffer is returned containing the byte values of the characters.

The following statements convert a byte buffer called netBuffer into a character buffer using the ISO-8859-1 character set:

Charset set = Charset.forName("ISO-8859-1");
CharsetDecoder decoder = set.newDecoder();
netBuffer.position(0);
CharBuffer netText = decoder.decode(netBuffer);

Caution

Before the decoder is used to create the character buffer, the call to position(0) resets the current position of the netBuffer to the start. When working with buffers for the first time, it’s easy to overlook this, resulting in a buffer with much less data than you expected.

Channels

A common use for a buffer is to associate it with an input or output stream. You can fill a buffer with data from an input stream or write a buffer to an output stream.

To do this, you must use a channel, an object that connects a buffer to the stream. Channels are part of the java.nio.channels package.

Channels can be associated with a stream by calling the getChannel() method available in some of the stream classes in the java.io package.

The FileInputStream and FileOutputStream classes have getChannel() methods that return a FileChannel object. This file channel can be used to read, write, and modify the data in the file.

The following statements create a file input stream and a channel associated with that file:

try {
    String source = "prices.dat";
    FileInputStream inSource = new FileInputStream(source);
    FileChannel inChannel = inSource.getChannel();
} catch (FileNotFoundException fne) {
    System.out.println(fne.getMessage());
}

After you have created the file channel, you can find out how many bytes the file contains by calling its size() method. This is necessary if you want to create a byte buffer to hold the contents of the file.

Bytes are read from a channel into a ByteBuffer with the read(ByteBuffer, long) method. The first argument is the buffer. The second argument is the current position in the buffer, which determines where the file’s contents will begin to be stored.

The following statements extend the last example by reading a file into a byte buffer using the inChannel file channel:

long inSize = inChannel.size();
ByteBuffer data = ByteBuffer.allocate( (int)inSize );
inChannel.read(data, 0);
data.position(0);
for (int i = 0; data.remaining() > 0; i++)
    System.out.print(data.get() + " ");

The attempt to read from the channel generates an IOException error if a problem occurs. Although the byte buffer is the same size as the file, this isn’t a requirement. If you are reading the file into the buffer so that you can modify it, you can allocate a larger buffer.

The next project you undertake incorporates the new input/output features you have learned about so far: buffers, character sets, and channels.

The BufferConverter application reads a small file into a byte buffer, displays the contents of the buffer, converts it to a character buffer, and then displays the characters.

Enter the text of Listing 17.4 and save it as BufferConverter.java.

Example 17.4. The Full Text of BufferConverter.java

 1: import java.nio.*;
 2: import java.nio.channels.*;
 3: import java.nio.charset.*;
 4: import java.io.*;
 5:
 6: public class BufferConverter {
 7:     public static void main(String[] arguments) {
 8:         try {
 9:             // read byte data into a byte buffer
10:             String data = "friends.dat";
11:             FileInputStream inData = new FileInputStream(data);
12:             FileChannel inChannel = inData.getChannel();
13:             long inSize = inChannel.size();
14:             ByteBuffer source = ByteBuffer.allocate( (int)inSize );
15:             inChannel.read(source, 0);
16:             source.position(0);
17:             System.out.println("Original byte data:");
18:             for (int i = 0; source.remaining() > 0; i++)
19:                 System.out.print(source.get() + " ");
20:
21:            // convert byte data into character data
22:            source.position(0);
23:            Charset ascii = Charset.forName("US-ASCII");
24:            CharsetDecoder toAscii = ascii.newDecoder();
25:            CharBuffer destination = toAscii.decode(source);
26:            destination.position(0);
27:            System.out.println("

New character data:");
28:            for (int i = 0; destination.remaining() > 0; i++)
29:                System.out.print(destination.get());
30:         } catch (FileNotFoundException fne) {
31:             System.out.println(fne.getMessage());
32:         } catch (IOException ioe) {
33:             System.out.println(ioe.getMessage());
34:         }
35:     }
36: }

After you compile the file, you need a copy of friends.dat, the small file of byte data used in the application. To download it from the book’s website at http://www.java21days.com, open the Day 17 page, click the friends.dat hyperlink, and save the file in the same place as BufferConverter.class.

Tip

You also can create your own file. Open a text editor, type a sentence or two in the document, and save it as friends.dat.

If you use the copy of friends.dat from the book’s website, the output of the BufferConverter application is the following:

Original byte data:
70 114 105 101 110 100 115 44 32 82 111 109 97 110 115 44 32
99 111 117 110 116 114 121 109 101 110 44 32 108 101 110 100
32 109 101 32 121 111 117 114 32 101 97 114 115 46 13 10 13
10

New character data:
Friends, Romans, countrymen, lend me your ears.

The BufferConverter application uses the techniques introduced today to read data and represent it as bytes and characters, but you could have accomplished the same thing with the old input/output package, java.io.

For this reason, you might wonder why it’s worth learning the new package at all.

One reason is that buffers enable you to manipulate large amounts of data much more quickly. You’ll find out another reason in the next section.

Network Channels

A popular feature of the java.nio package is its support for nonblocking input and output over a networking connection.

In Java, blocking refers to a statement that must complete execution before anything else happens in the program. All the socket programming you have done up to this point has used blocking methods exclusively. For example, in the TimeServer application, when the server socket’s accept() method is called, nothing else happens in the program until a client makes a connection.

As you can imagine, it’s problematic for a networking program to wait until a particular statement is executed because numerous things can go wrong. Connections can be broken. A server could go offline. A socket connection could appear to be stalled because a blocked statement is waiting for something to happen.

For example, a client application that reads and buffers data over HTTP might be waiting for a buffer to be filled even though no more data remains to be sent. The program will appear to have halted because the blocked statement never finishes executing.

With the java.nio package, you can create networking connections and read and write from them using nonblocking methods.

Here’s how it works:

  • Associate a socket channel with an input or output stream.

  • Configure the channel to recognize the kind of networking events you want to monitor, such as new connections, attempts to read data over the channel, and attempts to write data.

  • Call a method to open the channel.

  • Because the method is nonblocking, the program continues execution so that you can handle other tasks.

  • If one of the networking events you are monitoring takes place, your program is notified—a method associated with the event is called.

This is comparable to how user-interface components are programmed in Swing. An interface component is associated with one or more event listeners and placed in a container. If the interface component receives input being monitored by a listener, an event-handling method is called. Until that happens, the program can handle other tasks.

To use nonblocking input and output, you must work with channels instead of streams.

Nonblocking Socket Clients and Servers

The first step in the development of a nonblocking client or server is the creation of an object that represents the Internet address to which you are making a connection. This task is handled by the InetSocketAddress class in the java.net package.

If the server is identified by a hostname, call InetSocketAddress(String,int) with two arguments: the name of the server and its port number.

If the server is identified by its IP address, use the InetAddress class in java.net to identify the host. Call the static method InetAddress.getByName(String) with the IP address of the host as the argument. The method returns an InetAddress object representing the address, which you can use in calling InetSocketAddress(InetAddress, int). The second argument is the server’s port number.

Nonblocking connections require a socket channel, another of the new classes in the java.nio package. Call the open() static method of the SocketChannel class to create the channel.

A socket channel can be configured for blocking or nonblocking communication. To set up a nonblocking channel, call the channel’s configureBlocking(boolean) method with an argument of false. Calling it with true makes it a blocking channel.

After the channel is configured, call its connect(InetSocketAddress) method to connect the socket.

On a blocking channel, the connect() method attempts to establish a connection to the server and waits until it is complete, returning a value of true to indicate success.

On a nonblocking channel, the connect() method returns immediately with a value of false. To figure out what’s going on over the channel and respond to events, you must use a channel-listening object called a Selector.

A Selector is an object that keeps track of things that happen to a socket channel (or another channel in the package that is a subclass of SelectableChannel).

To create a Selector, call its open() method, as in the following statement:

Selector monitor = Selector.open();

When you use a Selector, you must indicate the events you are interested in monitoring. This is handled by calling a channel’s register(Selector, int, Object) method.

The three arguments to register() are the following:

  • The Selector object you have created to monitor the channel

  • An int value that represents the events being monitored (also called selection keys)

  • An Object that can be delivered along with the key, or null otherwise

Instead of using an integer value as the second argument, it’s easier to use one or more class variables from the SelectionKey class: SelectionKey.OP_CONNECT to monitor connections, SelectionKey.OP_READ to monitor attempts to read data, and SelectionKey.OP_WRITE to monitor attempts to write data.

The following statements create a Selector to monitor a socket channel called wire for reading data:

Selector spy = Selector.open();
channel.register(spy, SelectionKey.OP_READ, null);

To monitor more than one kind of key, add the SelectionKey class variables together. For example:

Selector spy = Selector.open();
channel.register(spy, SelectionKey.OP_READ + SelectionKey.OP_WRITE, null);

After the channel and selector have been set up, you can wait for events by calling the selector’s select() or select(long) methods.

The select() method is a blocking method that waits until something has happened on the channel.

The select(long) method is a blocking method that waits until something has happened or the specified number of milliseconds has passed, whichever comes first.

Both select() methods return the number of events that have taken place, or 0 if nothing has happened. You can use a while loop with a call to the select() method as a way to loop until something happens on the channel.

After an event has taken place, you can find out more about it by calling the selector’s selectedKeys() method, which returns a Set object containing details on each of the events.

Use this Set object as you would any other set, creating an Iterator to move through the set by using its hasNext() and next() methods.

The call to the set’s next() method returns an object that should be cast to a SelectionKey object. This object represents an event that took place on the channel.

Three methods in the SelectionKey class can be used to identify the key in a client program: isReadable(), isWritable(), and isConnectible(). Each returns a boolean value. (A fourth method is used when you’re writing a server: isAcceptable().)

After you retrieve a key from the set, call the key’s remove() method to indicate that you are going to do something with it.

The last thing to find out about the event is the channel on which it took place. Call the key’s channel() method, which returns the associated SocketChannel.

If one of the events identifies a connection, you must make sure that the connection has been completed before using the channel. Call the key’s isConnectionPending() method, which returns true if the connection is still in progress and false if it is complete.

To deal with a connection that is still in progress, you can call the socket’s finishConnect() method, which makes an attempt to complete the connection.

Using a nonblocking socket channel involves the interaction of numerous new classes from the java.nio and java.net packages.

To give you a more complete picture of how these classes work together, the day’s final project is NewFingerServer, a web application that uses a nonblocking socket channel to handle finger requests.

Enter the text of Listing 17.5, save it as NewFingerServer.java, and compile the application.

Example 17.5. The Full Text of NewFingerServer.java

  1: import java.io.*;
  2: import java.net.*;
  3: import java.nio.*;
  4: import java.nio.channels.*;
  5: import java.util.*;
  6:
  7: public class NewFingerServer {
  8:
  9:     public NewFingerServer() {
 10:         try {
 11:             // Create a nonblocking server socket channel
 12:             ServerSocketChannel sockChannel = ServerSocketChannel.open();
 13:             sockChannel.configureBlocking(false);
 14:
 15:             // Set the host and port to monitor
 16:             InetSocketAddress server = new InetSocketAddress(
 17:                 "localhost", 79);
 18:             ServerSocket socket = sockChannel.socket();
 19:             socket.bind(server);
 20:
 21:             // Create the selector and register it on the channel
 22:             Selector selector = Selector.open();
 23:             sockChannel.register (selector, SelectionKey.OP_ACCEPT);
 24:
 25:             // Loop forever, looking for client connections
 26:             while (true) {
 27:                 // Wait for a connection
 28:                 selector.select();
 29:
 30:                 // Get list of selection keys with pending events
 31:                 Set keys = selector.selectedKeys();
 32:                 Iterator it = keys.iterator();
 33:
 34:                 // Handle each key
 35:                 while (it.hasNext()) {
 36:
 37:                      // Get the key and remove it from the iteration
 38:                      SelectionKey selKey = (SelectionKey) it.next();
 39:
 40:                      it.remove();
  41:                      if (selKey.isAcceptable()) {
 42:
 43:                           // Create a socket connection with the client
 44:                           ServerSocketChannel selChannel =
 45:                               (ServerSocketChannel) selKey.channel();
 46:                           ServerSocket selSocket = selChannel.socket();
 47:                           Socket connection = selSocket.accept();
 48:
 49:                           // Handle the finger request
 50:                           handleRequest(connection);
 51:                           connection.close();
 52:                      }
 53:                 }
 54:              }
 55:          } catch (IOException ioe) {
 56:              System.out.println(ioe.getMessage());
 57:          }
 58:      }
 59:
 60:     private void handleRequest(Socket connection) throws IOException {
 61:
 62:         // Set up input and output
 63:         InputStreamReader isr = new InputStreamReader (
 64:             connection.getInputStream());
 65:         BufferedReader is = new BufferedReader(isr);
 66:         PrintWriter pw = new PrintWriter(new
 67:             BufferedOutputStream (connection.getOutputStream()),
 68:             false);
 69:
 70:         // Output server greeting
 71:         pw.println("Nio Finger Server");
 72:         pw.flush();
 73:
 74:        // Handle user input
 75:        String outLine = null;
 76:        String inLine = is.readLine();
 77:
 78:        if (inLine.length() > 0) {
 79:            outLine = inLine;
 80:        }
 81:        readPlan(outLine, pw);
 82:
 83:       // Clean up
 84:       pw.flush();
 85:       pw.close();
 86:       is.close();
 87:     }
 88:
 89:     private void readPlan(String userName, PrintWriter pw) {
 90:     try {
 91:         FileReader file = new FileReader (userName + ".plan");
 92:         BufferedReader buff = new BufferedReader(file);
 93:         boolean eof = false;
 94:
 95:         pw.println("
User name: " + userName + "
");
 96:
 97:         while (!eof) {
 98:             String line = buff.readLine();
 99:
100:             if (line == null)
101:                 eof = true;
102:             else
103:                pw.println(line);
104:          }
105:
106:          buff.close();
107:      } catch (IOException e) {
108:          pw.println("User " + userName + " not found.");
109:      }
110:   }
111:
112:   public static void main(String[] arguments) {
113:       NewFingerServer nio = new NewFingerServer();
114:   }
115: }

The finger server requires one or more user .plan files stored in text files. These files should have names that take the form username.plan—for example, linus.plan, lucy.plan, and franklin.plan. Before running the server, create one or more plan files in the same folder as NewFingerServer.class.

When you’re done, run the finger server with no arguments:

java NewFingerServer

The application waits for incoming finger requests, creating a nonblocking server socket channel and registering one kind of key for a selector to look for: connection events.

Inside a while loop that begins on line 25, the server calls the Selector object’s select() method to see whether the selector has received any keys, which would occur when a finger client makes a connection. When it has, select() returns the number of keys, and the statements inside the loop are executed.

After the connection is made, a buffered reader is created to hold a request for a .plan file. The syntax for the command is simply the username of the .plan file being requested.

Summary

Today, you learned how to use URLs, URL connections, and input streams in combination to pull data from the Web into your program.

Networking can be extremely useful. The WebReader project is a rudimentary web browser—it can load a web page or RSS file into a Java program and display it, although it doesn’t do anything to make sense of the markup tags, presenting the raw text delivered by a web server.

You created a socket application that implements the basics of the finger protocol, a method for retrieving user information on the Internet.

You also learned how client and server programs are written in Java using the nonblocking techniques in the java.nio package.

To use nonblocking techniques, you learned about the fundamental classes of Java’s new networking package: buffers, character encoders and decoders, socket channels, and selectors.

Q&A

Q

How can I do POST form submissions?

A

You can mimic what a browser does to send forms using POST. Create a URL object for the form-submission address, such as http://www.example.com/cgi/mail2.cgi, and then call this object’s openConnection() method to create a URLConnection object. Call the connection’s setDoOutput() method to indicate that you will be sending data to this URL and then send the connection a series of name-value pairs that hold the data, separated by ampersand characters ("&").

For instance, if the mail2.cgi form is a CGI program that sends mail with name, subject, email, and comments fields, and you have created a PrintWriter stream called pw connected to this CGI program, you can post information to it using the following statement:

pw.print("name=YourName&subject=Book&[email protected]&"
    + "comments= A+POST+example """);

Quiz

Review today’s material by taking this three-question quiz.

Questions

1.

Which of the following is not an advantage of the new java.nio package and its related packages?

  1. Large amounts of data can be manipulated quickly with buffers.

  2. Networking connections can be nonblocking for more reliable use in your applications.

  3. Streams are no longer necessary to read and write data over a network.

2.

In the finger protocol, which program makes a request for information about a user?

  1. The client

  2. The server

  3. Both can make that request.

3.

Which method is preferred for loading the data from a web page into your Java application?

  1. Creating a Socket and an input stream from that socket

  2. Creating a URL and an HttpURLConnection from that object

  3. Loading the page using the method toString()

Answers

1.

c. The java.nio classes work in conjunction with streams. They don’t replace them entirely.

2.

a. The client requests information, and the server sends something back in response. This is traditionally how client/server applications function, although some programs can act as both client and server.

3.

b. Sockets are good for low-level connections, such as when you are implementing a new protocol. For existing protocols such as HTTP, there are classes better suited to that protocol—URL and HttpURLConnection, in this case.

Certification Practice

The following question is the kind of thing you could expect to be asked on a Java programming certification test. Answer it without looking at today’s material or using the Java compiler to test the code.

Given:

import java.nio.*;

public class ReadTemps {
    public ReadTemps() {
        int[] temperatures = { 78, 80, 75, 70, 79, 85, 92, 99, 90, 85, 87 };
        IntBuffer tempBuffer = IntBuffer.wrap(temperatures);
        int[] moreTemperatures = { 65, 44, 71 };
        tempBuffer.put(moreTemperatures);
        System.out.println("First int: " + tempBuffer.get());
    }
}

What will be the output when this application is run?

  1. First int: 78

  2. First int: 71

  3. First int: 70

  4. None of the above

The answer is available on the book’s website at http://www.java21days.com. Visit the Day 17 page and click the Certification Practice link.

Exercises

To extend your knowledge of the subjects covered today, try the following exercises:

  1. Write an application that stores some of your favorite web pages on your computer so that you can read them while you are not connected to the Internet.

  2. Write a program that takes finger requests, looks for a .plan file matching the username requested, and sends it if found. Send a “user not found” message otherwise.

Where applicable, exercise solutions are offered on the book’s website at http://www.java21days.com.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset