The article was first published on my blog www.nullobject.cn and updated with the public account NullObject.

This article is based on Java 8, Netty 4.1.69.final

Netty is a Java asynchronous network communication framework. Based on Java NIO in Netty implements the asynchronous I/o communication, which achieve NioServerSocketChannel/NioSocketChannel related components, is actually the Java NIO relatively component encapsulation. This article will start from the Java NIO startup process, to learn and analyze the startup process of NIO Server in Netty.

1.0 NIO Server Startup process

NioServerSocketChannel and other components in Netty are encapsulated on the basis of Java NIO. Therefore, before learning Netty NIO startup process, you should first clarify the steps of using Java NIO. This will make it easier to understand the Netty Server startup and running process in the following analysis.

1.1 Startup Procedure Overview

The following steps are used to start and initialize the NIO Server:

  • Initializes the Selector/multiplexer Selector
  • Initialize the server channel ServerSocketChannel and configure the asynchronous working mode
  • The ServerSocketChannel is registered with the Selector, the corresponding SelectionKey is obtained, and the Accept event is registered for attention
  • Bind the listening address port
  • Handle client connections
  • Receive data

Startup flowchart:

1.2 registered ServerSocketChannel

You need to register the server channel SSC with selector and register the events you need to care about:

mKey = mSSC.register(mSelector,0);
mKey.interestOps(SelectionKey.OP_ACCEPT);
Copy the code

The ServerSocketChannel#register method returns a SelectionKey object that represents the registration of the current SocketChannel and its associated Selector. Internally, it holds references to both SocketChannel and Selector objects, and is responsible for maintaining the events that the current channel cares about and the events that have occurred/are ready. The SelectionKey#interestOps method is used to register the events that the corresponding Channel needs to care about.

  • OP_READ

    Read event, indicating that a Selector detects that a readable event has occurred in the Channel to which it is associated, including a Channel ready to read, the end of the stream reached, a remote read Channel closed, or an error pending in the Channel, etc. The Selector then adds the OP_READ event to the SelectionKey object and identifies the event as ready.

  • OP_WRITE

    Write event, which indicates that a Selector detects that a writable event has occurred in the Channel to which it is associated, including the Channel data being written ready, the remote write Channel being closed, or an error in the Channel being suspended, etc. The Selector then adds the OP_WRITE event to the corresponding SelectionKey and identifies the event as ready.

  • OP_CONNECT

    Connection event, indicating that the Selector detects that a connection event has occurred in the associated Channel, including completion of the Channel connection or an error in the Channel. This event usually occurs after the NIO Client successfully connects to the server. Selector adds the OP_CONNECT event to the corresponding SelectionKey and identifies the event as ready.

  • OP_ACCEPT

    Accept event, indicating that the Selector detects that the associated ServerSocketChannel is ready to accept an incoming client connection request, or that an error has occurred on the channel. The Selector then adds an OP_ACCEPT event to the corresponding SelectionKey. And identifies the event as ready.

1.3 Selector Collects I/O events

A Selector is a multiplexer in NIO that can manage multiple I/O channels simultaneously. After registering serverSocketChannel, we begin a loop calling selector. Select () to collect events that occur on the serverSocketChannel channel:

// Detect ready I/o events in all channels associated with selector
// count: indicates the number of I/O events
int count = mSelector.select();
Copy the code

The select() method blocks the current thread until an IO event is collected. To end the blocking in advance, you can call the overloaded method mSelector. Selector (interval) to specify the blocking duration. If no I/O event is collected within the duration, the blocking is ended. Or the select() method returns immediately after another thread calls mSelector. Wakeup () to stop blocking manually and continue execution.

1.4 Processing Client Connection requests

When an OP_ACCEPT event is detected, it indicates that there is a new client connection request, and you need to call ServerSocketChannel#accept() to accept and establish a client connection, and then register the SocketChannel with the Selector. And register a read message event to receive data sent by processing clients:

private void handleNewConnection(final SelectionKey key) throws IOException {
    // Process the new access request message
    // Accept the new connection
    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
    // Accept a new connection and create a SocketChannel example. At this point, the TCP three-way handshake is completed and the TCP physical link is established.
    SocketChannel socketChannel = serverSocketChannel.accept();
    // Set to asynchronous non-blocking mode
    socketChannel.configureBlocking(false);
    // Register the new connection with the multiplexer and associate a Bytebuffer attachment to store the received data
    ByteBuffer buffer = ByteBuffer.allocate(INIT_READ_BUFFER_SIZE);
    // Register the new connection to the selector
    socketChannel.register(mSelector, SelectionKey.OP_READ, buffer);
}
Copy the code

1.5 Receiving Data

When an OP_READ event is detected, the channel receives data from the peer or disconnects from the peer. In this case, you can attempt to read data from the SocketChannel:

private void handleReadData(final SelectionKey key) throws IOException {
    // Read the data
    // Read the client request message
    // Get the connection channel instance
    SocketChannel socketChannel = (SocketChannel) key.channel();
    // Get the associated buffer attachment
    ByteBuffer readBuffer = (ByteBuffer) key.attachment();
    // Read data from the TCP buffer
    int readBytes;
    if ((readBytes = socketChannel.read(readBuffer)) > 0) {
        // Process the data read
        handleData(readBuffer);
        // Determine whether expansion is required
        if (readBuffer.position() == readBuffer.limit()) {
            final ByteBuffer buffer = ByteBuffer.allocate(readBuffer.capacity() * 2); readBuffer.flip(); buffer.put(readBuffer); key.attach(buffer); }}// The link is closed. In this case, you need to close the SocketChannel to release resources
    if (readBytes < 0) {
        System.out.println("connection closed: " + socketChannel.getRemoteAddress());
        // The peer link is downkey.cancel(); socketChannel.close(); }}Copy the code

As you can see, NIO is a bit more complex to use than traditional Java BIO, but its synchronous non-blocking IO model makes it possible for NIO to handle high concurrency and massive connections. Netty has further optimized and encapsulated NIO, simplifying the development process of asynchronous IO.

Having sorted out the STARTUP process of NIO, we then examine the packaging and execution of each of the key steps of NIO in Netty.

2.0 Starting the Netty Server

It is very easy to start a Netty Server, initialize the related components with a simple chain call, and then bind the port to start Socket listening:

@Slf4j
public class TestSourceServer {

    public static void main(String[] args) {
        final ServerBootstrap bootstrap = new ServerBootstrap();
      // The event loop group that executes IO events
        final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
      // Configure the server initiator
        bootstrap.group(bossGroup)
                 .channel(NioServerSocketChannel.class)
                 .childHandler(new ChannelInitializer<NioSocketChannel>() {
                     @Override
                     protected void initChannel(final NioSocketChannel ch) throws Exception {
                         ch.pipeline().addLast(newLoggingHandler()); }});// Start listening on the specified port
        bootstrap.bind(8080); }}Copy the code

ServerBootStrap is the Netty server initiator that initializes each component through a series of chained calls to the ServerBootStrap object:

  • Group (bossGroup) : sets the event loop group for executing I/O events/common events.
  • Channel (NioServerSocketChannel. Class) : the configuration of the need to use the socket server component, this article is the analysis of the NIO related start-up process, so need to configure the server components for NioServerSocketChannel. Class.
  • ChildHandler (Initializer) : Configures the client connection channel initializer. After the server establishes the SocketChannel connection channel with the client, Netty notifies the user that the SocketChannel channel has been created by calling the chain final callback initChannel method. At this time, the user can perform some service initialization for the SocketChannel. Such as setting up message codecs, adding log handlers, and so on. In the above example, a LoggingHandler is added to the event processing pipe in the client connection channel each time it is created.

All the chain calls to the serverBootstrap object before the ind() method binds the port do some configuration on the server side to actually start the server listening, starting with the bind() method.

3.0 registered ServerSocketChannel

3.1 Creating a ServerSocketChannel Object

Next start in debug mode [the example from Section 1](#1 from Netty Server minimal startup), starting at line bootstrap.bind(8080), **AbstractBootstrap#doBind() **

The first line of the initAndRegister() method calls an initAndRegister() method and returns a ChannelFuture object. From the returned parameters, you can infer that an asynchronous operation is performed internally, and you can guess from the name of the method, The step of registering a serverSocketChannel is probably implemented in this method. Proceed to the initAndRegister() method to find out:

A channel is an abstract interface defined in Netty that represents a Netty component with IO communication capability. The NioServerSocketChannel class in the example implements the Channel interface. The channel object is an instance of the NioServerSocketChannel class that was configured when serverBootStrap was configured:

In the configuration serverBootStrap step, through the bootstrap. Channel (NioServerSocketChannel. Class) method set up to implement the service to monitor the specific components, () create a ReflectiveChannelFactory object and pass in the NioServerSocketChannel type information. Then assign a ReflectiveChannelFactory object to the channelFactory field. Complete the configuration of the Server IO Channel component:

As for the implementation of newChannel in reflectiveChannelFactory, the nioServerSocketChannel object is created by calling the no-parameter constructor of NioServerSocketChanel through reflection. Therefore, the program next runs the no-argument constructor that jumps to the NioServerSocketChannel class:

NioServerSocketChannel class constructor, and through the newSocket method and invoke the selectorProvider openServerSocketChannel () method, Java NIO ServerSocketChannel object

Here directly through selectorProvider. OpenServerSocketChannel () create ServerSocketChannel object, and in the steps in the NIO invoked ServerSocketChannel. Open () to create a way, The interior is also through selectorProvider. OpenServerSocketChannel () method to create:

At this point, the ServerSocketChannel is created.

3.2 Registering a Channel

. Continue to back abstractBootStrap initAndRegister () method, after the access to the channel object, by init (channel) method to initialize the channel, and then to register:

Where config().group() gets the bossGroup object that was set when the serverBootStrap configuration was initialized. Config (.) group (). The register (channel), the realization of the abstract methods, the superclass MultithreadEventLoopGroup NioEventLoopGroup:

// MultithreadEventLoopGroup.java Line 85
@Override
public ChannelFuture register(Channel channel) {
  return next().register(channel);
}
Copy the code

The next() method executes the Register (Channel) method by selecting an event loop object from the pool of event loops created when the bossGroup is initialized. The nioEventloop.register () abstract method is implemented in nioEventLoop’s parent, SingleThreadEventLoop:

The AbstractChannel$abstractunsafe.register method (abstractchannel.java Line 465), which in turn calls register0(Promise) to implement the registration details:

The eventLoop.ineventLoop () method is used to determine whether to delegate the registration task to the eventLoop object. It is essentially a matter of checking whether the thread in the eventLoop is in the same thread environment as the current thread to decide whether to call the register0 method directly or hand it over to the eventLoop. Note that the eventLoop object here was created when the nioBossGroup eventLoop object was created, but the child thread object contained in the eventLoop has not been initialized and does not actually start the thread, only when the first asynchronous task like the eventLoop is submitted, EventLoop initializes and starts the thread, delaying the start of the thread, which is optimized for using thread resources in Netty. We then enter the regster0 method and call the doRegster() method internally to actually register:

Get the Java NIO ServerSocketChannel object created in the previous step via javaChannel(), register it with the selector method, Finally, the current channel object is saved as an attachment to the selectionKey(in subsequent IO events, the channel object associated with the selectionKey is retrieved from the selectionKey). SelectionKey is assigned to the SelectionKey field of the current channl. The ServerSocketChannel in ②Netty is registered.

4.0 create a Selector

When you see the selector object from the serverSocketChannel to the selector object that you get from eventLoop().unwrappedSelector(), when is the selector object created? First get the eventLoop object of the current channel object through eventLoop(), Through eventLoop. UnWrappedSelector () method to get into nioEventLoop unWrappedSelector objects as the selector of the channel, UnwrappedSelector is initialized in NioEventLoop: First call openSelector () and returns a SelectorTuple object, through SelectorTuple. UnwrappedSelector assignment of unwrappedSelector openSelector () method, Was finally called selectorProvider. OpenSelector () method to create a selector, and will create the selector packaging to selectorTuple returns:

Provider is the SelectorProvider object instance. At this point, Selector ③ is created.

. There is no direct use of selectorProvider openSelector () to create the selector, but first it encapsulates a selectorTuple objects using unwrappedSelector again, This is because Netty has made some performance optimizations to encapsulate this step in the Implementation of the Selector () method of the NioEventLoop class.

5.0 Bind starts listening

Program calls AbstractNioChannel. DoRegister () method to complete the serverSocketChannel after registration, and then began to bind listening on port. Back to AbstractChannel. Register0 () method, doRegister () is done, will be registered marked as true, said the current channel is registered, Then through the pipeline. FireChannelRegistered () method will be completed serverSocketChannel registered events are sent to the processing of the pipeline to notify the upper code processing. Back in the abstractBootstrap.dobind () method, initAndRegister() completes the channel registration process, and doBind() runs down to regFuture to determine whether the registration is complete or asynchronous. Either way, The doBind0 method is then called for port binding. The doBind0 method gives the binding task to the serverSocketChannel event loop to execute:

AbstractChannel$abstractunsafe. bind: AbstractChannel$abstractunsafe. bind: AbstractChannel$AbstractUnsafe.

. As you can see, NioServerSocketChannel doBind implementation, call the Java NIO ServerSocketChannel. The bind () method of the completion port binding, (4) Netty Server binding listener port step is complete.

6.0 Pay attention to ACCEPT events

AbstractChannel$abstractunsafe.bind () It then continues to submit a task like eventLoop to send the channelActive event to the serverSocketChannel event handler to tell the upper code to handle:

In the pipeline. FireChannelActive () invocation chain, execution to DefaultChannelPipeline $HeadContext. ChannelActive () :

Ctx.firechannelactive () continues the event, and readIfIsAutoRead() runs down to register the OP_ACCEPT event. ReadIfIsAutoRead () has been run to AbstractNioChannel. DoBeginRead () method, and registered with the method of realize OP_READ events:

InterestOps (selectionKey, serverSocketChannel); interestOps (selectionKey, channel);

It can be seen that before the interested event is registered, the current registered event value of channel is 0,0 indicates that no event is registered. ServerSocketChannel The value of the event to be registered (readInterestOp field) is not 16, which corresponds to the OP_ACCEPT event. The readInterestOp field creates the srverSocketChannel object by passing selectionkey. OP_ACCEPT to the parent constructor: selectionKey.op_accept

After doBeginRead is executed, the ACCEPT event step is complete.

At this point, the Netty NIO Server starts and initializes, and then starts to listen and process client connection requests and receive data sent by clients.

7.0 Collecting and Processing I/O Events

7.1 Starting the eventLoop thread

During a NIO run, in order not to block the main thread and to keep the selector constantly collecting IO events that occur, it is common to put selector. Select () and the code that handles IO events into child threads for execution. In Netty, IO events are also collected and processed in child threads: these tasks are handed off to the eventLoop object of serverSocketChannel for asynchronous execution. As mentioned above, eventLoop initializes the internal thread object and starts the thread only on the first execution of the task. Looking back at the Netty startup steps, the program first submits a task to eventLoop during serverSocketChannel’s registration process, the actual registration phase of the code, In the AbstractChannel$abstractunsafe.register () method (Line 483) :

Submit the register0 task through the eventloop. execute method. EventLoop object here is NioEventLoop class instances, its the execute method in NioEventLoop SingleThreadEventExecutor the parent class:

Execute (Runnable, Boolean) is a private execute(runnable, Boolean) method. The inEventLoop() method is used to determine whether the thread environment of the submission task and the child thread environment of the current event loop are children of the same event loop. If the eventLoop thread has not started yet, it will return false:

// SingleThreadEventExecutor(Line 558)
// thread: Thread.currentThread()
// this.thread: an internal thread in eventLoop. This. Thread has not been started before the task has been executed
@Override
public boolean inEventLoop(Thread thread) {
  return thread == this.thread;
}
Copy the code

If the thread of the eventLoop object is not already running, call startThread()->doStartThread() to initialize the thread:

Can see, the final executor is through the internal eventLoop thread pool object to create a thread, and assign a value to the eventLoop thread objects, to complete the initialization, and then call SingleThreadEventExecutor. The run () method, launch wireless loop. NioEventLoop class implements SingleThreadEventExecutor. The run () method:

// NioEventLoop.java Line 435
@Override
protected void run(a) {
  int selectCnt = 0;
  for (;;) {
    try {
      int strategy;
      try {
        strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
        switch (strategy) {
          case SelectStrategy.CONTINUE:
            continue;

          case SelectStrategy.BUSY_WAIT:
            // fall-through to SELECT since the busy-wait is not supported with NIO

          case SelectStrategy.SELECT:
            long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
            if (curDeadlineNanos == -1L) {
              curDeadlineNanos = NONE; // nothing on the calendar
            }
            nextWakeupNanos.set(curDeadlineNanos);
            try {
              if (!hasTasks()) {
                strategy = select(curDeadlineNanos);
              }
            } finally {
              // This update is just to help block unnecessary selector wakeups
              // so use of lazySet is ok (no race condition)
              nextWakeupNanos.lazySet(AWAKE);
            }
            // fall through
          default:}}catch (IOException e) {
        // If we receive an IOException here its because the Selector is messed up. Let's rebuild
        // the selector and retry. https://github.com/netty/netty/issues/8566
        rebuildSelector0();
        selectCnt = 0;
        handleLoopException(e);
        continue;
      }

      selectCnt++;
      cancelledKeys = 0;
      needsToSelectAgain = false;
      final int ioRatio = this.ioRatio;
      boolean ranTasks;
      if (ioRatio == 100) {
        try {
          if (strategy > 0) { processSelectedKeys(); }}finally {
          // Ensure we always run tasks.ranTasks = runAllTasks(); }}else if (strategy > 0) {
        final long ioStartTime = System.nanoTime();
        try {
          processSelectedKeys();
        } finally {
          // Ensure we always run tasks.
          final long ioTime = System.nanoTime() - ioStartTime;
          ranTasks = runAllTasks(ioTime * (100- ioRatio) / ioRatio); }}else {
        ranTasks = runAllTasks(0); // This will run the minimum number of tasks
      }

      if (ranTasks || strategy > 0) {
        if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
          logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                       selectCnt - 1, selector);
        }
        selectCnt = 0;
      } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
        selectCnt = 0; }}catch (CancelledKeyException e) {
      // Harmless exception - log anyway
      if (logger.isDebugEnabled()) {
        logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?", selector, e); }}catch (Error e) {
      throw e;
    } catch (Throwable t) {
      handleLoopException(t);
    } finally {
      // Always handle shutdown even if the loop processing threw an exception.
      try {
        if (isShuttingDown()) {
          closeAll();
          if (confirmShutdown()) {
            return; }}}catch (Error e) {
        throw e;
      } catch(Throwable t) { handleLoopException(t); }}}}Copy the code

7.2 Collecting I/O Events

In an infinite loop executed by child threads, eventLoop continuously processes I/O events and non-I/O tasks submitted to the eventLoop queue from the application. (NioEventLoop not only processes I/O events internally, but also performs normal tasks, including tasks submitted by Netty programs or by users.) Netty NioEventLoop Is an event loop that processes I/O events. It first calls selector to collect the I/O events that occur (the collection phase) and then iterates through the results one by one (the processing phase). It’s just that because of Netty’s encapsulation and optimization, this process looks a little more complicated than the processing steps in Java NIO. An eventLoop combines selector select(), select(interval), and selectNow() methods to collect I/O events. The execution process can be divided into two situations (judging by hasTasks()) : current ordinary tasks and no ordinary tasks.

  • There are common tasks to be processed in eventLoop: The selectNow() method returns the collection result immediately, regardless of whether an IO event has occurred, and does not block the thread. This process is as shown in step 1 in the following code: SelectStrategy. CalculateStrategy method to judge if the current outstanding common tasks, call the selectNowSupplier. The get () method, this method is invoked in the selector. SelectNow () and return to collect the results, If there are no ordinary tasks to be processed, the return selectStrategy. SELECT is assigned to the strategy variable;

  • There are no pending tasks in the eventLoop task queue: the sWITH SELECT branch is executed as shown in step ② above: In this step, it is determined whether there are unprocessed common tasks because the submission and execution of tasks are performed asynchronously. Tasks may be submitted from other parts of the program between step 1 and step 2. When the task queue is still empty, select(interval) is called and the collection result is returned to the strategy variable. The select(interval) method further determines whether to execute selector. SelectNow () or selector. Select (interval), Select (interval) executes if no event occurs within the specified interval, and returns the following result:
// NioEventLoop.java Line 808
private int select(long deadlineNanos) throws IOException {
  if (deadlineNanos == NONE) {
    return selector.select();
  }
  // Timeout will only be 0 if deadline is within 5 microsecs
  long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
  return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}
Copy the code

7.3 Handling I/O Events

As can be seen from the process of COLLECTING I/O events, the strategy variable records the number of I/O events collected (strategy>0 indicates that I/O events occurred and were collected). Next to the IO event processing stage, the corresponding key code:

The processSelectedKeys() or runAllTasks() methods are executed depending on the criteria, processSelectedKeys() implements receiving and processing IO events, and runAllTask() implements executing tasks in the eventLoop queue. In addition to strategy, there is a key local variable ioRatio: Since PROCESSING IO events is a time-consuming process, this code uses the ioRatio variable to control the proportion of time spent processing IO events and executing queue tasks in order to balance the time spent processing I/O events and executing queue tasks to ensure that non-I/O tasks in the eventLoop queue can be executed. If I/O events are collected in this loop (strategy>0), processSelectedKeys() processes the collected I/O events one by one.

7.3.1 Processing Connection Requests

ProcessSelectedKey (SelectionKey K, AbstractNioChannel CH); processSelectedKey(SelectionKey K, AbstractNioChannel CH);

Ch.unsafe ().read() is called after retrieving the I/O event (OP_ACCEPT or OP_READ). ProcessSelectedKey (SelectionKey K, AbstractNioChannel CH); debug mode starts the Netty Server listening on port 8080 and uses TCP client tools to open a connection request:

Click start connection, return Idea, find the program has method first Line breakpoint, continue to Line 718:

First, the NioUnsafe object instance of the I/O channel object ch was unsafe. serverSocketChannel received the client connection request event OP_ACCEPT(the TCP client initiated the connection request just now). Ch corresponds to the NioServerSocketChannel class. In this case, ch represents the server channel serverSocketChannel. The realization of the corresponding ch. Unsafe () object, the superclass AbstractNioMessageChannel NioServerSocketChannel. Then step 2 gets all the collected events in SelectionKey and allocates them to the readyOps variable. As can be seen from the single step debugging, the readyOps value is 16, corresponding to the OP_ACCEPT event, so the program will run to Line 719. Unsafe. Read ()->doReadMessages(readBuf) to handle client connection requests:

SocketUtils. Accept method realized the serverSocketChannel. The accept () method call, accept the client connection requests and return the corresponding NIO connection channel object, The NIO socketChannel client connection channel object for Netty is then initialized with the NIO Channel object and Netty socketChannel is returned to the upper-layer call. At this point, the step of ⑥ accepting the client connection request and creating the connection channel is complete. Finally, the accept connection event is sent to the serverSocketChannel event handler via the pipeline.FireChannelRead method to notify the upper layer program and the user, Some further initialization can be done on the client connection channel socketChannel (including socketChannel registration and user-done business initialization). This will eventually call back to the childHandler object method that the user set when initializing serverBootStrap:

The NioSocketChannel object here creates a connection channel when it accepts a client connection. This is where you add a custom handler to socketChanel’s event handling pipeline using the ch.pipeline().addlast () method. You can then receive processing and send message data in the handler.

7.3.2 Receiving I/O Messages

After the Netty Server accepts and creates a client connection channel, the Netty Server can send and receive messages to each other. The creation and initialization of the client connection channel NioSocketChannel object is basically the same as the initialization of the server channel NioServerSocketChannel object, which will not be described here. The processSelectedKey(SelectionKey K, AbstractNioChannel CH) method is used to process the eventLoop I/O event. And the server program stays running, sending the string “Hello” from the TCP client to the server:

Then go back to Idea and see the breakpoint where the program is already running:

The ch object is an instance of the client connection channel NioSocketChannel. The collected I/O event value is 1, corresponding to the OP_READ event (the client just sent data to the server). The unsafe.read() method is implemented in the NioByteUnsafe class in AbstractNioByteChannel, the parent of NioSocketChannel. Program finally in NioSocketChannel. DoReadBytes (byteBuf) method to read data in a socketChannel:

The socketchannelRead ().fireChannelRead(byteBuf) method passes the read data to the handler handler for processing:

At this point, the step for the ⑦Netty Server to receive and process user messages is complete.

8.0 summary

The above is the entire process of Netty Server from initial configuration, starting the Server, initializing NIO ServerSocketChannel, Selector and other components, listening to the port, to accepting and establishing the client connection channel, receiving and processing the client message. The goal of this article is to clarify the overall process of Netty Server startup and initialization. In this article, the running process of Netty Server is combed according to the Java NIO startup process. The whole analysis process is not too difficult. The components of Netty, such as EventLoopGroup, EventLoop component, Channel’s event processing Pipeline component, Handler related component, and message related Bytebuf component, are not explained in the article. It is the key to further learning and understanding Netty. In the subsequent study of Netty, we will try to interpret these components one by one.