JBoss.org: Netty - The Client Server Framework and ToolsCommunity Documentation

Chapter 2. Architectural Overview

2.1. Rich Buffer Data Structure
2.2. Universal Asynchronous I/O API
2.3. Event Model based on the Interceptor Chain Pattern
2.4. Advanced Components for More Rapid Development
2.4.1. Codec framework
2.4.2. SSL / TLS Support
2.4.3. HTTP Implementation
2.4.4. Google Protocol Buffer Integration
2.5. Summary
The Architecture Diagram of Netty

In this chapter, we will examine what core functionalities are provided in Netty and how they constitute a complete network application development stack on top of the core. Please keep this diagram in mind as you read this chapter.

Netty uses its own buffer API instead of NIO ByteBuffer to represent a sequence of bytes. This approach has significant advantages over using ByteBuffer. Netty's new buffer type, ChannelBuffer has been designed from the ground up to address the problems of ByteBuffer and to meet the daily needs of network application developers. To list a few cool features:

  • You can define your own buffer type if necessary.

  • Transparent zero copy is achieved by a built-in composite buffer type.

  • A dynamic buffer type is provided out-of-the-box, whose capacity is expanded on demand, just like StringBuffer.

  • There's no need to call flip() anymore.

  • It is often faster than ByteBuffer.

For more information, please refer to the org.jboss.netty.buffer package description.

Traditional I/O APIs in Java provide different types and methods for different transport types. For example, java.net.Socket and java.net.DatagramSocket do not have any common super type and therefore they have very different ways to perform socket I/O.

This mismatch makes porting a network application from one transport to another tedious and difficult. The lack of portability between transports becomes a problem when you need to support additional transports, as this often entails rewriting the network layer of the application. Logically, many protocols can run on more than one transport such as TCP/IP, UDP/IP, SCTP, and serial port communication.

To make matters worse, Java's New I/O (NIO) API introduced incompatibilities with the old blocking I/O (OIO) API and will continue to do so in the next release, NIO.2 (AIO). Because all these APIs are different from each other in design and performance characteristics, you are often forced to determine which API your application will depend on before you even begin the implementation phase.

For instance, you might want to start with OIO because the number of clients you are going to serve will be very small and writing a socket server using OIO is much easier than using NIO. However, you are going to be in trouble when your business grows exponentially and your server needs to serve tens of thousands of clients simultaneously. You could start with NIO, but doing so may hinder rapid development by greatly increasing development time due to the complexity of the NIO Selector API.

Netty has a universal asynchronous I/O interface called a Channel, which abstracts away all operations required for point-to-point communication. That is, once you wrote your application on one Netty transport, your application can run on other Netty transports. Netty provides a number of essential transports via one universal API:

  • NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),

  • OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),

  • OIO-based UDP/IP transport, and

  • Local transport (See org.jboss.netty.channel.local).

Switching from one transport to another usually takes just a couple lines of changes such as choosing a different ChannelFactory implementation.

Also, you are even able to take advantage of new transports which aren't yet written (such as serial port communication transport), again by replacing just a couple lines of constructor calls. Moreover, you can write your own transport by extending the core API.

A well-defined and extensible event model is a must for an event-driven application. Netty has a well-defined event model focused on I/O. It also allows you to implement your own event type without breaking the existing code because each event type is distinguished from another by a strict type hierarchy. This is another differentiator against other frameworks. Many NIO frameworks have no or a very limited notion of an event model. If they offer extension at all, they often break the existing code when you try to add custom event types

A ChannelEvent is handled by a list of ChannelHandlers in a ChannelPipeline. The pipeline implements an advanced form of the Intercepting Filter pattern to give a user full control over how an event is handled and how the handlers in the pipeline interact with each other. For example, you can define what to do when data is read from a socket:

public class MyReadHandler implements SimpleChannelHandler {
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
        Object message = evt.getMessage();
        // Do something with the received message.
        ...

        // And forward the event to the next handler.
        ctx.sendUpstream(evt);
    }
}

You can also define what to do when a handler receives a write request:

public class MyWriteHandler implements SimpleChannelHandler {
    public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) {
        Object message = evt.getMessage();
        // Do something with the message to be written.
        ...

        // And forward the event to the next handler.
        ctx.sendDownstream(evt);
    }
}

For more information on the event model, please refer to the API documentation of ChannelEvent and ChannelPipeline.

On top of the core components mentioned above, that already enable the implementation of all types of network applications, Netty provides a set of advanced features to accelerate the page of development even more.

As demonstrated in Section 1.8, “ Speaking in POJO instead of ChannelBuffer ”, it is always a good idea to separate a protocol codec from business logic. However, there are some complications when implementing this idea from scratch. You have to deal with the fragmentation of messages. Some protocols are multi-layered (i.e. built on top of other lower level protocols). Some are too complicated to be implemented in a single state machine.

Consequently, a good network application framework should provide an extensible, reusable, unit-testable, and multi-layered codec framework that generates maintainable user codecs.

Netty provides a number of basic and advanced codecs to address most issues you will encounter when you write a protocol codec regardless if it is simple or not, binary or text - simply whatever.

Google Protocol Buffers are an ideal solution for the rapid implementation of a highly efficient binary protocols that evolve over time. With ProtobufEncoder and ProtobufDecoder, you can turn the message classes generated by the Google Protocol Buffers Compiler (protoc) into Netty codec. Please take a look into the 'LocalTime' example that shows how easily you can create a high-performing binary protocol client and server from the sample protocol definition.

In this chapter, we reviewed the overall architecture of Netty from the feature standpoint. Netty has a simple, yet powerful architecture. It is composed of three components - buffer, channel, and event model - and all advanced features are built on top of the three core components. Once you understood how these three work together, it should not be difficult to understand the more advanced features which were covered briefly in this chapter.

You might still have unanswered questions about what the overall architecture looks like exactly and how each of the features work together. If so, it is a good idea to talk to us to improve this guide.