Skip to main content

Grizzly : Create a server and client with only few changes

Posted by survivant on December 29, 2008 at 12:05 PM PST

With Grizzly it can be simple to create a server and client with a few lines changes. I want to show you how you can create or convert a server to a client without to much troubles.

I will use ProtocolChain in the server. For the client there are two ways to create it using the logic of the server. You can use the ProtocolChain or use a CallbackHandler.

I'll show you how to create the client for the both alternatives.

Let's start by showing the main lines and later I'll show you the differences between the ProtocolChain and CallbackHandler implementations for the client.

The complete source code is available at the end of this post.

Let's take a look if the init method of the server and the clients implementations.

Server

f_tg = new ThreadGroup("ThreadGroup");
[prettify]</code><br /><code>final CountDownLatch started = new CountDownLatch(1);
</code><br /><code>controller = new Controller();
TCPSelectorHandler tcpSelectorHandler = new TCPSelectorHandler();
tcpSelectorHandler.setPort(port);
controller.addSelectorHandler(tcpSelectorHandler);
[/prettify]

Client
f_tg = new ThreadGroup("ThreadGroup");
[prettify]</code><br /><code>final CountDownLatch started = new CountDownLatch(1);
</code><br /><code>controller = new Controller();
TCPSelectorHandler tcpSelectorHandler = new TCPSelectorHandler();
controller.addSelectorHandler(tcpSelectorHandler);
[/prettify]

The difference is that you need to specify the listening port of the server.

Let's continue with the next step : the close notification and the state listener

The server and clients are identical.

BaseSelectionKeyHandler selectionKeyHandler = new BaseSelectionKeyHandler();
[prettify]</code><br /><code>// to be notify when a client/server close the connection
selectionKeyHandler.setConnectionCloseHandler(new ConnectionCloseHandler() {
</code><br /><code>  public void locallyClosed(SelectionKey key) {
      if(s_logger.isDebugEnabled()){
        s_logger.debug(key + " is being locally cancelled");
      }
     }
</code><br /><code>     public void remotlyClosed(SelectionKey key) {
       if(s_logger.isDebugEnabled()){
         s_logger.debug(key + " is being remotly cancelled (connection closed)");
       }
     }
});
</code><br /><code>tcpSelectorHandler.setSelectionKeyHandler(selectionKeyHandler);
</code><br /><code>// STATE Listener
controller.addStateListener(new ControllerStateListenerAdapter() {
</code><br /><code>  public void onException(Throwable e) {
    s_logger.error("Grizzly controller exception:" + e.getMessage());
  }
</code><br /><code>  public void onReady() {
    if(s_logger.isDebugEnabled()){
      s_logger.debug("Ready!");
    }
    started.countDown();
  }
</code><br /><code>});
[/prettify]

Now it's time the see the ProtocolChain in the server and the client implementations

Server and client (the ProtocolChain implementation)
// the protocol chain
[prettify]EchoQueryProtocolFilter protocolParser = new EchoQueryProtocolFilter();
EchoQueryManagerFilter echoManagerFilter = new EchoQueryManagerFilter(this);
  
final ProtocolChain protocolChain = new DefaultProtocolChain();
protocolChain.addFilter(protocolParser);
protocolChain.addFilter(echoManagerFilter);
((DefaultProtocolChain) protocolChain).setContinuousExecution(true);
</code><br /><code>
ProtocolChainInstanceHandler pciHandler = new DefaultProtocolChainInstanceHandler() {
</code><br /><code>       public boolean offer(ProtocolChain protocolChain) {
           return false;
</code><br /><code>       }
</code><br /><code>       public ProtocolChain poll() {
</code><br /><code>           return protocolChain;
       }
   };
</code><br /><code>controller.setProtocolChainInstanceHandler(pciHandler);
[/prettify]

Client (the Callbackhandler) : nothing here.

We finish by starting the server and clients

Server

// start the server
[prettify]new Thread(f_tg, controller).start();

try {
  started.await();
} catch (Exception e) {
  s_logger.error("Timeout in wait" + e.getMessage());
}
</code><br /><code>// extra stuff : Queue
blockingQueue = new LinkedBlockingQueue<queueitem>();</queueitem>
queueConsumer = new QueueConsumer(this, blockingQueue);
new Thread(f_tg, queueConsumer,"QueueConsumer").start();
[/prettify]

Client
// start the client
[prettify]new Thread(f_tg, controller).start();

try {
  started.await();
} catch (Exception e) {
  s_logger.error("Timeout in wait" + e.getMessage());
}
</code><br /><code>// extra stuff : Queue
blockingQueue = new LinkedBlockingQueue<queueitem>();</queueitem>
queueConsumer = new QueueConsumer(this, blockingQueue);
new Thread(f_tg, queueConsumer,"QueueConsumer").start();
</code><br /><code>connector_handler = (TCPConnectorHandler) controller.acquireConnectorHandler(Controller.Protocol.TCP);
[/prettify]

The difference this time is on the client side. We need to acquire a ConnectorHandler that will be used later.

I want to say that the ProtocolChain is this example are identical for the client and server. Normally you could have a different implementations but the logic is the same. The init method will look alike.

Now let's take a deeper look into the Client implementations.

On the client side we need to add a method to connect to the server.

There is a little difference between the ProtocolChain and CallbackHandler. In the connect you need to specify a CallbackHandler.
For the ProtocolChain implementation, I was expecting the pass a null handler, and the API will use the ProtocolChain instead, but it doesn't work. I sent a email to the developers to find if it's a bug or not. I'll describe the current way that work.

UPDATED : It was a bug.  It fixed with Grizzly 1.9.3+.

Client (ProtocolChain)
public boolean connect() {
[prettify]  try {
    //connector_handler.connect(new InetSocketAddress(host, port), new ProtocolChainCallbackHandler(connector_handler));
connector_handler.connect(new InetSocketAddress(host, port), (CallbackHandler)null);  // don't need the callback anymore with 1.9.3+<br />&nbsp;} catch (Exception e) {
    s_logger.error("Exception in execute..." + e);
    return false;
  }
 
  return true;
}
[/prettify]
Client (CallbackHandler)
public boolean connect() {
[prettify]  try {
    connector_handler.connect(new InetSocketAddress(host, port), new ClientCallbackHandler(connector_handler));
  } catch (Exception e) {
    s_logger.error("Exception in execute..." + e);
    return false;
  }
 
  return true;
}
[/prettify]

The last part is the CallbackHandler. I'll start by the ProtocolChain implementation followed by the CallbackHandler implementation. I'll describe
the difference between the implementations, I suggested that you take a look at the source code to get the full overview.

ProtocolChainCallbackHandler
// read event
[prettify]public void onRead(IOEvent<context> ioEvent) {</context>
  SelectionKey key = ioEvent.attachment().getSelectionKey();
  SelectorHandler selectorHandler = ioEvent.attachment().getSelectorHandler();
 
  // CALL THE PROTOCOL CHAIN
  try {
    if(key.isValid() &amp;&amp; key.isReadable()){
      Context ctx = ioEvent.attachment();
     
      ctx.getProtocolChain().execute(ioEvent.attachment());
     
    }
  } catch (Exception ex){
    if(s_logger.isDebugEnabled()){
      s_logger.debug("IOException", ex);
    }
      selectorHandler.getSelectionKeyHandler().cancel(key);
  } 
</code><br /><code>}
[/prettify]

ClientCallbackHandler
// read event
[prettify]public void onRead(IOEvent<context> ioEvent) {</context>
  SelectionKey key = ioEvent.attachment().getSelectionKey();
  SelectorHandler selectorHandler = ioEvent.attachment().getSelectorHandler();
  SocketChannel socketChannel = (SocketChannel)key.channel();
 
  try {
    if(key.isValid() &amp;&amp; key.isReadable()){
     
        // parse the responses here
        int count = socketChannel.read(response);
        if(count&gt;0){
          response.flip();
         
          byte[] b = new byte[(int)count];
          response.get(b);
       
          // ...... do your own logic.  You can use EchoQueryProtocolParser as example
         
          response.clear();
          if(s_logger.isDebugEnabled()){
            s_logger.debug(new String(b)); // for debug purpose
          }
        }
     
        selectorHandler.register(key, SelectionKey.OP_READ);
    }
  } catch (IOException ex){
    if(s_logger.isDebugEnabled()){
      s_logger.debug("IOException", ex);
    }
      selectorHandler.getSelectionKeyHandler().cancel(key);
  } 
</code><br /><code>}
[/prettify]
I hope that can show you that with Grizzly it can be really simple to create a server and a client in the same time with just few changes. The source code can be downloaded here.

Related Topics >>

Comments

Grizzly 1.9.3+ fixed the problem with ProtocolChain client implementation that needed to have a CallbackHandler to execute the ProtocolChain.