Port Binding Netty Series server source code analysis of Channel

Fanger Wei code or search public micro-channel number of the next scan 菜鸟飞呀飞, you can focus on micro-channel public number, read more Spring源码分析, and Java并发编程articles.

Micro-channel public number

problem

This article is followed by the first two articles written by friends who are interested can go read the next two articles: Netty series of source code analysis server Channel initialization and Netty series of source code analysis server Channel Register

Since Netty is a NIO package native JDK, JDK native contrast NIO wording, we can first consider the following two questions.

  1. NIO in JDK written in native, calls serverSocketChannel.bind (new InetSocketAddress (port)) bind port number, then Netty, the operation binding the port number is somewhere to achieve it?

  2. In JDK NIO written in the native, in the time register to the multiplexer ServerSocketChannel Selector, will be provided for the event of interest ServerSocketChannel OP_ACCEPT event, and in Netty, when the channel register Selector to the channel of interest event set is 0 , that is not interested in any event. Then in Netty, what is of interest when the server channel event is set OP_ACCEPT it?

JDK native NIO wording follows.

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 轮询器,不同的操作系统对应不同的实现类
Selector selector = Selector.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
// 将服务端channel注册到轮询器上,并告诉轮询器,自己感兴趣的事件是ACCEPT事件
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
复制代码

Port Binding

When you call serverBootstrap.bind (port) , the calls to AbstractBootstrap.doBind (final SocketAddress localAddress) method. In doBind (localAddress) method, will first call initAndRegister () method to initialize the server Channel and two articles in the server channel registered on the multiplexer, which has previously been detailed source code analysis Netty source code analysis Channel series server initialization and Netty source code analysis server Channel series registered analyzed, and today will be followed by analysis (localAddress) doBind logic behind. Source doBind (localAddress) method as follows

private ChannelFuture doBind(final SocketAddress localAddress) {
    //初始化和注册
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }
    // 无论是进入if还是进入else,最终均会执行doBind0()
    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        // Registration future is almost always fulfilled already, but just in case it's not.
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                // cause!= null 表示channel在初始化和注册过程中出席那了异常
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
                    // 没有异常,执行doBind0()
                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}
复制代码

When initAndRegister () execution is complete, it will return a ChannelFuture . ChannelFuture implement the Future Interface (For more information about Future can refer to this article: Concurrent Programming Series Future - the most commonly used performance optimization tools ), call ChannelFuture.isDone () can determine initAndRegister () method has not been implemented is completed ( because initAndRegister () method using asynchronous execution threads other logic, when the initAndRegister () method returns, it does not necessarily have logic inside the execution completion ).

If initAndRegister () method has been performed completely finished, if the logic proceeds to block, and then call doBind0 () ; if initAndRegister () is not executed, else it into the logical block. In else by adding a listener to listen initAndRegister () if executed, when executed, the listener will be able to immediately know initAndRegister () has completed execution, then still call doBind0 () method.

If so whether it is into the logical blocks, or else logic blocks are ultimately call doBind0 () method. Source doBind0 () method is as follows.

private static void  doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {
    /**
     * 此时的channel就是NioServerSocketChannel
     * channel.eventLoop()获取到的就是服务端channel绑定的NioEventLoop线程
     */
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
            	// 调用NioServerSocketChannel的bind()方法
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}
复制代码

At this time, the channel is an example of NioServerSocketChannel, channel.eventLoop () is acquired server channel NioEventLoop binding threads (bound in initAndRegister () method). Therefore doBind0 () method is actually through NioEventLoop asynchronous execution threads channel.bind () , source channel.bind () method is as follows.

public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return pipeline.bind(localAddress, promise);
}
复制代码

channel.bind () it is actually a call to bind pipeline in the channel () method. Source pipeline.bind () method is as follows.

public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return tail.bind(localAddress, promise);
}
复制代码

pipeline.bind () method, a pipeline is called the tail nodes bind () method. In tail.bind () method, it does not do other processing logic, just call the pipeline in the next node in the bind () method (here is start looking forward pipeline from the tail node), so the final will be called to the head node the bind () method. bind head node () method as the source.

public void bind(
        ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
    unsafe.bind(localAddress, promise);
}
复制代码

For the purposes of the server channel, unsafe here it is NioMessageUnsafe instance of the type (constructor in the unsafe NioServerSocketChannel the instantiated). Bind NioMessageUnsafe class () method is defined in AbstractChannel, the streamlined source code is as follows.

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    // 省略部分代码...

    // 此时由于服务端channel还没哟绑定端口号,所以isActive()返回的是false
    boolean wasActive = isActive();
    try {
        // 真正绑定端口号的操作
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
    // 经过doBind(localAddress)这一步,服务端channel已经绑定了端口号,所以isActive()会返回true
    // !wasActive && isActive() 运算的结果为true
    if (!wasActive && isActive()) {
        // 通过线程异步执行任务:在pipeline中传播执行handler的channelActive()方法
        invokeLater(new Runnable() {
            @Override
            public void run() {
            	// 通过pipeline来传播执行handler的channelActive()方法
                pipeline.fireChannelActive();
            }
        });
    }

    safeSetSuccess(promise);
}
复制代码

It can be seen in the bind () method, call the doBind (localAddress) method, do see the beginning of the method name, finally happy about the whole thing, because the method usually do at the beginning of the method, are doing real things. Practical Indeed, in doBind () method, to do the operation of the port number binding. Source doBind NioServerSocketChannel class () method is as follows.

protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}
复制代码

Which javaChannel () Gets the JDK native channel, namely ServerSocketChannel, and then call the JDK NIO native API: the bind (localAddress, backlog) , this step is executed finish, channel Netty in the service side of it and the port number to achieve a binding, solve the first problem in the beginning of the article. However, the second problem has not yet resolved, so continue to look back.

When doBind () method after the implementation, back AbstractChannel.bind () method in this case, because the server channel has been bound port number, so ! WasActive && isActive () evaluates to true, so will be entered into if logic blocks. If the logic blocks, and asynchronous execution of pipeline.fireChannelActive () , you can guess by the name of the method, this step is doing something that is spread chanelActive () method of execution handler in the pipeline. pipeline.fireChannelActive () of source code as follows.

public final ChannelPipeline fireChannelActive() {
    // 从head节点开始传播
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}
复制代码

You can see, will start from the head node spread the implementation of the pipeline, invokeChannelActive (head) eventually calls to channelActive handler's (ChannelHandlerContext ctx). The following look head node channelActive (ctx) method, the head node is HeadContext type, so the code found HeadContext.channelActive (ctx), its source code is as follows.

public void channelActive(ChannelHandlerContext ctx) {
    // 将channelActive时间传播给pipeline中下一个handler
    ctx.fireChannelActive();
    // 如果服务端channel的autoRead属性为1,就表示自动读数据,那么这个时候,就开始读数据
    // 默认情况下,autoRead属性为1,即开启自动读功能
    readIfIsAutoRead();
}
复制代码

Two lines of code, first of all first line of code, is to continue to spread in the pipeline execution channelActive (ctx) method on, there will no longer continue to read (in fact, there is no need to see, because the pipeline on the service side of the four nodes other channelActive few nodes (ctx) method, do not do important logic processing, and therefore can not see).

Look at the second line of code, it calls readIfIsAutoRead () method, the method name translation is: If the state is automatically read, they begin to read, by default, automatically read the switch is turned on, the default start reading. Here may be a bit ignorant force, to read exactly what is it? So we continue to follow up readIfIsAutoRead () source method.

private void readIfIsAutoRead() {
    // 默认自动读
    if (channel.config().isAutoRead()) {
        // 读数据
        channel.read();
    }
}
复制代码

Since autoRead default equal to 1, so channel.config (). IsAutoRead () returns true, if so enters logic block, call channel.read () method. In this case, the server channel, so the call is NioServerSocketChannel the read () method. The method defined in AbstractChannel, the source follows.

public Channel read() {
    // 从pipeline中开始传播,一次调用pipeline中的handler的read()方法
    pipeline.read();
    return this;
}
复制代码

FML, saw the pipeline, a lesson, we can guess, the read () method will perform to spread along the pipeline. Then look at the pipeline read () method, the following source.

public final ChannelPipeline read() {
    tail.read();
    return this;
}
复制代码

It was found tail node calls Read () method, and then, is propagated along the forward tail node performs Read (), will eventually perform a read head node () method, since I have seen the source code, in addition to the known pipeline an outer head node, read other nodes () method did not do significant processing logic, the head node jump directly read () method. Its source code as follows.

public void read(ChannelHandlerContext ctx) {
    // 对于服务端的channel而言,unsafe的值为NioMessageUnsafe类型的实例
    // 对于客户端channel而言,unsafe的值为NioSocketChannelUnsafe类型的实例
    unsafe.beginRead();
}
复制代码

It can be seen, in the read head node () method will be called unsafe BeginRead attribute () method. For the channel server, the unsafe values NioMessageUnsafe instance of the type; the client channel, examples of unsafe values NioSocketChannelUnsafe type. Because here is the server channel, so calls NioMessageUnsafe of beginRead () method. The method defined in AbstractUnsafe class, its source code is as follows.

public final void beginRead() {
    assertEventLoop();

    if (!isActive()) {
        return;
    }

    try {
        // 真正开始读数据
        doBeginRead();
    } catch (final Exception e) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireExceptionCaught(e);
            }
        });
        close(voidPromise());
    }
}
复制代码

They can see in the code to do the method begins with: doBeginRead () , can be happy about finally, finally saw the core code, source code doBeginRead () method is as follows.

protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;
    /**
     * 在服务端channel注册到多路复用器上时,将selectionKey的interestOps属性设置为了0
     * selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
     */
    final int interestOps = selectionKey.interestOps();
    /**
     * readInterestOp属性的值,是在NioServerSocketChannel的构造器中,被设置为SelectionKey.OP_ACCEPT,即16
     * public NioServerSocketChannel(ServerSocketChannel channel) {
     *    super(null, channel, SelectionKey.OP_ACCEPT);
     *    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
     * }
     */
    if ((interestOps & readInterestOp) == 0) {
        // 对于服务端channel而言,interestOps | readInterestOp运算的结果为16,即SelectionKey.OP_ACCEPT
        // 所以最终selectionKey感兴趣的事件为OP_ACCEPT事件,至此,服务端channel终于可以开始接收客户端的链接了。
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}
复制代码

To obtain the first selectionKey interestOps value of the property, the attribute value is 0 . why? Because when the server registered on the multiplexer channel, passed-in value is 0 . As shown below.

register()

Then the obtained readInterestOp value for this property is 16 , i.e. SelectionKey.OP_ACCEPT . why? Because in the constructor NioServerSocketChannel, the value assigned to it is SelectionKey.OP_ACCEPT . As shown below.

Construction method

Finally, OR: interestOps | readInterestOp get results is 16 , that is OP_ACCEPT , and the result is set to SelectionKey , interested in this event is the server channel OP_ACCEPT event, so when a new client to connect, the server channel It can be perceived by selectionKey, which answers the second question at the beginning of the article.

Here, start the process Netty server source code analysis finally completed. Sigh, too TM complicated! ! ! Although we use Netty when the code is particularly simple, but its underlying logic is quite complex, really it is so how years of quiet good, but it was for you before loading the line .

to sum up

recommend

Micro-channel public number

Guess you like

Origin juejin.im/post/5df622386fb9a015ff64e714