[onJava]Introducing Nonblocking Sockets

Nonblocking sockets, introduced in Java 2 Standard Edition 1.4, allow net communication between applications without blocking the processes using the sockets. In this article, I will show in detail what a nonblocking socket is, how it works, and in which contexts it can be useful.


Java developers might ask: why introducing a new technology to handle sockets? What's wrong with the Java 1.3.x sockets? Suppose you would like to implement a server accepting diverse client connections. Suppose, as well, that you would like the server to be able to process multiple requests simultaneously. Using Java 1.3.x, you have two choices to develop such a server:

  • Implement a multithread server that manually handles a thread for each connection.
  • Using an external third-party module.

Nonblocking System Architecture

  • Server: the application receiving requests.

  • Client: the set of applications sending requests to the server.

  • Socket channel: the communication channel between client and server. It is identified by the server IP address and the port number. Data passes through the socket channel by buffer items.

  • Selector: the main object of all nonblocking technology. It monitors the recorded socket channels and serializes the requests, which the server has to satisfy.

  • Keys: the objects used by the selector to sort the requests. Each key represents a single client sub-request and contains information to identify the client and the type of the request.

    Figure 1
    illustrates the architecture of a system using nonblocking sockets.      

 

As you may notice, client applications simultaneously perform requests to the server. The selector collects them, creates the keys, and sends them to the server. This may seem like a blocking system, because the requests are processed one at a time; actually, it is not like that. In fact, each key doesn't represent the entire information stream a client sends to a server, but just a part. We don't have to forget the selector divides the client-data in sub-requests identified by the keys. Consequently, if more clients continuously send data to the server, the selector will create more keys, which will be processed according to a time-sharing policy. To emphasize that, in Figure 1 the keys have the same color of their related clients.


Server Nonblocking

The selector is a Selector class object. Each instance of Selector can monitor more socket channels, and thus more connections. When something interesting happens on the channel (for example, a client attempting a connection or a read/write operation), the selector informs the application to process the request. The selector does that by creating the keys, which are instances of the SelectionKey class. Each key holds information about the application making the request and the type of the request. The type can be one of the following:

  • Attempting connection (by the client)
  • Accepting connection (by the server)
  • Reading operation
  • Writing operation

A general algorithm to write a nonblocking server might be this:

create SocketChannel;create Selectorassociate the SocketChannel to the Selectorfor(;;) {  waiting events from the Selector;  event arrived; create keys;  for each key created by Selector {    check the type of request;    isAcceptable:      get the client SocketChannel;      associate that SocketChannel to the Selector;      record it for read/write operations      continue;   isReadable:      get the client SocketChannel;      read from the socket;      continue;   isWriteable:      get the client SocketChannel;      write on the socket;      continue;  } }

Basically, the server implementation consists of an infinite loop in which the selector waits for events and creates the keys. According to the key-types, an opportune operation is performed. There are four possible types for a key:

  • Acceptable: the associated client requests a connection.
  • Connectable: the server accepted the connection.
  • Readable: the server can read.
  • Writeable: the server can write.

Now you are ready to write the server in Java, following the proposed algorithm. The creation of the socket channel, the selector, and the socket-selector registration can be made in this way:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Create the server socket channel
ServerSocketChannel server = ServerSocketChannel.open();
// nonblocking I/O
server.configureBlocking(false);
// host-port 8000
server.socket().bind(new java.net.InetSocketAddress(host,8000));
System.out.println("Server attivo porta 8000");
// Create the selector
Selector selector = Selector.open();
// Recording server to selector (type OP_ACCEPT)
server.register(selector,SelectionKey.OP_ACCEPT);
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

The Java code for the infinite loop is the following:

// Infinite server loop
for(;;) {
   // Waiting for events
   selector.select();
   // Get keys
   Set keys = selector.selectedKeys();
   Iterator i = keys.iterator();

   // For each keys...
   while(i.hasNext()) {
      SelectionKey key = (SelectionKey) i.next();

      // Remove the current key
      i.remove();

      // if isAccetable = true
      // then a client required a connection
      if (key.isAcceptable()) {
      // get client socket channel
      SocketChannel client = server.accept();
      // Non Blocking I/O
      client.configureBlocking(false);
      // recording to the selector (reading)
      client.register(selector, SelectionKey.OP_READ);
      continue;
  }

  // if isReadable = true
  // then the server is ready to read
  if (key.isReadable()) {

   SocketChannel client = (SocketChannel) key.channel();

   // Read byte coming from the client
   int BUFFER_SIZE = 32;
   ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
   try {
      client.read(buffer);
   }
   catch (Exception e) {
      // client is no longer active
      e.printStackTrace();
      continue;
   }

   // Show bytes on the console
   buffer.flip();
   Charset charset=Charset.forName("ISO-8859-1");
   CharsetDecoder decoder = charset.newDecoder();
   CharBuffer charBuffer = decoder.decode(buffer);
   System.out.print(charBuffer.toString());
   continue;
   }
  }
}


Client Nonblocking

// Create client SocketChannel
SocketChannel client = SocketChannel.open();

// nonblocking I/O
client.configureBlocking(false);

// Connection to host port 8000
client.connect(new java.net.InetSocketAddress(host,8000));

// Create selector
Selector selector = Selector.open();

// Record to selector (OP_CONNECT type)
SelectionKey clientKey = client.register(selector, SelectionKey.OP_CONNECT);

// Waiting for the connection
while (selector.select(500)> 0) {

  // Get keys
  Set keys = selector.selectedKeys();
  Iterator i = keys.iterator();

  // For each key...
  while (i.hasNext()) {
    SelectionKey key = (SelectionKey)i.next();

    // Remove the current key
    i.remove();

    // Get the socket channel held by the key
    SocketChannel channel = (SocketChannel)key.channel();

    // Attempt a connection
    if (key.isConnectable()) {

      // Connection OK
      System.out.println("Server Found");

      // Close pendent connections
      if (channel.isConnectionPending())
        channel.finishConnect();

      // Write continuously on the buffer
      ByteBuffer buffer = null;
      for (;;) {
        buffer =
          ByteBuffer.wrap(
            new String(" Client " + id + " ").getBytes());
        channel.write(buffer);
        buffer.clear();
      }

    }
  }
}

Resources

D. Flanagan, "Top Ten Cool New Features of Java 2SE 1.4," O'Reilly Network, March 2002.

J. Zukowski, "New I/O Functionality for Java 2 Standard Edition 1.4," java.sun.com, December 2001.

T. Burns, "Nonblocking Socket I/O in JDK 1.4," Owl Mountain Software, December 2001.

A. Saltarin, "New I/O API in JDK 1.4," MokaByte N?9, January 2002.


 

by 슬럼퍼 | 2008/07/31 10:27 | 트랙백 | 덧글(0)

트랙백 주소 : http://gusspia.egloos.com/tb/645839
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶