12.1 An Example Client
Although the
new I/O APIs aren't specifically designed for
clients, they do work for them. I'm going to begin
with a client program using the new I/O APIs because
it's a little simpler. In particular, many clients
can be implemented with one connection at a time, so I can introduce
channels
and buffers before talking about selectors and non-blocking I/O.To demonstrate the basics, I'll implement a simple
client for the character generator protocol defined in RFC
864. This protocol is designed for testing clients. The server
listens for connections on port 19. When a client connects, the
server sends a continuous sequence of characters until the client
disconnects. Any input from the client is ignored. The RFC does not
specify which character sequence to send, but recommends that the
server use a recognizable pattern. One common pattern is rotating,
72-character carriage return/linefeed delimited lines of the 95 ASCII
printing characters, like this:
!"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghI picked this protocol for the examples in this chapter because both
"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm
the protocol for transmitting the data and the algorithm to generate
the data are simple enough that they won't obscure
the I/O. However, chargen can transmit a lot of data over a
relatively few connections and quickly saturate a network connection.
It's thus a good candidate for the new I/O APIs.When
implementing a client that takes advantage of Java
1.4's new I/O APIs, begin by invoking the static
factory method SocketChannel.open() to create a new
java.nio.channels.SocketChannel object. The
argument to this method is a
java.net.SocketAddress object indicating the host
and port to connect to. For example, this fragment connects the
channel to SocketAddress rama = new InetSocketAddress(", 19);
SocketChannel client = SocketChannel .open(rama);The channel is opened in blocking mode, so the next line of code
won't execute until the connection is established.
If the connection can't be established, an
IOException is thrown.If this were a traditional client, you'd now ask for
the socket's input and/or output streams. However,
it's not. With a
channel you write directly to the channel
itself. Rather than writing byte arrays, you write
ByteBuffer objects.
We've got a pretty good idea that the lines of text
are 74 ASCII characters long (72 printable characters followed by a
carriage return/linefeed pair) so we'll create a
ByteBuffer that has a 74-byte capacity using the
static allocate( ) method:
ByteBuffer buffer= ByteBuffer.allocate(74);Pass this ByteBuffer object to the
channel's read( ) method. The
channel fills this buffer with the data it reads from the socket. It
returns the number of bytes it successfully read and stored in the
buffer:
int bytesRead = client.read(buffer);By default, this will read at least one byte or return -1 to indicate
the end of the data, exactly as an InputStream
does. It will often read more bytes if more bytes are available to be
read. Shortly you'll see how to put this client in
non-blocking mode where it will return 0 immediately if no bytes are
available, but for the moment this code blocks just like an
InputStream. As you could probably guess, this
method can also throw an IOException if anything
goes wrong with the read.Assuming there is some data in the bufferthat is, n >
0this data can be copied to System.out.
There are ways to extract a byte array from a
ByteBuffer that can then be written on a
traditional OutputStream such as
System.out. However, it's more
informative to stick with a pure, channel-based solution. Such a
solution requires wrapping the OutputStream
System.out in a channel using the
Channels utility class, specifically,
its newChannel( ) method:
WritableByteChannel output = Channels.newChannel(System.out);You can then write the data that was read onto this output channel
connected to System.out. However, before you do
that you have to flip the
buffer
so that the output channel starts from the beginning of the data that
was read rather than the end:
buffer.flip( );You don't have to tell the output channel how many
output.write(buffer);
bytes to write. Buffers keep track of how many bytes they contain.
However, in general, the output channel is not guaranteed to write
all the bytes in the buffer. In this specific case, though,
it's a blocking channel and it will either do so or
throw an IOException.You shouldn't create a new buffer for each read and
write. That would kill the performance. Instead, reuse the existing
buffer.
You'll need to clear the buffer before reading into
it again:
buffer.clear( );This is a little different than flipping. Flipping leaves the data in
the buffer intact, but prepares it for writing rather than reading.
Clearing resets the buffer to a pristine state.[2][2] Actually that's a tad simplistic. The old data
is still present. It's not overwritten, but it will
be overwritten with new data read from the source as soon as
possible.
Example 12-1 puts this together into a complete
client. Because chargen is by design an endless protocol,
you'll need to kill the program using Ctrl-C.
Example 12-1. A channel-based chargen client
import java.nio.*;Here's the output from a sample run:
import java.nio.channels.*;
import java.net.*;
import java.io.IOException;
public class ChargenClient {
public static int DEFAULT_PORT = 19;
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Usage: java ChargenClient host [port]");
return;
}
int port;
try {
port = Integer.parseInt(args[1]);
}
catch (Exception ex) {
port = DEFAULT_PORT;
}
try {
SocketAddress address = new InetSocketAddress(args[0], port);
SocketChannel client = SocketChannel.open(address);
ByteBuffer buffer = ByteBuffer.allocate(74);
WritableByteChannel out = Channels.newChannel(System.out);
while (client.read(buffer) != -1) {
buffer.flip( );
out.write(buffer);
buffer.clear( );
}
}
catch (IOException ex) {
ex.printStackTrace( );
}
}
}
$ java ChargenClientSo far, this is just an alternate vision of a program that could have
!"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg
!"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm
...
easily been written using streams. The really new feature comes if
you want the client to do something besides copying all input to
output. You can run this connection in either blocking or
non-blocking mode in which read( ) returns
immediately even if no data is available. This allows the program to
do something else before it attempts to read. It
doesn't have to wait for a slow network connection.
To change the blocking mode, pass true (block) or
false (don't block) to the
configureBlocking( ) method.
Let's make this connection non-blocking:
client.configureBlocking(false);In non-blocking mode, read() may return 0 because it doesn't read
anything. Therefore the loop needs to be a little different:
while (true) {
// Put whatever code here you want to run every pass through the loop
// whether anything is read or not
int n = client.read(buffer);
if (n > 0) {
buffer.flip( );
out.write(buffer);
buffer.clear( );
}
else if (n == -1) {
// This shouldn't happen unless the server is misbehaving.
break;
}
}There's not a lot of call for this in aone-connection client like this one. Perhaps you could check to see
if the user has done something to cancel input, for example. However,
as you'll see in the next section, when a program is
processing multiple connections, this enables code to run very
quickly on the fast connections and more slowly on the slow ones.
Each connection gets to run at its own speed without being held up
behind the slowest driver on the one-lane road.
• Table of Contents• Index• Reviews• Reader Reviews• Errata• AcademicJava Network Programming, 3rd EditionBy
Elliotte Rusty Harold Publisher: O'ReillyPub Date: October 2004ISBN: 0-596-00721-3Pages: 706
Thoroughly revised to cover all the 100+ significant updates
to Java Developers Kit (JDK) 1.5, Java Network
Programming is a complete introduction to
developing network programs (both applets and applications)
using Java, covering everything from networking fundamentals
to remote method invocation (RMI). It includes chapters on
TCP and UDP sockets, multicasting protocol and content
handlers, servlets, and the new I/O API. This is the
essential resource for any serious Java developer.