IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
The easy way to non-blocked sockets
code81 KBe-mail it!
Contents:
Blocking and non-blocking communication
Traditional, non-blocking client socket
NIO, non-blocking client socket
Traditional, non-blocking server socket
Building an alternative non-blocking server socket
Creating an SSL connection
Creating a secure, non-blocking connection
Secure and non-secure connections
The simplest path to integration
Resources
About the author
Rate this article
Related content:
Merlin brings non-blocking I/O to the Java platform
The ins and outs of Merlin's new I/O buffers
Custom SSL for advanced JSSE developers
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Use JSSE and NIO for a quick way to implement non-blocking communications

Level: Intermediate

Kenneth Ballard (mailto:kenneth.ballard@ptk.org?cc=&subject=The easy way to non-blocked sockets)
Computer Science undergraduate, Peru State College
22 October 2003

Although SSL blocking operations -- in which the socket is blocked from access while data is being read from or written to -- provide better I/O-error notification than the non-blocking counterpart, non-blocking operations allow the calling thread to continue. In this article, the author will cover both the client and server side as he describes how to create non-blocking secure connections using the Java Secure Socket Extensions (JSSE) and the Java NIO (new I/O) library, and he will explain the traditional approach to creating a non-blocking socket, as well as an alternative (and necessary) method if you want to use JSSE with NIO.

To block, or not to block? That is the question. Whether 'tis nobler in the minds of programmers.... While not Shakespeare, this article brings up an important point that any programmer should consider when writing an Internet client. Should the communication operation be of the blocking or non-blocking variety?

Many programmers don't consider this question when writing an Internet client using the Java language, primarily because originally there was only one option -- blocking communication. But now a choice is available to Java programmers, so perhaps it should be considered for every client we write.

Non-blocking communication was introduced to the Java language with version 1.4 of the Java 2 SDK. If you've programmed using this version, you've probably come across the new I/O (NIO) library. Prior to its introduction, non-blocking communication could only be used if you implemented a third-party library, which often had the effect of introducing bugs into your application.

The NIO library includes non-blocking abilities for files, pipes, and client and server sockets. A feature the library is missing is secure non-blocking socket connections. There isn't a secure channel class built into the NIO or JSSE libraries, but that doesn't mean that you cannot have secure non-blocking communication. It just involves a little overhead.

To fully appreciate this article, you should be familiar with:

  • Java socket communication concepts. You should also have some practice writing applications. And not just the simple applications that open a connection, read a line, then quit; programs such as a client or a communication library that implements a protocol such as POP3 or HTTP.

  • SSL in general and such concepts as cryptography. Basically, know how to set up a secure connection (but don't worry about JSSE -- this will be a "crash course" on it).

  • The NIO library.

  • The Java 2 SDK 1.4 or later installed on the platform of your choice. (I'm working with version 1.4.1_01 on Windows 98.)

If you need instruction with any of these technologies, see the Resources section.

So what exactly is blocking and non-blocking communication?

Blocking and non-blocking communication
Blocking communication means that the communication method is blocking access to the socket while either trying to access the socket or read or write data. Prior to JDK 1.4, the way to get around these blocking limitations was to liberally use threads, but this often created immense thread overhead, which had an impact on the performance and the scalability of a system. The java.nio package changed the landscape, allowing servers to effectively use its I/O streams, handling several client requests in a reasonable amount of time.

With non-blocking communication, the process acts on what I like to call the "do what you can" concept. Basically, the process will send or read what it can. If nothing is available to be read, it aborts the read and the process can do something else until data is available. When sending data, the process will attempt to send all of the data, but will return what is actually sent. It can be all, some, or none of the data.

Blocking does have some advantages over non-blocking, especially when it comes to error control. In blocking socket communication, if any error results, the method automatically returns with a code identifying the error. The error could be due to a network timeout, a closed socket, or any type of I/O error. In non-blocking socket communication, the only error not processed by the method is a network timeout. To detect timeouts using non-blocking communication, a little more code must be written to determine how long it has been since data was last received.

Which type is better depends on the application. If you are using synchronous communication, blocking communication is better if part of the data does not have to be processed prior to everything being read while non-blocking communication will give the opportunity to process any data already read. Asynchronous communication, such as with IRC and chat clients, however, will require non-blocking communication to avoid tying up the socket.

Building a traditional, non-blocking, client socket
The Java NIO library uses channels instead of streams. The channels can use both blocking and non-blocking communication, but default to the non-blocking version when created. But all non-blocking communication goes through a class with Channel in its name. In the case of socket communication, the class SocketChannel is used, and the procedure for creating an object of this class is different from one used in a typical socket, as shown in Listing 1:

Listing 1. Creating and connecting a SocketChannel object

SocketChannel sc = SocketChannel.open();
sc.connect("www.ibm.com",80);
sc.finishConnect();

A pointer of the type SocketChannel must be declared, but you cannot use the new operator to create the object. Instead, a static method of the SocketChannel class must be called to open the channel. After the channel is opened, it can be connected by calling the connect() method. However, when this method returns, the socket is not necessarily connected. To be certain that it is, a follow-up call to finishConnect() must be made.

When the socket is connected, non-blocking communication can commence using the read() and write() methods of the SocketChannel class. Or you can cast the object into separate ReadableByteChannel and WritableByteChannel objects. Either way, you will be using Buffer objects for the data. Because use of the NIO library goes beyond the scope of this article, we won't go any further into this subject.

When the socket is no longer needed, it can be closed using the close() method:


sc.close();

This will close both the socket connection and the underlying communication channels.

Building an alternative, non-blocking, client socket
The previous method is slightly more complicated than the traditional route of creating a socket connection. However, the traditional route can be used to create a non-blocking socket, with a few added steps to enable non-blocking communication.

The underlying communication in a SocketChannel object involves two Channel classes: ReadableByteChannel and WritableByteChannel. These two classes can be created from existing blocking streams, InputStream and OutputStream respectively, using the newChannel() method in the Channels class, as shown in Listing 2:

Listing 2. Deriving channels from streams

ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());
WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());

The Channels class is also used to convert channels into streams or readers and writers. This might appear to switch the communication over to blocking mode, but it's not the case. If you were to attempt to read from a stream derived from a channel, the read method would throw an IllegalBlockingModeException.

The same applies in the opposite direction. You cannot use the Channels class to convert a stream to a channel and expect non-blocking communication. If you were to attempt a read from a channel derived from a stream, it would still be a blocking read. But like so many things in programming, there are exceptions to this rule.

The exception applies to classes that implement the SelectableChannel abstract class. SelectableChannel and its derivatives have the ability to select either blocking or non-blocking mode. SocketChannel is one such derivative.

However, in order to be able to switch back and forth between the two, the interface must be implemented as a SelectableChannel. With sockets, SocketChannel would need to be used instead of Socket to enable this ability.

Getting back on track, to create the socket, first build the socket as you normally would using an object of the Socket class. After the socket is connected, the streams are converted to channels using the two lines of code in Listing 2.

Listing 3. Alternate method for creating a socket

Socket s = new Socket("www.ibm.com", 80);
ReadableByteChannel rbc = Channels.newChannel(s.getInputStream());
WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream());

As I just explained, this doesn't implement non-blocking socket communication -- all communication will still be in blocking mode. Non-blocking communication must be emulated in this kind of setup. Not much more coding is required for this emulation layer. Let's take a look.

Reading data from the emulation layer
The emulation layer checks the availability of data prior to attempting a read operation. If data is available, it is read. If the data is not available, possibly because the socket was closed, it returns a code signifying such. Notice in Listing 4 that the ReadableByteChannel is still used for reading, though InputStream is perfectly capable of performing this action. The reason? To provide the illusion that NIO is performing the communication instead of the emulation layer. Plus it makes it much easier to have both the emulation layer and other channels together, for example, to write the data to a file channel.

Listing 4. Emulating a non-blocking read operation

/* The checkConnection method returns the character read when 
   determining if a connection is open.
 */

y = checkConnection();
if(y <= 0) return y;

buffer.putChar((char ) y);
return rbc.read(buffer); 

Writing data to the emulation layer
With non-blocking communication, write operations will write only what can be written. The size of the send buffer has a lot to do with how much data can be sent in one write operation. The buffer's size can be determined by calling the getSendBufferSize() method of the Socket object. The size must be taken into account when attempting a non-blocking write operation. If you try to write data larger than that block size in length, it must be broken into multiple write operations. A single, too-large write will be blocked.

Listing 5. Emulating a non-blocking write operation

int x, y = s.getSendBufferSize(), z = 0;
int expectedWrite;
byte [] p = buffer.array();
ByteBuffer buf = ByteBuffer.allocateDirect(y);

/* If there isn't any data to write, return, otherwise flush the stream */

if(buffer.remaining() == 0) return 0;
os.flush()

for(x = 0; x < p.length; x += y)
{
    if(p.length - x < y)
    {
        buf.put(p, x, p.length - x);
        expectedWrite = p.length - x;
    }
    else
    {
        buf.put(p, x, y);
        expectedWrite = y;
    }
    
    /* Check the status of the socket to make sure it's still open */
    
    if(!s.isConnected()) break;

    /* Write the data to the stream, flushing immediately afterward */

    buf.flip(); 
    z = wbc.write(buf); os.flush();
    if(z < expectedWrite) break;
    buf.clear();
    
}
if(x > p.length) return p.length;
else if(x == 0) return -1;
else return x + z;

Just like a read operation, first the socket needs to be checked to see if it is still connected. But if the data is written to a WritableByteBuffer object, such as in Listing 5, the object will automatically check this and throw the necessary exception if it is not connected. Following this action, and before the data is written, the stream should be immediately flushed to ensure there is room in the send buffer for sending data. The same applies for any write operation. The data is sent in blocks equal in length to the send buffer. Doing this flush will ensure that the send buffer doesn't get overrun, causing the write operation to block.

Because the write operation is supposed to write only what it can, the process must also check the socket to make sure it is still open after each block of data is written. If the socket ever closes while attempting to write the data, then the write operation must abort and return the amount of data it was able to send before the socket closed.

BufferedOutputReader cannot be used when emulating non-blocking writes. If you are attempting to write data that is more than twice the length of the buffer, all data up to a multiple of the buffer length is written directly (with the leftover data being buffered). For example, if the buffer length is 256 bytes and you're attempting to write 529 bytes, the object will flush the current buffer, send 512 bytes, then store the remaining 17.

For non-blocking writes, this is not what we want. Instead we want the data to be written in separate writes of the same block size with the ultimate goal of all data eventually being written. If the data is sent in one large block with a leftover amount being buffered, the write operation will block while all data is being sent.

Template of the emulation layer class
The entire emulation layer can be placed inside a class to allow for easy integration into an application. If this is to be done, I recommend that the class be derived from ByteChannel. The class can be cast from ByteChannel into separate ReadableByteChannel and WritableByteChannel objects.

Listing 6 illustrates an example class template for the emulation layer, derived from ByteChannel. This class will be used throughout the rest of this article to represent non-blocking operations across blocking connections.

Listing 6. Template of a class for the emulation layer

public class nbChannel implements ByteChannel
{
    Socket s;
    InputStream is; OutputStream os;
    ReadableByteChannel rbc;
    WritableByteChannel wbc;
    
    public nbChannel(Socket socket);
    public int read(ByteBuffer dest);
    public int write(ByteBuffer src);
    public void close();
    
    protected int checkConnection();
}

Creating a socket using the emulation layer
Using the new emulation layer to create a socket is very straightforward. Simply create the Socket object as you normally would, then create the nbChannel object, as shown in Listing 7:

Listing 7. Using the emulation layer

Socket s = new Socket("www.ibm.com", 80);
nbChannel socketChannel = new nbChannel(s);
ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
WritableByteChannel wbc = (WritableByteChannel)socketChannel;

Building a traditional, non-blocking, server socket
Non-blocking sockets on the server side aren't much different from ones on the client side. There's just a little more overhead into setting up the socket to accept incoming connections. The socket must be bound in blocking mode by deriving a blocking server socket from the server socket channel. The code segment in Listing 8 outlines how to do it.

Listing 8. Creating a non-blocking server socket (SocketChannel)

ServerSocketChannel ssc = ServerSocketChannel.open();
ServerSocket ss = ssc.socket();
ss.bind(new InetSocketAddress(port));
SocketChannel sc = ssc.accept();

As with a client socket channel, the server socket channel must be opened instead of using the new operator and a constructor. After it is opened, a server socket object must be derived in order to bind the socket channel to a port. Once the socket is bound, the server socket object can be discarded.

The channel accepts incoming connections using the accept() method and routes them to socket channels. Once an incoming connection is accepted and routed to a socket channel object, communication can commence through the read() and write() methods.

Building an alternative non-blocking server socket
Actually, this really isn't an alternative. Because the server socket channel must be bound using a server socket object, why not avoid the server socket channel completely and just use a server socket object? But instead of using SocketChannel for communication, it'll instead use the emulation layer nbChannel.

Listing 9. Alternate for setting up a server socket

ServerSocket ss = new ServerSocket(port);
Socket s = ss.accept();
nbChannel socketChannel = new nbChannel(s);
ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
WritableByteChannel wbc = (WritableByteChannel)socketChannel;

Creating an SSL connection
In creating an SSL connection, we need to look at it from the client side and from the server side.

From the client side
The traditional method of creating SSL connections involves the use of socket factories and a few other things. I won't go into too much detail on how to create an SSL connection, but there is a great tutorial entitled, "Secure your sockets with JSSE" (see Resources) that you can review for more information.

The default method of creating an SSL socket is simple and involves only a few short steps:

  1. Create the socket factory.
  2. Create the connected socket.
  3. Start the handshake.
  4. Derive the streams.
  5. Communicate.

Listing 10 illustrates these steps:

Listing 10. Creating a secure client socket

SSLSocketFactory sslFactory = 
  (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);
ssl.startHandshake();
InputStream is = ssl.getInputStream();
OutputStream os = ssl.getOutputStream();

The default method does not involve client authentication, custom certificates, and other things that might be necessary for certain connections.

From the server side
The traditional method of setting up SSL server connections requires a little overhead, plus a lot of type casting. Because this goes beyond the scope of this article, I won't go into great detail, but will instead talk about the default method of enabling SSL server connections.

Creating a default SSL server socket also involves a few short steps:

  1. Create the server socket factory.
  2. Create and bind the server socket.
  3. Accept an incoming connection.
  4. Start the handshake.
  5. Derive the streams.
  6. Communicate.

Although this sounds suspiciously similar to the steps on the client side, note that this removes a lot of security options, such as client authentication.

Listing 11 illustrates these steps:

Listing 11. Creating a secure server socket

SSLServerSocketFactory sslssf = 
  (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
SSLSocket ssls = (SSLSocket)sslss.accept();
ssls.startHandshake();
InputStream is = ssls.getInputStream();
OutputStream os = ssls.getOutputStream();

Creating a secure, non-blocking connection
We also need to look at both the client and the server side when we craft a secure, non-blocking connection.

From the client side
Setting up a secure non-blocking connection on the client side is straightforward:

  1. Create and connect the Socket object.
  2. Attach the Socket object to the emulation layer.
  3. Communicate through the emulation layer.

Listing 12 illustrates these steps:

Listing 12. Creating a secure client connection

/* Create the factory, then the secure socket */

SSLSocketFactory sslFactory = 
  (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);

/* Start the handshake.  Should be done before deriving channels */

ssl.startHandshake();

/* Put it into the emulation layer and create separate channels */

nbChannel socketChannel = new nbChannel(ssl);
ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
WritableByteChannel wbc = (WritableByteChannel)socketChannel;

Using the emulation layer class outlined previously, non-blocking secure communication becomes possible. Because a secure socket channel cannot be opened using the SocketChannel class and a class for this does not exist in the Java API, an emulating class was created. The emulating class will then allow for non-blocking communication when used with either a secure or non-secure socket connection.

The steps outlined involve a default setup for the security. For more advanced security, such as custom certificates and client authentication, the Resources section offers articles outlining how it can be done.

From the server side
Setting up a socket on the server side requires only a little more overhead for default security. But once the socket is accepted and routed, the setup becomes exactly the same as on the client side, as shown in Listing 13:

Listing 13. Creating a secure, non-blocking server socket

/* Create the factory, then the socket, and put it into listening mode */               
                
SSLServerSocketFactory sslssf = 
  (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
SSLSocket ssls = (SSLSocket)sslss.accept();

/* Start the handshake on the new socket */

ssls.startHandshake();

/* Put it into the emulation layer and create separate channels */

nbChannel socketChannel = new nbChannel(ssls);
ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
WritableByteChannel wbc = (WritableByteChannel)socketChannel;

Again, remember these steps use the default security setup.

Integrating secure and non-secure client connections
Most Internet client applications, whether written using the Java language or another one, need to provide for both secure and non-secure connections. The Java Secure Socket Extensions library makes this easy; it is a method I employed recently in writing an HTTP client library.

The SSLSocket class is derived from Socket. You can probably see where I'm going with this. All that is necessary is just one Socket pointer for the object. If the socket connection will not use SSL, the socket can be created as you would normally. If it will use SSL, then there's slightly more overhead, but after that, the code is straightforward. Listing 14 shows an example:

Listing 14. Integrating secure and non-secure client connections

Socket s;
ReadableByteChannel rbc;
WritableByteChannel wbc;
nbChannel socketChannel;

if(!useSSL) s = new Socket(host, port);
else
{
    SSLSocketFactory sslsf = SSLSocketFactory.getDefault();
    SSLSocket ssls = (SSLSocket)SSLSocketFactory.createSocket(host, port);
    ssls.startHandshake();
    s = ssls;
}

socketChannel = new nbChannel(s);
rbc = (ReadableByteChannel)socketChannel;
wbc = (WritableByteChannel)socketChannel;

...

s.close();

Following the creation of the channels, the communication will be secure if the socket uses SSL or plain if it doesn't. Closing the socket will cause a terminating handshake if SSL is used.

One possibility for this setup is to have two separate classes. One class should handle all communication through the socket along with connecting a non-secure socket. A separate class should handle creating a secure connection including all necessary setup for the secure connection, whether default or not. The secure class should plug directly into the communication class and should only be called if a secure connection is to be used.

The simplest path to integration
The methods outlined in this article represent the simplest methods that I know of for integrating both JSSE and NIO into the same code to provide non-blocking secure communication. While other methods do exist, they require a lot of time and effort on the part of the programmer attempting to implement the procedure.

One such option is to implement your own SSL layer atop NIO using the Java Cryptography Extensions. Another option would be to modify an existing custom SSL layer called EspreSSL (formerly known as jSSL) to change it to the NIO library. I would recommend either of these two options only if you have ample time available.

The downloadable zip file in the Resources section provides sample code to help you get started practicing the techniques described in this article, including:

  • nbChannel, source code to the emulation layer introduced with Listing 7
  • A simple HTTPS client that connects to Verisign's Web site and downloads the home page
  • A simple, non-blocking, secure server (Secure Daytime Server)
  • An integrated secure and non-secure client

Resources

About the author
Kenneth Ballard is currently a junior studying computer science at Peru State College in Peru, Nebraska. He is also a staff writer for the school's student newspaper, the Peru State Times. He has an Associate of Science degree in Computer Programming from Southwestern Community College in Creston, Iowa, where he worked in a work-study program as a PC technician. His studies include Java technology, C++, COBOL, Visual Basic, and networking. Contact Kenneth at kenneth.ballard@ptk.org


code81 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact