“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”

Source code analysis

NioEventLoop: NioEventLoop: NioEventLoop: NioEventLoop: NioEventLoop: NioEventLoop: NioEventLoop: NioEventLoop: NioEventLoop Look carefully, see behind will have a suddenly enlightened feeling!

In this lesson, we will learn the initialization source of ServerSocketChannel on the server side. First of all, we will still follow the old rules. I will tell you where to find it, how it is called step by step to ServerSocketChannel, and then analyze it!

One, entrance search

First of all, when we re – develop the Netty server, we will have the following lines of code:

ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss,work)
    .channel(NioServerSocketChannel.class)
    .childOption(ChannelOption.TCP_NODELAY,true)
    .childAttr(AttributeKey.newInstance("childAttr"),"childAttrValue") .handler(...) .childHandler(...) ; serverBootstrap.bind(8888).sync();
Copy the code

1. channel()

ServerBootstrap: childHandler (); ServerBootstrap: childHandler (); ServerBootstrap: childHandler (); ServerBootstrap: childHandler ();

public B channel(Class<? extends C> channelClass) {
    return channelFactory(new ReflectiveChannelFactory<C>(
        ObjectUtil.checkNotNull(channelClass, "channelClass"))); }Copy the code

In order to make the analysis process as simple as possible, we only analyze the main line code and branch code, I will make specific explanation when used:

We can see the code above, which wraps the channel type NioServerSocketChannel we passed in as a ReflectiveChannelFactory object. From the name, we can almost tell that it is a ReflectiveChannelFactory. Then pass the ReflectiveChannelFactory object into the channelFactory method.

public B channelFactory(ChannelFactory<? extends C> channelFactory) {... Ignore unnecessary code......// Save the SocketChannel wrapper object
    this.channelFactory = channelFactory;
    return self();
}
Copy the code

As we can see, it just saves the wrapper object for our NioServerSocketChannel!

Let’s go back and see what ReflectiveChannelFactory does:

public ReflectiveChannelFactory(Class<? extends T> clazz) {
    try {
        //.channel The incoming NioServerSocketChannel
        this.constructor = clazz.getConstructor();
    } catch(NoSuchMethodException e) { ........................................ }}Copy the code

As you can see, the logic of ReflectiveChannelFactory is very simple, just take the NioServerSocketChannel we passed in, get its empty constructor, and save it!

2. childHandler()

Going back to the childHandler method, the basic principle is the same:

public ServerBootstrap childHandler(ChannelHandler childHandler) {
    this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
    return this;
}
Copy the code

It’s the same logic, but it saves our Settings to the outbound and inbound processor, and doesn’t do much else. The rest of the method you can try to analyze, all of it is to save some properties we want to set for future calls!

3. The bind method

We’re talking about some properties that are saved, so where do we call them? The main method is the bind() method, which is the main entry to start the server!

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

First, he wraps the port as an InetSocketAddress object, much as we did in NIO development, and we continue with it:

public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}

// There is nothing more to say
private ChannelFuture doBind(final SocketAddress localAddress) {
        // Create a channel on the server
        // Initialize and register a Channel, and return a ChannelFuture instance regFuture async
        finalChannelFuture regFuture = initAndRegister(); . The rest of the code is then analyzed.............. }Copy the code

We go down two levels and finally see a large chunk of code. We only analyze the first line of code, and then all the rest of the code. In this lesson, we only focus on the NioServerSocketChannel code, and we go into initAndRegister

I. initAndRegister

final ChannelFuture initAndRegister(a) {
        Channel channel = null;
        try {
            // create channel reflection on the server
            //io.netty.channel.ReflectiveChannelFactory.newChannel
            channel = channelFactory.newChannel();
            // Initialize the channel
            init(channel);
        }case{... Ignore... }... Ignore... }Copy the code

Here we call channelFactory. NewChannel () creates a Channel object, channelFactory is what? When we set the ServerSocketChannel, we will internally wrap the channelFactory as a ReflectiveChannelFactory object. We follow up the io.net ty. Channel. ReflectiveChannelFactory# newChannel source code:

@Override
public T newChannel(a) {
    try {
        // Reflection creates NioServerSocketChannel
        return constructor.newInstance();
    } catch(Throwable t) { ........................................ }}Copy the code

Create a NioServerSocketChannel object using the constructor object saved when we rebuilt the ReflectiveChannelFactory! We need to go into the NioServerSocketChannel’s no-parameter construct to find its logic!

Second, source code analysis

NioServerSocketChannel: socketChannel: socketChannel: socketChannel: socketChannel: SocketChannel: SocketChannel

/** * Create a new instance */
public NioServerSocketChannel(a) {
    //DEFAULT_SELECTOR_PROVIDER:SelectorProvider.provider()
    //newSocket creates a channel
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
Copy the code

First, let’s focus on the newSocket method:

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        return provider.openServerSocketChannel();
    } catch(IOException e) { ......................... }}Copy the code

The newSocket method uses the provider to create a JDK underlying ServerSocketChannel. Note that this object is the JDK’s original Channel object. Let’s go back to the no-parameter constructor:

public NioServerSocketChannel(ServerSocketChannel channel) {
    // Save the configuration item and the connection concern event OP_ACCEPT
    super(null, channel, SelectionKey.OP_ACCEPT);
    // Create a configuration class that stores the current object and the underlying JDK socket
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
Copy the code

Let’s look at the super method, which passes in the SocketChannel underlying the JDK NIO created in the previous step and a client access event.

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



// Nothing more to say
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    // Create key data
    super(parent);
    // Save the underlying JDK channel
    this.ch = ch;
    // Save the event of concern
    this.readInterestOp = readInterestOp;
    try {
        // Set it to non-blocking
        ch.configureBlocking(false);
    } catch(IOException e) {...... }}Copy the code

Let’s skip the super method for the moment and analyze the following methods first. After analyzing the following methods, we will analyze the super method in reverse:

  1. First, save the JDK NIO Channel object we obtained earlier!
  2. Save the selectionKey. OP_ACCEPT event passed in earlier!
  3. Call the JDK NIO method and set the native Channel to non-blocking!

These objects will be saved here, so when you use these properties later, don’t forget where they came from!

Let’s start analyzing the super method

protected AbstractChannel(Channel parent) {
    / / save the channel
    this.parent = parent;
    // The unique identifier of a channel
    id = newId();
    // The underlying JDK operation reads and writes classes
    //unsafe operates the underlying read and write
    //NioServerSocketChannel creates NioMessageUnsafe, which handles connections
    //NioSocketChannel creates NioByteUnsafe, which handles byte reads
    unsafe = newUnsafe();
    // Pipeline is responsible for orchestration of business processors
    pipeline = newChannelPipeline();
}
Copy the code
  1. First, we will create an ID, which you can think of as a unique id. There are long ids and short ids, which can uniquely identify a Channel. From this line of code, we can see that each Channel object has a unique ID.

  2. To create a newUnsafe, to access this line of code, you need to know the NioServerSocketChannel inheritance, or else a large chunk will appear like this:

  3. You don’t know which source to read. To understand this, I have to understand its class hierarchy, NioServerSocketChannel inheritance relationship below:

As shown in figure can see, NioServerSokcetChannel inheritance in AbstractNioMessageChannel, so, we will naturally into AbstractNioMessageChannel implementation:

@Override
protected AbstractNioUnsafe newUnsafe(a) {
    return new NioMessageUnsafe();
}
Copy the code

As you can see, what’s returned here is a NioMessageUnsafe, and I want you to remember the unsafe property in the NioServerSocketChannel object, which is of the NioMessageUnsafe type!

After looking over the broadening property type, we’ll go back to the main line and look at pipeline initialization. The newChannelPipeline method looks at the source code. Looking at the inheritance diagram above, it’s easy to navigate to this object:

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

Here we create a DefaultChannelPipeline object and continue:

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

Note that we create a bidirectional linked list with tail and head nodes by default.

From the above analysis, we can know that the pipeline attribute will create a bidirectional linked list by default when the NioServerSocketChannel is initialized, and there are two nodes by default, the head node and the tail node, and form a bidirectional linked list!

At this point, the creation of NioServerSocketChannel is complete,

We go straight back to the initAndRegister method where the reflection created the Channel in the first place:

channel = channelFactory.newChannel();
init(channel);
Copy the code

Here we create a channel object by reflection. After the above process, it becomes a rudimentary channel. We need to make another call to initialize it for further use.

// The.option method is passed in
setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
// the.attr method is passed in
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
Copy the code

This is just setting the parameters passed in by the.option and.attr we built into the channel!

// Get the pipe
ChannelPipeline p = channel.pipeline();
// Get the worker Group
final EventLoopGroup currentChildGroup = childGroup;
// Get the.childHandler set earlier
final ChannelHandler currentChildHandler = childHandler;
// Gets the.childOption method set earlier
finalEntry<ChannelOption<? >, Object>[] currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);// Get the.attr property set earlier
finalEntry<AttributeKey<? >, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);Copy the code
  1. Get the pipe we created when we initialized the ServerSocketChannel
  2. Gets the properties associated with childXXXX () set when ServerBootstrap was created
p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) {
        final ChannelPipeline pipeline = ch.pipeline();
        // Add user-defined handlers to the pipeline handler is the handler passed in when ServerBootStr is built
        ChannelHandler handler = config.handler();
        if(handler ! =null) {
            pipeline.addLast(handler);
        }
        ch.eventLoop().execute(() -> {
            pipeline.addLast(newServerBootstrapAcceptor( ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); }); }});Copy the code

P is the pipe we create when we create the Channel object. There are two nodes by default, as we explained above. So what does addLast do? Let’s see:

private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}
Copy the code

Here I have captured an important piece of code, which I will explain in detail in a later chapter. It can be seen from the above code that it wants to append a handler to the bidirectional linked list. At this point, our pipeline becomes the following format:

Third, summary

  1. Use ServerBootstrap to set properties such as SERVER SocketChannel, Handler, and so on
  2. Bind method to create a NioServerSocketChannel
    1. Save the JDK native SocketChannel and set it to non-blocking
    2. Create and save a unique ID for the channel
    3. Create an unsafe object, which is of type NioMessageUnsafe
    4. Create a bidirectional linked list with Head and Tail nodes
  3. Initialize the created channel, set the custom configuration, and add a ChannelInitializer to the two-way list!

NioServerSocketChannel initialization is complete!