ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Introducing Nonblocking Sockets
Pages: 1, 2, 3

Nonblocking System Architecture

In this section I explain, in a theoretical way, the architecture of a nonblocking system and how it works. The characters of this comedy (or drama, if you prefer) are the following:



  • 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.


Figure 1. Nonblocking socket architecture

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 entities I introduced in the previous section have correspondent Java entities. Client and server are two Java applications. The socket channels are instances of the new SocketChannel class, which allow transferring data through the net. They can be seen as the new sockets for the Java programmer. The SocketChannel class is defined in the java.nio.channel package.

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 Selector
associate the SocketChannel to the Selector
for(;;) {
  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.

Usually an acceptable key is created on the server side. In fact, this kind of key simply informs the server that a client required a connection. In this circumstance, as you can see by the algorithm, the server individuates the socket channel and associates this to the selector for read/write operations. From this moment, when the accepted client reads or writes something, the selector will create readable or writeable keys for that client. Hence, the server will intercept those keys and perform the right actions.

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 open static method creates an instance of SocketChannel. The configureBlocking(false) invocation sets the channel as nonblocking. The connection to the server is made by the bind method. The "host" string represents the IP address of the server, and 8000 is the communication port. To create the selector, you can invoke the open static method of the Selector class. Finally, the register method associates the selector to the socket channel.

The second parameter represents the type of the registration. In this case, we use OP_ACCEPT, which means the selector merely reports that a client attempts a connection to the server. Other possible options are: OP_CONNECT, which will be used by the client; OP_READ; and OP_WRITE.

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;
    }
  }
}

The first line of the loop calls the select method, which blocks the execution and waits for events recorded on the selector. In the code fragment, the socket channel is represented by the server variable. Actually, server is not a SocketChannel object, but a ServerSocketChannel object. This, as well as SocketChannel, is a generalization of SelectableChannel, which is generally used by the server applications.

The event the selector waits for is a client attempting a connection. When this happens, the server application gets the keys created by the selector and for each key, it checks the type. As you may notice, when a key is processed, it has to be removed from the set keys by invoking the remove method. If the type of the key is acceptable (isAcceptable()=true), the server locates the client socket channel by invoking the accept method, sets it as nonblocking, and records it to the selector using the OP_READ option. We could also use the OP_WRITE or OP_READ | OP_WRITE options, but for simplicity, I implemented the server such that it can only read from a channel and cannot write.

The client socket channel is now recorded to the selector for reading operations. Consequently, when the client writes something on the socket channel, the selector will inform the server application that there is something to read. That happens by the creation of a readable key, thus isReadable()=true. At this point, the application reads the data from the socket channel by using a 32-byte ByteBuffer, decodes the bytes using the ISO-8859-1 encoding, and shows them on the server console.

Pages: 1, 2, 3

Next Pagearrow