|
Welcome to the Core Java Technologies Tech Tips for September 14, 2004. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).
This issue covers:
Working with Selectors
SSL Servers
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.2.
This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.
See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.
For more Java technology content, visit these sites:
java.sun.com - The latest Java platform releases, tutorials, and newsletters.
java.net - A web forum for collaborating and building solutions together.
java.com - The marketplace for Java technology, applications and services.
WORKING WITH SELECTORS
Earlier Tech Tips explored various aspects of the NIO libraries, such as working with socket channels and programming with buffers. The following tip moves on to the multiplexed I/O aspects of channels. Instead of creating a new thread for each new socket connection (channel), you have one or more threads interact with all channels. You register interest in certain operations. Then an available thread responds to an operation of interest before waiting for the next task. This differs from the more typical model of one thread for each socket channel. In the multiplexed I/O model, each thread isn't waiting for an operation on a specific socket channel. Instead, a thread can monitor several channels at once and handle each when there is a ready set to process on the channel.
The basis of multiplexed I/O is the abstract Selector class. You register socket channels with the Selector and tell the Selector which operations you are interested in. You then have a thread that waits on the Selector for one of the registered operations to occur. The thread is notified when registered operations happen. The thread can then process those operations, and wait for the next ready batch. Let's look at each of these steps.
To create a Selector object, you call its open method (you don't call its constructor):
Selector selector = Selector.open();
This creates an instance of a platform-specific subclass that is hidden. For instance, on a Windows platform, the class is sun.nio.ch.WindowsSelectorImpl.
To register with the Selector, you call the register method of either the ServerSocketChannel or SocketChannel:
ServerSocketChannel serverSocketChannel = ...
serverSocketChannel.
configureBlocking(false);
serverSocketChannel.
register(selector, SelectionKey.OP_ACCEPT);
or
SocketChannel socketChannel = ...
serverSocketChannel.
configureBlocking(false);
serverSocketChannel.
register(selector, SelectionKey.OP_READ);
Notice the last parameter to the register method. This is a key that identifies the operations you want a thread to wait for. The OP_ACCEPT key specifies that the server should wait to accept a client connection. The OP_READ key specifies that after the server accepts a client connection, it should wait for something to read. Other operations that can be specified are OP_CONNECT and OP_WRITE.
After registration with the Selector, most programs have a processing loop similar to this:
while (true) {
int count = selector.select();
if (count == 0) {
continue;
}
Set keys = selector.selectedKeys();
Iterator itor = keys.iterator();
while (itor.hasNext()) {
SelectionKey selectionKey = (SelectionKey)itor.next();
selectionKey.remove();
// process channel from key here
}
}
In your processing thread, use select to wait for the Selector to have something to process. The select method reports the number of keys whose corresponding channels are ready for I/O operations. Typically the number is non-zero, but if multiple threads are processing requests from the Selector, it could be zero. Next, get the set of keys ready to be processed. This set of keys is also known as the ready set or selected set. Anything registered with the Selector that has waiting data will be part of this ready set. After getting the ready set, iterate through the keys, removing and processing each one as you go.
For each key you process, you can check its status with a method like isAcceptable or isReadable. This tells you what operation the key is waiting to be processed. For instance, if the server had an acceptable connection from the client, you then get the socket channel from the connection and register it with the Selector for OP_READ operations. Ignoring the exception handling, the code block for this operation looks like the following:
if (selectionKey.isAcceptable()) {
socket = serverSocket.accept();
channel = socket.getChannel();
if (channel != null) {
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
}
If an exception happens while processing a key, you want to tell the key to cancel, for example:
} catch (SomeException e) {
selectionKey.cancel();
}
This invalidates the key and its associated connection.
To demonstrate, what follows is the code for an "echo" server. It sends back to the client whatever data the client sends to it. The program puts together all the pieces previously described in this tip.
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
public class SelectorTest {
private static int PORT = 9876;
private static int BUFFER_SIZE = 1024;
public static void main (String args[]) {
ByteBuffer sharedBuffer =
ByteBuffer.allocateDirect(BUFFER_SIZE);
Selector selector = null;
ServerSocket serverSocket = null;
try {
ServerSocketChannel serverSocketChannel =
ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocket = serverSocketChannel.socket();
InetSocketAddress inetSocketAddress =
new InetSocketAddress(PORT);
serverSocket.bind(inetSocketAddress);
selector = Selector.open();
serverSocketChannel.register(
selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
System.err.println("Unable to setup environment");
System.exit(-1);
}
try {
while (true) {
int count = selector.select();
// nothing to process
if (count == 0) {
continue;
}
Set keySet = selector.selectedKeys();
Iterator itor = keySet.iterator();
while (itor.hasNext()) {
SelectionKey selectionKey =
(SelectionKey) itor.next();
itor.remove();
Socket socket = null;
SocketChannel channel = null;
if (selectionKey.isAcceptable()) {
System.out.println("Got acceptable key");
try {
socket = serverSocket.accept();
System.out.println
("Connection from: " + socket);
channel = socket.getChannel();
} catch (IOException e) {
System.err.println("Unable to accept channel");
e.printStackTrace();
selectionKey.cancel();
}
if (channel != null) {
try {
System.out.println
("Watch for something to read");
channel.configureBlocking(false);
channel.register
(selector, SelectionKey.OP_READ);
} catch (IOException e) {
System.err.println("Unable to use channel");
e.printStackTrace();
selectionKey.cancel();
}
}
}
if (selectionKey.isReadable()) {
System.out.println("Reading channel");
SocketChannel socketChannel =
(SocketChannel) selectionKey.channel();
sharedBuffer.clear();
int bytes = -1;
try {
while (
(bytes = socketChannel.read(sharedBuffer)) > 0)
{
System.out.println("Reading...");
sharedBuffer.flip();
while (sharedBuffer.hasRemaining()) {
System.out.println("Writing...");
socketChannel.write(sharedBuffer);
}
sharedBuffer.clear();
}
} catch (IOException e) {
System.err.println("Error writing back bytes");
e.printStackTrace();
selectionKey.cancel();
}
try {
System.out.println("Closing...");
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
selectionKey.cancel();
}
}
System.out.println("Next...");
}
}
} catch (IOException e) {
System.err.println("Error during select()");
e.printStackTrace();
}
}
}
This server will watch on port 9876. Feel free to change the port, or prompt for it from the command line.
Here's a client that tests this server. The client simply connects to the server on localhost, port 9876, and sends the string "Hello, World" ten times. The client waits half a second between each send.
import java.net.*;
import java.io.*;
public class Connect {
private static final int LOOP_COUNT = 10;
private static final int SLEEP_TIME = 500;
private static final int PORT = 9876;
public static void main(String args[])
throws IOException, InterruptedException {
for (int i=0; i<LOOP_COUNT; i++) {
Socket socket = new Socket("localhost", PORT);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
Writer writer = new OutputStreamWriter(os, "US-ASCII");
PrintWriter out = new PrintWriter(writer, true);
out.println("Hello, World");
BufferedReader in =
new BufferedReader
(new InputStreamReader(is, "US-ASCII"));
String line;
while ((line = in.readLine()) != null) {
System.out.println(i + ": " + line);
}
socket.close();
Thread.sleep(SLEEP_TIME);
}
}
}
Start the echo server. Then run the client program. You should see the following displayed by the server ten times:
Got acceptable key
Connection from: Socket[addr= ...]
Watch for something to read
Next...
Reading channel
Reading...
Writing...
Closing...
Next...
You should see the following displayed by the client:
0: Hello, World
1: Hello, World
2: Hello, World
3: Hello, World
4: Hello, World
5: Hello, World
6: Hello, World
7: Hello, World
8: Hello, World
9: Hello, World
Using multiplexing channels makes for more robust and expandable server software. Although you can still use thread pools to process all requests on popular systems, in multiplexed I/O, each thread is not limited to handling only one client request. As an operation becomes available for processing, it is handled directly. If there are delays between accepting and reading, no thread has to wait, sitting idle.
For additional information about the NIO libraries, see New I/O APIs. That documentation includes another working, non-blocking server in the form of a time server.
SSL SERVERS
In last month's Tech Tip Secure Communications with JSSE, you learned how to handle secure HTTP requests and responses, (also known as through HTTPS or HTTP over SSL), from the client side. In the following tip, you'll learn about the server-side part of SSL communications.
As was the case for the first tip in this issue, Working with Selectors, the server in the following tip will be an echo server, that is, a server that simply returns to the client what it receives from the client.
Again, as was the case for the client-side tip, to create the server, you first get the socket factory. For an SSL server-side socket, the factory is of type SSLServerSocketFactory. The SSLServerSocketFactory is in the javax.net.ssl package.
ServerSocketFactory sslserversocketfactory =
SSLServerSocketFactory.getDefault();
You then get the server socket and wait for a connection with accept:
ServerSocket serverSocket =
serverSocketFactory.createServerSocket(PORT_NUM);
Socket socket = serverSocket.accept();
The rest of the server code is rather simple. Similar to the "SelectorTest" tip, you just read what the client sends you and send it back. Here's the complete server code:
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;
import java.net.*;
public class EchoServer {
private static final int PORT_NUM = 6789;
public static void main(String args[]) {
ServerSocketFactory serverSocketFactory =
SSLServerSocketFactory.getDefault();
ServerSocket serverSocket = null;
try {
serverSocket =
serverSocketFactory.createServerSocket(PORT_NUM);
} catch (IOException ignored) {
System.err.println("Unable to create server");
System.exit(-1);
}
while(true) {
Socket socket = null;
try {
socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(
new InputStreamReader(is, "US-ASCII"));
OutputStream os = socket.getOutputStream();
Writer writer =
new OutputStreamWriter(os, "US-ASCII");
PrintWriter out = new PrintWriter(writer, true);
String line = null;
while ((line = br.readLine()) != null) {
out.println(line);
}
} catch (IOException exception) {
exception.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException ignored) {
}
}
}
}
}
}
Before running this program though, there is one slight problem. You don't have a certificate. If you run the program without specifying a certificate, you'll get an SSLException:
javax.net.ssl.SSLException: No available certificate
corresponds to the SSL cipher suites which are enabled.
at com.sun.net.ssl.internal.ssl.SSLServerSocketImpl.a(DashoA6275)
at com.sun.net.ssl.internal.ssl.SSLServerSocketImpl.accept(DashoA6275)
at EchoServer.main(EchoServer.java:21)
You can generate a certificate with the keytool program that comes with the SDK. Issue the keytool command with the -genkey option to generate a keypair, the -keystore option to specify the key store file, and the -keyalg options to specify the encryption algorithm:
keytool -genkey -keystore testStore -keyalg RSA
In response, you'll be prompted for information. Supply the information, as appropriate. For example, here's what the dialog might look like:
Enter keystore password: tutorial
What is your first and last name?
[Unknown]: Sun Tutorial
What is the name of your organizational unit?
[Unknown]: Sun
What is the name of your organization?
[Unknown]: Sun
What is the name of your City or Locality?
[Unknown]: Santa Clara
What is the name of your State or Province?
[Unknown]: CA
What is the two-letter country code for this unit?
[Unknown]: US
Is CN=Sun Tutorial, OU=Sun, O=Sun, L=Santa Clara,
ST=CA, C=US correct?
[no]: yes
Enter key password for <mykey>
(RETURN if same as keystore password):
Typically the CN entry (first and last name) will be the host name of the server, although it isn't an absolute requirement. In this example, because the client will access the server with localhost, that name should be used.
After running the command, you'll find a new file named testStore in your working directory. You can now run the server using SSL. When you issue the command to run the server, you identify the key store with the javax.net.ssl.keyStore property, and its password with the javax.net.ssl.keyStorePassword property:
java -Djavax.net.ssl.keyStore=testStore
-Djavax.net.ssl.keyStorePassword=tutorial EchoServer
Of course, there needs to be a client to talk to the server. The following client reads input from the command line, sends it to the server, and writes out the echoed response:
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;
import java.net.*;
public class EchoClient {
private static int PORT_NUM = 6789;
private static String host = "localhost";
public static void main(String args[])
throws IOException {
SocketFactory socketFactory =
SSLSocketFactory.getDefault();
Socket socket = socketFactory.createSocket(
host, PORT_NUM);
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in, "US-ASCII"));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream(), "US-ASCII"), true);
BufferedReader socketBr = new BufferedReader(
new InputStreamReader(
socket.getInputStream(), "US-ASCII"));
String string = null;
System.out.print("First line: ");
while (!(string = br.readLine()).equals("")) {
out.println(string);
String line = socketBr.readLine();
System.out.println("Got Back: " + line);
System.out.print("Next line: ");
}
socket.close();
}
}
Compile the client. To run the client, you need to specify the same certificate you specified when you started the server. Otherwise, you'll get an SSLHandshakeException in the server when the client attempts to connect to the server:
javax.net.ssl.SSLHandshakeException: Remote host closed
connection during handshake
When you start the client, you reference the certificate as a trust store, instead of a key store:
java -Djavax.net.ssl.trustStore=testStore
-Djavax.net.ssl.trustStorePassword=tutorial EchoClient
The client will now talk to the server over SSL.
First line: Hello
Got Back: Hello
Next line: There
Got Back: There
Next line: Server
Got Back: Server
Sharing of a certificate is easy on one machine. However, sharing a certificate over multiple machines requires getting the certificate to the foreign (client) machine. You could simply copy the "testStore" keystore, however that would reveal your private keys. Then anyone could masquerade as you. Instead, you can use the -export and -import options of the keytool program. You export the certificate from your keystore, which can be public. Then someone who trusts you imports the certificate into their machine. This can be done multiple times for multiple machines. After importing the certificate to the foreign machine, a client on that machine can communicate with you over SSL.
For additional information on using SSL, see Java Secure Socket Extension (JSSE) Reference Guide.
OTHER RESOURCES
Got a question about Java technologies or tools? Then join these upcoming chats and webinars:
Chats:
|
|
 |
 |
|
|
 |
 |
IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html
Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.
Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).
To subscribe to these and other Sun Developer Network publications:
- Go to the Sun Developer Network Subscriptions page, choose the newsletters you want to subscribe to and click
"Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/developer/JDCTechTips/index.html
Copyright 2004 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/developer/copyright.html
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.
|