background

Netty is an asynchronous event-driven network communication layer framework

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

We choose Netty as the underlying network communication framework in SAILFISH (daily push message volume of 5 billion) and SHARK (daily throughput of 3 billion).

Since two such important systems at the bottom of the use of NetTY, so it is necessary to netTY mechanism, and even the source code if at all, so, it spawned netTY source code series articles. Later, I will unreservedly introduce what I have learned from the Netty source code, which is based on 4.1.6.Final, through a series of topics

why netty

Netty’s underlying NIO is based on JDK, why don’t we just base it on JDK NIO or another NIO framework? Here are my reasons

1. The NIO in JDK requires too many concepts to understand, and the programming is complex. 2. 4.NET TY solves a lot of bugs in JDK, including empty wheel training. 5.NET TY has done a lot of small optimization on threads and selectors. The well-designed Reactor thread can achieve very efficient concurrent processing 6. 8.NET TY has been extensively validated across RPC frameworks, message-oriented middleware, and distributed communication middleware lines, making it extremely robust

dive into netty

With that in mind, today we’ll start our journey through Netty source code with an example.

This article focuses on how Netty binds ports and starts services. As you start the service, you will learn about the core components of Netty. I won’t go into detail about these components, but I will tell you how they are strung together to form the core of Netty

example

Here is a very simple server startup code

public final class SimpleServer {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new SimpleServerHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {}}); ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally{ bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }}private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelActive");
        }

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelRegistered");
        }

        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            System.out.println("handlerAdded"); }}}Copy the code

In a few simple lines of code, you can start a server, bind to port 8888, and use NIO mode. The details of each step are described below

An EventLoopGroup, which I have dissected in detail in other articles, is basically an infinite loop that continuously detects IO events, processes IO events, and executes tasks

ServerBootstrap is a bootstrap class on the server that binds ports to start the service by setting a set of parameters

Group (bossGroup, workerGroup) we need two types of people to do the work, one is the boss and the other is the worker, and the boss takes the work from the outside, gets the work from the workers, puts it in here, and the bossGroup’s function is to constantly accept new connections, Throw the new connection to the workerGroup for processing

Channel (NioServerSocketChannel. Class) said the server startup is nio related channel, the channel is a core concept in netty, can be understood as a channel is a connection or a server bind action, More on that later

.handler(new SimpleServerHandler() indicates the process that SimpleServerHandler needs to go through during server startup. Here, SimpleServerHandler’s final top-level interface is ChannelHander, which is a core concept of Netty. The processor that represents the data flow through can be thought of as each level in the pipeline

childHandler(new ChannelInitializer

)… After a new connection comes in, how to deal with it, that is, what the boss said above, how to match the workers

ChannelFuture f = b.bind(8888).sync(); Here is the real startup process, bind port 8888, wait for the server startup, will enter the downstream code

f.channel().closeFuture().sync(); Wait for the server to close the socket

bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); Close both sets of infinite loops

The above code can easily run locally again, and the final console output is:

handlerAdded
channelRegistered
channelActive
Copy the code

It’s easy to dig a little deeper into why this is being sequential, right

Further details

ServerBootstrap is a set of parameters that are stored using method chaining to start the server. Our focus falls on the following code

b.bind(8888).sync();
Copy the code

Here’s a sentence: If we are just beginning to look at the source code, we can use the IDE debug function step by step, one step one test, or binary test to determine which line of code is the final entry to start the service. In this case, we have determined that the bind method is the entry. Let’s go in there and analyze

public ChannelFuture bind(int inetPort) {
    return bind(new InetSocketAddress(inetPort));
} 
Copy the code

Create an InetSocketAddress with the port number and continue with the bind

public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    return doBind(localAddress);
}
Copy the code

Validate () validates the necessary parameters needed to start the service, and then calls doBind()

private ChannelFuture doBind(final SocketAddress localAddress) {
    / /...
    final ChannelFuture regFuture = initAndRegister();
    / /...
    final Channel channel = regFuture.channel();
    / /...
    doBind0(regFuture, channel, localAddress, promise);
    / /...
    return promise;
}
Copy the code

I’m going to cut out the details here, let’s focus on the core methods, but there are really two core methods initAndRegister() and doBind0()

In fact, we already know a little bit from the method name, init-> initialize, register-> register, so what do we register to? To get to the registration of the polisher in NIO, maybe you initialize something and then you register it with a selector, and then you bind, you know, the port number locally, so with that in mind, we’re going to go a little bit further

initAndRegister()

final ChannelFuture initAndRegister(a) {
    Channel channel = null;
    // ...
    channel = channelFactory.newChannel();
    / /...
    init(channel);
    / /...
    ChannelFuture regFuture = config().group().register(channel);
    / /...
    return regFuture;
}
Copy the code

Still focusing on the core code, leaving aside the bits and pieces, we see that initAndRegister() does a few things: 1. New a channel; 2. Init the channel 3. Register the channel into an object

Let’s analyze these three things step by step

1. A new channel

Netty defines a channel as follows

A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind

In this case, since the channel is created when the service is started, we can represent a pipeline through which the server is bound, corresponding to the ServerSocket in normal Socket programming

It turns out that this channel is channelFactory new, which has a very simple interface

public interface ChannelFactory<T extends Channel> extends io.netty.bootstrap.ChannelFactory<T> {
    /** * Creates a new channel. */
    @Override
    T newChannel(a);
}
Copy the code

With just one method, let’s look at where channelFactory is assigned

AbstractBootstrap.java

public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    if (channelFactory == null) {
        throw new NullPointerException("channelFactory");
    }
    if (this.channelFactory ! =null) {
        throw new IllegalStateException("channelFactory set already");
    }

    this.channelFactory = channelFactory;
    return (B) this;
}
Copy the code

It’s assigned here, and we go back and look at where this function was called, and it turns out that it’s in this function that ChannelFactory is new

public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
Copy the code

Here, when our demo calls the channel(channelClass) method, it uses channelClass as the constructor of ReflectiveChannelFactory to create a ReflectiveChannelFactory

The code of the Demo is as follows:

.channel(NioServerSocketChannel.class);
Copy the code

And then we go back to the beginning of this section

channelFactory.newChannel();
Copy the code

We can infer that, in the end, is calling to ReflectiveChannelFactory. NewChannel () method, follow up

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Class<? extends T> clazz;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        this.clazz = clazz;
    }

    @Override
    public T newChannel(a) {
        try {
            return clazz.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class "+ clazz, t); }}}Copy the code

See clazz. NewInstance (); , we understand, is to create an object by means of reflection, and this class is our incoming in ServerBootstrap NioServerSocketChannel. Class

As a result, it goes all the way around and creating a channel is equivalent to calling the default constructor new to create a NioServerSocketChannel object

Details here, read the source code, there are two ways of reading, one kind is back, can use an object step by step back, for example, will find at the beginning of the object is to create the code block, there is a kind of way is a top-down, stepwise analysis, generally with the analysis of the specific method, skilled and magical craftsmanship, the last piece together a complete process

We can then focus on the default constructor of the NioServerSocketChannel

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public NioServerSocketChannel(a) {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
Copy the code

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    / /...
    return provider.openServerSocketChannel();
}
Copy the code

Through SelectorProvider. OpenServerSocketChannel () to create a server side channel, then into the following method

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
Copy the code

Here is the first line of code and ran into the parent class, the second line, a NioServerSocketChannelConfig new comes out, its top level interface for ChannelConfig, netty official described as follows

A set of configuration properties of a Channel.

Basically can determine, ChannelConfig is also a netty inside a core module, first look at the source code, see here, we don’t need to dig into this object, but when used to come back to dig, as long as remember, this object is created when the NioServerSocketChannel object

We continue to trace the NioServerSocketChannel’s parent class

AbstractNioMessageChannel.java

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}
Copy the code

Keep chasing

AbstractNioChannel.java

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    / /...
    ch.configureBlocking(false);
    / /...
}
Copy the code

Here, simply to front the provider. OpenServerSocketChannel (); Create a ServerSocketChannel and save it to a member variable, then call ch.configureBlocking(false); Set the channel to non-blocking mode, standard JDK NIO programming play

Selectionkey.op_accept; selectionKey.op_accept; super(parent); (Parent is null and passed in)

AbstractChannel.java

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}
Copy the code

Here, we have three new components, and we assign values to member variables, which are

id = newId();
protected ChannelId newId(a) {
    return DefaultChannelId.newInstance();
}
Copy the code

The ID is the unique identifier of each channel in Netty

unsafe = newUnsafe();
protected abstract AbstractUnsafe newUnsafe(a);
Copy the code

Look at the definition for Unsafe

Unsafe operations that should never be called from user-code. These methods are only provided to implement the actual transport, and must be invoked from an I/O thread

After capturing another big component of Netty, we can forget what TA does, except that the newUnsafe method here finally belongs to the NioServerSocketChannel class

The last

pipeline = newChannelPipeline();

protected DefaultChannelPipeline newChannelPipeline(a) {
    return new DefaultChannelPipeline(this);
}

protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
}

Copy the code

When we first look at this code, we may not know what DefaultChannelPipeline is for. We still use the above method to look at the definition of the top-level interface ChannelPipeline

A list of ChannelHandlers which handles or intercepts inbound events and outbound operations of a Channel

As you can see from the documentation for this class, this interface is basically one of the core modules of Netty

At this point, we have finally created a server channel. When we put these details together, we have extracted the basic components of Netty, summarized as follows

  • Channel
  • ChannelConfig
  • ChannelId
  • Unsafe
  • Pipeline
  • ChannelHander

When we first look at the code, our goal is to follow the line of code that the server starts. We will write down the above components first, and when the code is finished, we can analyze them from top to bottom, layer by layer. I will go into each component in the source code series later

In conclusion, the user calls the method bootstrap.bind (port) to create a New NioServerSocketChannel object by means of reflection. In the process of creating the new object, a series of core components are created

2. The init this channel

At this point, you might want to skip to the very beginning of the article and remember, the first step, the newChannel, we’re going to do init on that channel, and what does init do? We’re going to go deeper

@Override
void init(Channel channel) throws Exception {
    finalMap<ChannelOption<? >, Object> options = options0();synchronized (options) {
        channel.config().setOptions(options);
    }

    finalMap<AttributeKey<? >, Object> attrs = attrs0();synchronized (attrs) {
        for(Entry<AttributeKey<? >, Object> e: attrs.entrySet()) {@SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }

    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    finalEntry<ChannelOption<? >, Object>[] currentChildOptions;finalEntry<AttributeKey<? >, Object>[] currentChildAttrs;synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
    }

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if(handler ! =null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run(a) {
                    pipeline.addLast(newServerBootstrapAcceptor( currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); }}); }}); }Copy the code

When you first see this, you might think, wow, that’s a long way to go. Is that how you look at it? Do you still remember what we said before? Step-by-step disassembly and finally normalization. The following is my disassembly steps

1. Set Option and attr

finalMap<ChannelOption<? >, Object> options = options0();synchronized (options) {
        channel.config().setOptions(options);
    }

    finalMap<AttributeKey<? >, Object> attrs = attrs0();synchronized (attrs) {
        for(Entry<AttributeKey<? >, Object> e: attrs.entrySet()) {@SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); }}Copy the code

We call options0() and attrs0(), and then inject the options and attrs into channelConfig or channel. You don’t need to know much about options and attr. Just look at ChannelOption, the top interface, and look at the specific inheritance of channel, which I’ll save for the source analysis series later

2. Set the option and attR of the new access channel

final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
finalEntry<ChannelOption<? >, Object>[] currentChildOptions;finalEntry<AttributeKey<? >, Object>[] currentChildAttrs;synchronized (childOptions) {
    currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
    currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
Copy the code

Here, similar to above, except that instead of setting these two properties for the current channel, we set the corresponding channel for the new incoming connection. Since we are only concerned with how the server is started in this article, the access connection will be analyzed in detail in the next article, okay

3. Add a new connection processor

p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if(handler ! =null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run(a) {
                    pipeline.addLast(newServerBootstrapAcceptor( currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); }}); }});Copy the code

As a final step, p.adddlast () adds a ServerBootstrapAcceptor to the Pipeline of the serverChannel. As you can see from the name, this is an accessor that accepts new requests and throws them to an event loop. Let’s not do too much analysis

To sum up, init does not start the service. It just initializes some basic configuration and properties, and adds an accessor to the pipeline to accept new connections

3. Register the channel to an object

In this step, we are analyzing the following methods

ChannelFuture regFuture = config().group().register(channel);
Copy the code

Call register in NioEventLoop

@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}
Copy the code
@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}
Copy the code

Okay, now that WE’re at this point, remember what object should be returned from unsafe()? Now, if you don’t remember, you can look at the previous description of unsafe, or the fastest way to do that is to debug over here, follow the register method, and see what type of unsafe it is

We followed in and found out it was

AbstractUnsafe.java

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // ...
    AbstractChannel.this.eventLoop = eventLoop;
    // ...
    register0(promise);
}
Copy the code

Again, we just need to focus, bind the EventLoop EventLoop to the NioServerSocketChannel, and then call register0()

private void register0(ChannelPromise promise) {
    try {
        boolean firstRegistration = neverRegistered;
        doRegister();
        neverRegistered = false;
        registered = true;

        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if(config().isAutoRead()) { beginRead(); }}}catch(Throwable t) { closeForcibly(); closeFuture.setClosed(); safeSetFailure(promise, t); }}Copy the code

This one is actually pretty clear, so call doRegister(); , specific do speak again later, and then call invokeHandlerAddedIfNeeded (), as a result, the console the first line is printed

handlerAdded
Copy the code

How does it end up being called, which we’ll talk about later when we parse the pipeline

Then call pipeline. FireChannelRegistered (); After the call, the console displays as

handlerAdded
channelRegistered
Copy the code

Keep going

if (isActive()) {
    if (firstRegistration) {
        pipeline.fireChannelActive();
    } else if(config().isAutoRead()) { beginRead(); }}Copy the code

At this point, you might think that the last line on the console

pipeline.fireChannelActive();
Copy the code

With this line of code output, let’s first look at the isActive() method

@Override
public boolean isActive(a) {
    return javaChannel().socket().isBound();
}
Copy the code

It’s finally called into the JDK

ServerSocket.java

    /**
     * Returns the binding state of the ServerSocket.
     *
     * @return true if the ServerSocket succesfuly bound to an address
     * @since1.4 * /
    public boolean isBound(a) {
        // Before 1.3 ServerSockets were always bound during creation
        return bound || oldImpl;
    }
Copy the code

Here isBound() returns false, but from the process we’ve followed so far, we haven’t bound a ServerSocket to an address, so isActive() returns false, We don’t have successfully into the pipeline. FireChannelActive (); Method, so who is the last line of output, we are a little crazy, in fact, as long as you are familiar with the IDE, to locate the function call stack, it is very easy

Here’s how I use Intellij to locate function calls

We put a breakpoint on this line of code where the final output text is, and we debug, and we run to this line, and intellij automatically pulls up the call stack for us, and the only thing we have to do is move the arrow keys, and we see the full chain of calls to the function

If you see that the most recent initiate of a method is the run method of a thread Runnable, then put a breakpoint where the Runnable object method was committed, remove other breakpoints, and debug again. For example, we first debug the latest Runnable in the call stack as follows

if(! wasActive && isActive()) { invokeLater(new Runnable() {
        @Override
        public void run(a) { pipeline.fireChannelActive(); }}); }Copy the code

We stopped at the line of the pipeline. FireChannelActive (); If we want to look at the initial call, we have to jump out and break at if (! WasActive && isActive()), because many tasks in Netty are called by the asynchronous (reactor) thread (see the last step in the Reactor thread trilogy). If we want to look at the first method call, We have to look at where the Runnable was committed, recursively, to find the line of “missing code.”

Finally, in this way, finally find the pipeline. The fireChannelActive (); The code that makes the call, unfortunately, happens to be the doBind0() method below

doBind0()

private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run(a) {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else{ promise.setFailure(regFuture.cause()); }}}); }Copy the code

We find that when calling doBind0(…) Netty source code Analysis: Unlocking the mask of the Reactor thread

Ok, next we go to the channel.bind() method

AbstractChannel.java

@Override
public ChannelFuture bind(SocketAddress localAddress) {
    return pipeline.bind(localAddress);
}
Copy the code

Discovery is a call to the PIPELINE’s bind method

@Override
public final ChannelFuture bind(SocketAddress localAddress) {
    return tail.bind(localAddress);
}
Copy the code

If you don’t know what tail is, you can go back to the beginning. Tail was used to create pipelines. I will explain the tail classes later in the source code series, but the only good way to know where the code is going is to debug step in. For reasons of space, I won’t go into detail

Finally, we come to the following area

HeadContext.java

@Override
public void bind( ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
        throws Exception {
    unsafe.bind(localAddress, promise);
}
Copy the code

Unsafe should be AbstractUnsafe, or NioMessageUnsafe

We go to its bind method

@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    // ...
    boolean wasActive = isActive();
    // ...
    doBind(localAddress);

    if(! wasActive && isActive()) { invokeLater(new Runnable() {
            @Override
            public void run(a) { pipeline.fireChannelActive(); }}); } safeSetSuccess(promise); }Copy the code

Obviously following the normal process, we have already analyzed isActive(); Method returns false, into the doBind (), if the channel is activated, it launched a pipeline. The fireChannelActive (); Calls, eventually calls the user method, prints out the last line on the console, so now you see why you end up printing out those three lines in order on the console

The doBind() method is also simple

protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        //noinspection Since15
        javaChannel().bind(localAddress, config.getBacklog());
    } else{ javaChannel().socket().bind(localAddress, config.getBacklog()); }}Copy the code

Finally, you get to the BIND method in the JDK, and after that line of code, normally, you actually bind the port.

In addition, through the way of the top-down analysis of pipeline. The call fireChannelActive (); Method, the following method is called

HeadContext.java

public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelActive();

    readIfIsAutoRead();
}
Copy the code

Enter the readIfIsAutoRead

private void readIfIsAutoRead(a) {
    if(channel.config().isAutoRead()) { channel.read(); }}Copy the code

Analyze the isAutoRead method

private volatile int autoRead = 1;
public boolean isAutoRead(a) {
    return autoRead == 1;
}
Copy the code

As you can see, the isAutoRead method returns true by default, which leads to the following method

public Channel read(a) {
    pipeline.read();
    return this;
}
Copy the code

The final call is to

AbstractNioUnsafe.java

protected void doBeginRead(a) throws Exception {
    final SelectionKey selectionKey = this.selectionKey;
    if(! selectionKey.isValid()) {return;
    }

    readPending = true;

    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) { selectionKey.interestOps(interestOps | readInterestOp); }}Copy the code

This. SelectionKey is the object that we returned in the register step. When we register, the ops is 0

Remember registration

AbstractNioChannel

selectionKey = javaChannel().register(eventLoop().selector, 0.this)
Copy the code

So this is the equivalent of taking the registered OPS, passing the if condition, and calling it

selectionKey.interestOps(interestOps | readInterestOp);
Copy the code

Selectionkey.op_accept, selectionKey.op_Accept, selectionKey.op_Accept, selectionKey.op_Accept

summary

Finally, let’s wrap up the process 1 through which Netty starts a service. Set to start the class parameter, the most important thing is to set the channel 2. Create a server corresponding channel, create the major components, including ChannelConfig, ChannelId, ChannelPipeline, ChannelHandler, Unsafe, etc. 3. Initialize the corresponding channel of the server, set some attr and option, and set attr and option of sub-channel, add new channel accessor to the server channel, and start addHandler,register and other events 4. Call the underlying JDK to do the port binding, and trigger the active event, when the active trigger, the actual service port binding

In addition, the article to read the source code in detail may also be able to bring you some help.

If you want to learn Netty systematically, my small book “Netty Introduction and actual Practice: Imitation wechat IM instant messaging system” can help you, if you want to learn Netty principles systematically, then you must not miss my Netty source analysis series of videos: Coding.imooc.com/class/230.h…