Core Java Technologies Tech Tips Tips, Techniques, and Sample Code 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. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/developer/JDCTechTips/2004/tt0914.html 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 (http://java.sun.com/developer/JDCTechTips/2003/tt0909.html#1) and programming with buffers (http://java.sun.com/developer/JDCTechTips/2002/tt0604.html#tip2). 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 (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" (http://java.sun.com/j2se/1.4.2/docs/guide/security/jsse/JSSERefGuide.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OTHER RESOURCES Got a question about Java technologies, tools, or resources? Then join these upcoming chats: Sept. 23. 9:00 A.M. PDT/16:00 UTC Chat With Sun's Chief Web Strategist, Mark Hapner (https://see.sun.com/Apps/DCS/mcp?q=ST4hmQTFvkJhgw) Sept. 28 11:00 A.M. PDT/6:00 P.M. UTC java.net (http://java.sun.com/developer/community/chat/JavaLive/index.html) . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developers.sun.com/dispatcher.jsp?uid=6910008 * FEEDBACK Comments? Please enter your feedback on the Tech Tips at: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE 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 JDC publications: - Go to the JDC Newsletters and Publications page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Core Java Technologies Tech Tips archives at: http://java.sun.com/developer/TechTips/index.html - COPYRIGHT Copyright 2004 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html Core Java Technologies Tech Tips September 14, 2004 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.