Blakes 21 Days Chapter 17 Document
Day 17, Communicating Across the Internet
- Top
- 469 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.
- 469 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 creating sockets.
- 469 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.
- 469 The java.nio package expands Java's input and output classes.
- 469 Today you write networking Java programs that do each of the following:
- Load a document over the Web
- Mimic a popular Internet service
- Serve information to clients
Networking in Java
- Top
- 470 Networking allows different computers to make connections with each other and exchange information.
- In Java, basic networking is supported by classes in the java.net package,
- including support for connecting and retrieving files through Hypertext Transfer Protocol (HTTP)
- and File Transfer Protocol (FTP), as well as working at a lower level with sockets.
- 470 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).
- 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
- Top
- 470 As you learned during Day 15, "Working with Input and Output," you can pull information through a stream into your Java program in several ways.
- The classes and methods you choose depend on the format of the information and what you want to do with it.
- 470 One of the resources you can reach from your Java programs is a text document on the Web, whether it's an HTML file, XML file, or some other kind of plain-text document.
- 470 You can use a four-step process to load a text document off the Web and read it line by line:
- Create a URL object that represents the resource's web address.
- Create an HttpURLConnection object that can load the URL and make a connection to the site hosting it.
- Use the getContent() method of that HttpURLConnection object to create an InputStreamReader that can read a stream of data from the URL.
- Use that input stream reader to create a BufferReader object that can efficiently read characters from an input stream.
- Top
- 470 Much interaction occurs 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 deal with any exceptions that occur along the way adds more complexity to the process.
- 471 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.
- 471 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 program.
- 471 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.freebad.org".
- 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 filename or pathname.
- URL(String, String, String) is the same as the previous constructor minus the port number....
- Top
- 471 When you use the URL(String) constructor, you must deal with MalformedURLException exceptions,
- 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.samepublishing.com");
} catch (MalformedURLException e) {
System.out.println("Bad URL");
}
First Program - Listing 17.1
- Top | Back to Page 474
- 471 The WebReader application, shown 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.
- Create this class in NetBeans in the com.java21days package.
Listing 17.1 - The Full Text of WebReader.java
- Top
- page 471 - 472
package com.java21days;
import javax.swing.*;
import java.net.*;
import java.io.*;
public class WebReader extends JFrame {
JTextArea box = new JTextArea("Getting data ...");
public WebReader() {
super("Get File Application:);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600, 300);
JScrollPane pane = new JScrollPane(box);
add(pane);
setVisible(true);
}
void getData(String address) throws MalformedURLException {
setTitle(address);
URL page = new URL(address);
StringBuilder text = new StringBuilder();
try {
HttpURLConnection conn = (HttpURLConnection)
page.openConnection();
conn.connect();
InputStreamReader in = new InputStreamReader(
(InputStream) conn.getContent();
BufferedReader buff = new BufferedReader(in);
box.setText("Getting data ...");
String line;
do {
line = buff.readLine();
text.append(line);
text.append("\n");
} while (line != null);
box.setText(text.toString());
} catch (IOExecption ioe) {
System.out.println("IO Error:" + ioe.getMessage());
}
}
public static void main(String[] arguments) {
if (arguments.length < 1) {
System.out.println("Usage: java WebReader url");
System.exit(1);
}
try {
WebReader app = new WebReader();
app.getData(arguments[0]);
} catch (MalformedURLException mue) {
System.out.println("Bad URL: " + arguments[0]);
}
}
}
The Explanation
- Top
- 473 The WebReader application requires one command-line argument,
- a web address, that can be set in NetBeans in the project configuration (Run, Set Project Configuration, Customize).
- 473 You can choose any URL.
- Try http://www.timeapi.org/utc/now to read a simple text file that contains the current time, as shown in Figure 17.1
- I get cannot connect to server
Figure 17.1 - Running the WebReader application. - goes here
Blake's Figure - "Safari Can't Connect to the Server" message displayed in the browser.
- Top
- 473 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 is displayed in a text area in the getData() method.
- 473 Four objects are used: URL, HttpURLConnection. InputStreamReader, and BufferReader.
- 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 StringBuilder.
- 473 Lines 24 - 26 open an HTTP URL connection, which is necessary to get an input stream from that connection.
- 473 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.
- 473 Line 29 uses that input stream reader to create a new buffered input stream reader,
- a BufferedReader object called buff.
- 473 After you have this buffered reader, you can use its readLine() method to read aline 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.
- Top
- 473 The do-while loop in lines 32 - 36 reads the web document line by line. appending each line to the StringBuilder object created to hold the page's text.
- 473 After all the data has been read, line 37 converts the string builder into a string with the toString() method.
- Then it puts that result in the program's text area by calling the component's setText(String) method.
- Top
- 474 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 request) or 404 (for documents that could not be found).
- getResponseMessage() returns a string containing the HTTP response code and an explanatory message (such as "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.
- Top
- 474 The following code could be added to WebReader's getData() method after line 26 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);
text.append(header);
text.append("\n");
}
i++;
} while (header != null);
text.append("\n");
Sockets
- Top
- 475 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.
- 475 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);
- Top
- 475 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);
- 475 When you use 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.
- 475 If you don't set a timeout in a program that uses sockets, it might hang indefinitely, waiting for data.
- Top
- 475 TIP: This problem is usally avoided by putting network operations in their own thread
- and running them separately from the rest of the program, as covered during Day 7, "Exceptions and Threads."
- 475 After the socket is open, you can use input and output streams to read from and write to that socket:
BufferedInputStream bis = new
BufferedInputStream(connection.getInputStream());
DataInputStream in = new DataInputStream(bis);
BufferedOutputStream bos = new
BufferedOutputStream(connection.getOutputStream());
DataOutputStream out = new DataOutputStream(bos);
- 476 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()));
- 476 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.
- 476 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.
- Top
- 476 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();
- 476 Socket programming can be used for many services delivered using TCP/IP networking,
- including telnet,
- Simple Mail Transfer Protocol (SMTP) for incoming mail,
- WHOIS protocol for requesting domain name records,
- and Finger.
- 476 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.
- 476 Although it has fallen into disuse because of security concerns,
- Finger was once a popular way for Internet users to share facts about themselves and their activities before blogs and social media.
- You could use Finger on a friend's account at another college or company to see whether that person was online and read the person's current .plan file.
Second Program - Listing 17.2
- Top
- 476 As an exercise in socket programming, the Finger application is a rudimentary Finger client.
- Enter Listing 17.2 as a new class named Finger in NetBeans.
Listing 17.2 - The Full Text of Finger.java
- Top
- page 477
package com.java21days;
import javax.swing.*;
import java.net.*;
import java.util.*;
public class Finger {
public static void main(String[] args) {
String user;
String host;
if ((args.length == 1) && (args[0].indexOf("@") > -1)) {
StringTokenizer split = new StringTokenizer(args[0],
"@");
user = split.nextToken();
host = split.nextToken();
} else {
System.out.println("Usage: java Finger user@host");
return;
}
try {
BufferedReader in = new BufferedReader(
new InputStreamReader(digit.getInputStream()));
) {
digit.setSoTimeout(20000);
PrintStream out = new PrintStream(
digit.getOutputStream());
out.print(user + "\015\012");
boolean eof = false;
while (!eof) {
String line = in.readLine();
if (line != null) {
System.out.println(line);
} else {
eof = true;
}
}
digit.close();
} catch (IOExecption e) {
System.out.println("IO Error:" + e.getMessage());
}
}
}
An Explanation
- Top
- 477 When making a Finger request, specify a username followed by an at sign @ and a hostname, the same format as an email address.
- One of the last examples that still works is icculus@icculus.org, the Finger address of game developer Ryan Gordon.
- You can request his .plan file by running the Finger application with that address as the only command-line argument.
- 478 If icculus has an account on the icculus.org Finger server, running the Finger application displays his .plan file and perhaps other information.
- The server also lets you know when a user can't be found.
- 478 The output of this request is shown in Figure 17.2
Figure 17.2 - Making a Finger request using a socket. - goes here
- Top
- 478 The Finger application uses the StringTokenizer class to convert an address in user@host format into two String objects:
- user and host (lines 12 - 15).
- 478 The following socket activities are taking place:
- Line 20 - A new Socket is created using the hostname and port 79, the port traditionally reserved for Finger services.
- Line 21 - 23 - The soket is used to create an InputStream, which in turn is used to create a BufferedReader.
- Line 25 - A timeout of 20 seconds is set for the socket.
- Line 26 - 27 - The socket is used to get an OutputStream, which feeds into a new PrintStream object.
- Line 28 - The Finger protocol requires that the username be sent through the socket, followed by a carriage return (\015) and linefeed (\012).
- This is handled by calling the print() method of the new print stream.
- Lines 31 - 38 - 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.
- 479 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 20 and little other modification.
- Top
- 479 NOTE: The Finger application makes use of the try-with-resources capability of Java in lines 20 - 23 of Listing 17.
- Declaring the socket and reader within the try statement's parentheses
- ensures that both of these resources will be closed even when the connection fails with an exception.
- This makes the explicit call to close() the socket in Line 39 unecessary.
Socket Servers
- Top
- 479 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.
- 479 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 servc = new ServerSocket(8888);
- looks like this previous example:
Socket connection = new Socket(hostName, portNumber);
- 479 Use the accept() method to listen on that port (and accept a connection from any clients if one is made):
- Top
- 479 After the socket connection is made, you can use the input and output streams to read from and write to the client.
- 480 To extend the behavior of the socket class,
- for example, to allow network connections to work across a firewall or proxy,
- you can use the abstract class SockImpl and the interface SocketImplFactory to create a new transport-layer socket implementation.
- This approach allows 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 and from having multiple socket implementations for each Java runtime.
- Top
- 480 Because the Socket and ServerSocket classes are not final,
- 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
- Top
- 480 Here's an example of a Java program that uses the Socket classes to implement a simple network-based server application.
- 480 The TimeServer application makes a connection to any client that connects to port 4415, displays the current time, and then closes the connection.
- 480 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.
- Top
- 480 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 others are using.
- Search the Web for references to the port you want to use, and then search for the phrases "registered port numbers" and "well-known port numbers" to find lists of in-use ports.
- A good guide to port usage is available at www.sockets.com/services.htm.
- 480 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.
- 480 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.
- Top
- 481 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.)
Third Program - Listing 17.3
- 481 Listing 17.3 contains the full source code for the server application, a class called TimeServer.
LISTING 17.3 The Full Text of TimeServer.java
- Top
- page 481-482
package com.java21days;
import java.io.*;
import java.net.*;
import javax.util.*;
public class TimeServer extends Thread {
private ServerSocket sock:
public TimeServer() {
super();
try {
sock = new ServerSocket(4415);
System.out.println("TimeServer running ...");
} catch (IOException e) {
System.out.println("Error: couldn't create socket.");
}
}
public void run() {
Socket client = null;
while (true) {
if (sock == null)
return;
try {
client = sock.acept();
BufferedOutputStream bb = new BufferedOutputStream(
client.getOutputStream());
PrintWriter os = new PrintWriter(bb, false);
String outline;
Date now = new Date();
os.println(now);
os.flush();
os.close();
client.close();
} catch (IOException e) { <---------------
System.out.println("Error: couldn't connect.");
System.exit(1);
}
}
}
public static void main(String[] arguments) {
TimeServer server = new TimeServer();
server.start();
}
}
The Explanation
- Top
- 482 The TimeServer application creates a server socket on port 4415.
- When a client connects, a PrintWriter object is constructed from a buffered output stream so that a string, the current time, can be sent to the client.
- 482 After the string has been sent, the writer's flush() and close() methods end the data exchange and close the socket to await new connections.
Testing the Server
- Top
- 482 The TimeServer application must be running for a client to be able to connect to it.
- The server displays only one line of output if the application is running successfully, as shown in Figure 17.3
Figure 17.3 - Launching an Internet server in a ServerSocket. - goes here
- Top
- 482 With the server running, you can connect to the server on port 4415 of your computer using a telnet program.
- 482 Do the following to run telnet on Windows:
- With Windows 8 and Windows 10, choose Start, choose the Search Icon, and search for telnet.
- Click the telnet item to run it.
- With Windows 7 and Windows Vista, choose Start, All Programs, Accessories, Run to open the Run dialog box.
- Type telnet to run that program.
- Then type the command open localhost 4415 in the Open field and press Enter.
- With Windows XP and 2003, choose Start, Run to open the Run dialog box.
- Type telnet to run that program.
- Then type the command open localhost 4415 in the Open field and press Enter.
- With earlier versions of Windows, choose Start, Run to open the Run dialog box, and then type telnet in the Open field and press Enter.
- Top
- 483 To make a telnet connection using this program, select 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.
- Top
- 483 CAUTION: The telnet program may be disabled by default on Windows Vista and Windows 7.
- To enable it, open the Control Panel, choose Programs and Features, and click Turn Windows features on or off.
- The Windows Features dialog opens.
- Select the Telnet Client check box, and click OK.
- 483 The hostname localhost represents your own computer, the system running the application.
- You can use it to test server applications before deploying them permanently on the Internet.
- 483 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.
- 483 If the server is on another computer connected to the Internet, you would specify that computer's hostname or IP address rather than localhost.
- Top
- 483 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 resemble Figure 17.4
Figure 17.4 - Making a telnet connection to your TimeServer. - goes here
The java.nio Package
- Top
- 484 The java.nio package expands the language's networking capabilities with classes useful for
- reading and writing data
- working with files
- sockets
- memory
- and handling text.
- 484 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
- Top
- 484 The java.nio package includes support for buffers, objects that represent data streams stored in memory.
- 484 Buffers often are 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.
- 484 A buffer corresponds with each of the primitive data types in Java:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- Top
- 484 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.
- 484 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 };
IntBuffer tempBuffer = IntBuffer.wrap(temperatures);
- 484 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());
- Top
- 485 Another way to create a buffer is to set up an empty buffer and then put data in 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.
- 485 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.
- These methods are used with 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 in 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 in the buffer.
- Top
- 485 As you put data in the buffer, you often must keep track of the current position so that you know where the next data will be stored.
- 485 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.
- 485 Call the position(int) method to change the position to the argument specified as an integer.
- 485 Another important position to track when using buffers is the limit, the last place in the buffer that contains data.
- 485 It isn't necessary to figure out the limit when the buffer is always full; in that case, you know the buffer's last position has something in it.
- Top
- 485 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 the 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.
- Top
- 486 If the buffer is 1,024 bytes in size and the page contains 1,500 bytes,
- the first attempt to read the data loads the buffer with 1,024 bytes, filling it.
- 486 The second attempt to read the 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.
- 486 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());
}
- 486 After the buffer's position is set back to the start, the buffer's contents are displayed.
Byte Buffers
- Top
- 486 You can use the buffer methods introduced so far with byte buffers, but byte buffers also offer additional useful methods.
- 486 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 represents 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.
- 486 Each of these methods puts more than 1 byte in the buffer, moving the current position forward by the same number of bytes.
- 486 There also are methods to retrieve nonbytes from a byte buffer:
- getChar(), getDouble(), getFloat(), getInt(), getLong(), and getShort().
Character Sets
- Top
- 487 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.
- 487 The three main classes are as follows:
- Charset is a Unicode character set with a different byte value for each different character in the set.
- CharsetDecoder is a class that transforms a series of bytes into a series of characters.
- CharsetEncoder is a class that transforms a series of characters into a series of bytes.
- Top
- 487 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.
- 487 To create a character set, call the forName(String) static method of the Charset class,
- specifying the name of the set's character encoding.
- 487 Java supports 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 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 composed of 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
- Top
- 487 The following statement creates a Charset object for the ISO-8859-1 character set:
Charset isoset = Charset.forName("ISO-8859-1");
- 488 After you have a character set object, you can use it to create encoders and decoders.
- Call the object's newDecorder() method to create a CharsetDecorder and the newEncoder() method to create a CharsetEncoder.
- 488 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.
- 488 To transform a character buffer into a byte buffer, call the encoder's encode(CharBuffer) method.
- A ByteBuffer is returned containing the character's byte values.
- Top
- 488 The following statements convert a byte buffer called netBuffer into a character buffer using the ISO-8859-1 character set:
ByteBuffer netBuffer = ByteBuffer.allocate(20480);
// code to fill byte buffer would be here
Charset set = Charset.forName("ISO-8859-1");
CharsetDecoder decoder = set.newDecoder();
netBuffer.position(0);
CharBuffer netText = decoder.decode(netBuffer);
- Top
- 488 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 you're 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
- Top
- 488 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.
- 488 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.
- 488 You can associate channels with a stream by calling the getChannel() method available in some of the stream classes in the java.io package
- 488 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.
- 489 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());
}
- Top
- 489 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 file's contents.
- 489 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.
- 489 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() + " ");
}
- Top
- 489 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 a buffer so that you can modify it, you can allocate a larger buffer.
Fourth Program - Listing 17.4
- Top
- 489 The next project you undertake incorporates the new input/output features you have learned about so far: buffers, character sets, and channels.
- 489 The Bufferconverter application
- reads a small file into a byte buffer,
- displays the contents of the buffer,
- and then displays the characters.
- Top
- 489 Enter the code shown in Listing 17.4 as the new Java class BufferConverter in the com.java21days package.
LISTING 17.4 The Full Text of BufferConverter.java
- Top
- page 490
package com.java21days;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.io.*;
public class BufferConverter {
public static void main(String[] arguments) {
try {
// read byte data into a byte buffer
String data = "friends.dat";
FileInputStream inData = new FileInputStream(data);
FileChannel inChannel = inData.getChannel();
long inSize = inChannel.size();
ByteBuffer source = ByteBuffer.allocate( (int) inSize );
inChannel.read(source, 0);
source.position(0);
System.out.println("Original byte data:");
for (int i = 0; source.remaining() > 0; i++) {
System.out.print(source.get() + " ");
}
// convert byte data into character data
source.position(0);
Charset ascii = Charset.forName("US-ASCII");
CharsetDecoder toAscii = ascii.newDecoder();
CharBuffer destination = toAscii.decode(source);
destination.position(0);
System.out.println("\n\nNew character data:");
for (int i = 0; destination.remaining() > 0; i++) {
System.out.print(destination.get());
}
System.out.println();
} catch (FileNotFoundException fne) {
System.out.println(fne.getMessage());
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
}
}
}
The Explanation
- Top
- 490 Before you run 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 www.java21days.com, open the Day 17 page,
- right-click the friends.dat hyperlink, and save the file in a folder on your computer.
- 491 To copy that file into the same place as the application, follow these steps in NetBeans:
- In the folder where you downloaded friends.dat, right-click the file and choose Copy.
- In NetBeans, click the Files pane to bring it to the front.
- Right-click Java21 ( the top folder in the pane) and choose Paste.
- 491 The file will be stored in the project's main folder.
- Top
- 491 TIP: You also can create your own file. In NetBeans, choose File, New File.
- In the New File dialog, choose the category Other and the file type Empty File.
- Give it the file name friends.dat.
- In the source code editor, type a sentence or two in the document, and save the file.
- 491 If you use the copy of friends.dat from the book's website, the output of the BufferConverter application is shown in Figure 17.5
Figure 17.5 - Reading character data from a buffer. - goes here
- Top
- 491 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 original input/output package, java.io.
- 491 For this reason, you might wonder why it's worth learning new packages at all.
- 491 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
- Top
- 492 A popular feature of the java.nio package is its support for nonblocking input and output over a networking connection.
- 492 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() methodis called,
- nothing else happens in the program until a client makes a connection.
- 492 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.
- 492 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 be halted, because the blocked statement never finishes executing.
- Top
- 492 With the java.nio package, you can create networking connections and read to and write from them using nonblocking methods.
- 492 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 executing 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.
- 492 This is comparable to how user-interface components are programmed in Swing.
- An interface component is associated with one or more event listeners and is 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.
- 492 To use nonblocking input and output, you must work with channels instead of streams.
Nonblocking Socket Clients and Servers
- Top
- 493 The first step in developing a nonblocking client or server is creating an object that represents the Internet address to which you are connecting.
- This task is handled by the InetSocketAddress class in the java.net package.
- 493 If the server is identified by a hostname, call InetSocketAddress(String, int) with two arguments:
- the server's name and port number.
- 493 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 host's IP address 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.
- Top
- 493 Nonblocking connections require a socket channel, another of the classes in the java.nio package.
- Call the open() static method of the SocketChannel class to create the channel.
- 493 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.
- 493 After the channel is configured, call its connect(InetSocketAddress) method to connect the socket.
- Top
- 493 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.
- 493 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.
- 493 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 the SelectedChannel).
- 493 To create a Selector, call its open() method, as in the following statement:
Selector monitor = Selector.open();
- 493 When you use a Selector, you must indicate the events you want to monitor.
- You do so by calling a channel's register(Selector, int, Object) method.
- 493 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
- 494 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.
- Top
- 494 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);
- 494 To monitor more than one kind of key, add together the SelectionKey class variables.
- 494 For example:
Selector spy = Selector.open();
channel.register(spy, SelectionKey.OP_READ + SelectionKey.OP_WRITE,
null);
- Top
- 494 After the channel and selector have been set up, you can wait for events by calling the selector's select() or select(long) methods.
- 494 The select() method is a blocking method that waits until something has happened on the channel.
- 494 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.
- 494 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.
- 494 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.
- 494 Use this Set object as you would any other set, creating an Iterator to move thru the set by using its hasNext() and next() methods.
- 494 The call to the set's next() method returns an object that should be cast to a SelectionKey.
- This object represents an event that took place on the channel.
- 494 Three methods in the SelectionKey class can be used to identify the key in a client program:
- isReadable()
- isWritable()
- isConnectible()
- Each returns a boolean value.
- (A fourth method is used when you're writing a server: isAcceptable().)
- Top
- 495 After you retrieve a key from the set, call the key's remove() method to indicate that you will do something with it.
- 495 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.
- 495 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.
- 495 To deal with a connection that is still in progress, you can call the socket's finishConect() method, which attempts to complete the connection.
- 495 Using a nonblocking socket channel involves the interaction of numerous new classes from the java.nio and java.net packages.
Fifth Program - Listing 17.5
- Top
- 495 To give you a more complete picture of how these classes work together, the day's final project is FingerServer,
- a web application that uses a nonblocking socket channel to handle Finger requests.
- 495 Enter the code shown in Listing 17.5 as the class FingerServer in the package com.java21days and save the application.
LISTING 17.5 The Full Text of FingerServer.java
package com.java21days;
import java.io.*;
import java.net.*;
import java.nio.channels.*;
import java.util.*;
public class FingerServer {
public FingerServer() {
try {
// Create a non-blocking server socket channel
ServerSocketChannel sock = ServerSocketChannel.open();
sock.configureBlocking(false);
// Set the host and port to monitor
InetSocketAddress server = new InetSocketAddress(
"localhost", 79);
ServerSocket socket = sock.socket();
socket.bind(server);
// Create the selector and register it on the channel
Selector selector = Selector.open();
sock.register(selector, SelectionKey.OP_ACCEPT);
// Loop forever, looking for client connections
while (true) {
// Wait for a connection
selector.select();
// Get list of selection keys with pending events
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
// Handle each key
while (it.hasNext()) {
// Get the key and remove it from the iteration
SelectionKey sKey = (SelectionKey) it.next();
it.remove();
if (sKey.isAcceptable()) {
// Create a socket connection with client
ServerSocketChannel selChannel =
(ServerSocketChannel) sKey.channel();
ServerSocket sSock = selChannel.socket();
Socket connection = sSock.accept();
// Handle the Finger request
handleRequest(connection);
connection.close();
}
}
}
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
}
}
private void handleRequest(Socket connection)
throws IOException {
// Set up input and output
InputStreamReader isr = new InputStreamReader (
connection.getInputStream());
BufferedReader is = new BufferedReader(isr);
PrintWriter pw = new PrintWriter(new
BufferedOutputStream(connection.getOutputStream()),
false);
// Output server greeting
pw.println("Nio Finger Server");
pw.flush();
// Handle user input
String outLine = null;
String inLine = is.readLine();
if (inLine.length() > 0) {
outLine = inLine;
}
readPlan(outLine, pw);
// Clean up
pw.flush();
pw.close();
is.close();
}
private void readPlan(String userName, PrintWriter pw) {
try {
FileReader file = new FileReader(userName + ".plan");
BufferedReader buff = new BufferedReader(file);
boolean eof = false;
pw.println("\nUser name: " + userName + "\n");
while (!eof) {
String line = buff.readLine();
if (line == null) {
eof = true;
} else {
pw.println(line);
}
}
buff.close();
} catch (IOException e) {
pw.println("User " + userName + " not found.");
}
}
public static void main(String[] arguments) {
FingerServer nio = new FingerServer();
}
}
The Explanation
- Top
- 497 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 root folder of the Java21 project.
- 498 When you're done, run the Finger server.
- 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.
- 498 Inside a while loop that begins on Line 27,
- 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.
- 498 After the connection is made, a buffered reader is created to hold a request for a .plan file.
- The syntax for the command is the username of the .plan file being requested.
- 498 While the Finger server is running, you can test this application with the Finger client.
- Create a custom project configuration in NetBeans to set the command-line arguments of the Finger user:
- Choose Run, Set Project Configuration, Customize. The Project Properties dialog opens.
- In the Main Class text field, enter Finger.
- In the Arguments text field, enter franklin@localhost, and click OK.
- Run the application by choosing Run, Run Project.
- 498 The output is shown in Figure 17.6 when you request the user franklin on the computer localhost.
FIGURE 17.6 - Making a Finger request from your Finger server. - goes here
- Top
- 498 Run the application again with lucy@localhost to see Lucy's .plan file, and finally with linus@localhost to look for Linus.
- 498 When you're done with the Finger server, press the Stop button on the left edge of the Output pane to shut it down.
- 499 CAUTION: The plan files must be in the root folder of the Java21 project for the FingerServer application to find them.
- If they were saved somewhere else, you can move them by dragging and dropping in NetBeans.
- Click the Files tab in the Projects pane to see a list of the project's files.
- Find the plan files, and drag them to the same folder that holds friends.dat.
Summary
- Top
- 499 Today you learned how to use URLs, URL connections, and input streams in combination to pull data from the Web into your program.
- 499 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.
- However, it doesn't do anything to make sense of the markup tags, presenting the raw text delivered by a web server.
- 499 You created a socket application that implements the basics of the Finger protocol, a method for retrieving user information on the Internet.
- 499 You also learned how client and server programs are written in Java using the nonblocking techniques in the java.nio package.
- 499 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
- Top
- Q Can other computers connect to my Finger server over the Internet ?
- A Probably not.
- Most computers have firewall settings and router security settings that would not accept incoming requests on port 79,
- the one used by the Finger protocol.
- If you create a server that isn't just for testing purposes,
- you must figure out how to configure the firewall and router to allow the server to access all the ports that it requires.
- Because Internet servers are frequent targets of attack,
- you must make sure your server can handle malformed client requests and other hacking attempts.
- You also should run the server with a user account that only has access to the files and system resources it needs and no others.
- This prevents a hacker from compromising the server and using it to read confidential data,
- infect the computer with viruses, and launch other harmful exploits.
Quiz - Questions
- Top | 500 Review today's material by taking this three-question quiz.
- Which of the following is not an advantage of the new java.nio package and its related packages ?
- Large amounts of data can be manipulated quickly with buffers.
- Networking connections can be nonblocking for more reliable use in your applications.
- Streams are no longer necessary to read and write data over a network.
- In the Finger protocol, which program makes a request for information about a user ?
- The client
- The server
- Both can make that request.
- Which method is preferred for loading the data from a web page into your Java application ?
- Creating a Socket and an input stream from that socket
- Creating a URL and an HttpURLConnection from that object
- Loading the page using the method toString()
Answers
- Top | Note A is 1, B is 2, and C is 3
- C. The java.nio classes work in conjunction with streams. They don't replace them.
- A. The client requests information, and the server sends back something in response.
- This is traditionally how client/server applications function, although soem programs can act as both client and server.
- B. Sockets are good for low-level connections, such as when you are implementing a new protocol.
- For existing protocols such as HTTP, some classes are better suited to that protocol - URL and HttpURLConnection, in this case.
Certification Practice
- Top
- 501 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.
- The answer is available on the book's website at www.java21days.com
- Given:
import java.nio.*
public class ReadTemps {
public ReadTemps() {
int[] temperatures = { 78, 80, 75, 70, 79, 85, 92, 99, 90 };
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 ?
- First int: 78
- First int: 71
- First int: 70
- None of the above
Exercise
- Top
- 501 To extend your knowledge of the subjects covered today, try the following exercises:
- 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.
- Modify the FingerServer application to use the try-with-resources improvement to try-catch blocks in Java.