This is the fourth article in the Netty series

In the last article we looked in depth at three implementations of I/O multiplexing, select/poll/epoll.

So which implementation does Netty use for I/O multiplexing? This problem starts with the Java NIO package.

Netty is also a packaged framework that essentially uses Java’s Nonblocking IO (New IO, not NIO) package for network I/O. So, from the network I/O model to Netty, we also need to know about the Java NIO package.

This article will take 5 minutes to read and will focus on answering the following questions:

  • How to implement a server with Java NIO package
  • How does the Java NIO package implement the I/O multiplexing model
  • Why encapsulate a Netty when you have a Java NIO package?

1. Take a look at an example of a Java NIO server

In the last article we looked at how I/O multiplexing can be implemented. That is, multiple processes can register their I/OS with a selector, and then call select with a process, which listens for all the registered I/OS.

NIO packages do the corresponding implementation. As shown in the figure below.

There’s a uniform selector that listens for all channels. Any one of these channels that has an IO action is detected by Selector. Select (), and the channels that have IO are obtained using selectedKeys, and the corresponding IO operations are invoked on them.

Let’s do a simple demo to demonstrate that. How to write a server program using NIO’s three core components: Buffer buffers, channels, and Selector selectors.

public class NioDemo { public static void main(String[] args) { try { //1. Create channel ServerketChannel socketChannel1 = serversocketChannel.open (); / / set to non-blocking mode, the default is blocking socketChannel1. ConfigureBlocking (false); SocketChannel1. Socket (). The bind (new InetSocketAddress (8811) "127.0.0.1,"); ServerSocketChannel socketChannel2 = ServerSocketChannel.open(); socketChannel2.configureBlocking(false); SocketChannel2. Socket (). The bind (new InetSocketAddress (8822) "127.0.0.1,"); //2. Create selector and register channel1 and channel2. Selector selector = Selector.open(); socketChannel1.register(selector, SelectionKey.OP_ACCEPT); socketChannel2.register(selector, SelectionKey.OP_ACCEPT); While (true) {//3. Block until at least one channel is ready int readChannelCount = selector. Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); While (iterator.hasnext ()) {SelectionKey key = iterator.next(); iterator.remove(); If (key.isacceptable ()) {// Create a new connection, register the connection with the selector, and declare that this channel is only interested in reads. ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel(); SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer readBuff = ByteBuffer.allocate(1024); socketChannel.read(readBuff); readBuff.flip(); System.out.println("received : " + new String(readBuff.array())); socketChannel.close(); } } } } catch (IOException e) { e.printStackTrace(); }}}Copy the code

With this code example, we can clearly see how to implement a server using the Java NIO package:

  • 1) Create channel1 and channel2 to listen on specific ports, respectively.
  • 2) Create selector and register channel1 and channel2.
  • 3) selector. Select () blocks until at least one channel is ready.
  • 4) The channel where the rotation training is ready
  • 5) And make corresponding response actions according to the event type.

After the program starts, it blocks at selector. Select (). A browser call to localhost:8811 or localhost:8822 triggers our server code.

2. How does the Java NIO package implement the I/O multiplexing model

The Java NIO server demonstrated above has shown the process of writing server-side programs using NIO somewhat clearly.

So how does I/O multiplexing work in this process?

We’re going to have to take a look at the implementation of selector.

//2. Create selector and register channel1 and channel2. Selector selector = Selector.open();Copy the code

Let’s start with open.

So we’re using a SelectorProvider to create a selector.

Enter SelectorProvider provider (), see the specific provider is by the sun. The nio. Ch. DefaultSelectorProvider created, the corresponding method is:

Yi? Previously, different operating systems provided different Provider objects. PollSelectorProvider and EPollSelectorProvide are included.

Does the name look familiar?

Yes, it has to do with poll/epoll, the different implementations of I/O multiplexing that we analyzed in our last article.

We select the default sun. Nio. Ch. PollSelectorProvider downwards see.

OK, found the implementation class PollSelectorImpl.

Then, with the following call:

Find the final native method poll0.

Does it still look familiar?

That’s right! This is consistent with the poll function we analyzed in our last article.

int poll (struct pollfd *fds, unsigned int nfds, int timeout);
Copy the code

After all this detour, we finally found the poll implementation we talked about for I/O multiplexing.

At this point, we have finally connected the Java NIO and I/O multiplexing model in tandem.

The Java NIO package implements the I/O multiplexing model using selectors.

Also, there are different poll/epoll options for different operating systems.

3. Why do you need Netty?

So why encapsulate a Netty framework when we already have the NIO package and can write our own service framework manually? What are the benefits?

The advantage of course is that there are many! Let’s start with the demo we implemented at the beginning.

3.1 Optimization of design mode

Our demo did work, but there were some obvious problems. Step 4 (polling channels that are already in place) and step 5 (handling events accordingly) are in the same thread, and when event processing becomes time-consuming or even blocking, the whole process is blocked.

We use what is essentially a single-reactor single-thread design pattern.

The Reactor listens to ports, receives requests, passes connection events to acceptors, and passes read/write events and business processes to Handlers, but there is only one thread that does everything.

In order to improve performance, we can certainly turn event processing over to the thread pool, which can evolve to a single-reactor multithreaded design pattern.

The main difference between this model and the first is that business processing is removed from the previous single thread and replaced with thread pool processing. Reactor thread only processes connection events and read and write events, and all business processing is entrusted to the thread pool to make full use of multi-core machine resources and improve performance.

But it’s still not enough!

It can be found that a Reactor thread undertakes all network events, such as listening and response, and a single thread has performance problems in high concurrency scenarios.

To make full use of the multi-core capability, we can build two reactors. The primary Reactor listens to the server socket separately, accepts the new connections, and registers the established SocketChannel with the specified secondary Reactor. Read, write, and distribute events from the Reactor, and then dump the business processing to the worker thread pool. This evolved into the “Reactor pattern” design pattern.

So, wouldn’t it be great if someone could just encapsulate this design pattern for us?

Yes, Netty is such a “live Lei Feng”! Netty uses the master-slave Reactor pattern to encapsulate the use of Java NIO packages, which greatly improves performance.

3.2 Other advantages (core knowledge points later)

In addition to encapsulating high-performance design patterns, Netty has many other advantages:

  • Stability. Netty is more reliable and stable. It fixes and improves the known problems of JDK NIO, including 100% CPU consumption due to SELECT idle and keep-alive detection.
  • ** Performance optimization. ** Object pool reuse technology. Netty avoids the overhead of frequent creation and destruction by reusing objects. Zero copy technology. In addition to zero-copy technology at the operating system level, Netty provides zero-copy technology for the user state, which uses DirectBuffer directly for I/O reads and writes, avoiding copying data between heap memory and off-heap memory.
  • Convenience. Netty provides many common tools, such as line decoders, length field decoders, and so on. If you use the JDK NIO package, you’ll need to implement these common tools yourself.

Netty’s high performance, high stability, and high ease of use perfectly complement Java NIO, so we prefer Netty to Java NIO in network programming.

Reviewing the previous chapters so far, we’ve worked our way through Netty’s network I/O model, starting with the network I/O model.

I/O multiplexing, Java NIO packages, and Netty relationships are also well understood.

With this knowledge base, we have a preliminary understanding of what Netty is and why we use it.

In the following articles, we will gradually expand the core knowledge of Netty framework, stay tuned.

See the end, the original is not easy, point a concern, point a like it ~

Reorganize the knowledge fragments and construct the Java knowledge graph: github.com/saigu/JavaK… (Easy access to historical articles)