This article goes on to cover the rest of the server class in the first two articles. Let's first look at the server code.
/** * Created by chenhao on 2019/9/4. */ 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 SimpleServerInitializer()) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8888).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
In the previous two blog posts, the main work of the following lines of code is analyzed from the source point of view.
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new SimpleServerHandler()) .childHandler(new SimpleServerInitializer()) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true);
This blog post will analyze the internal implementation of ChannelFuture f = b.bind(8888).sync() from the source code perspective. This completes the source code analysis of the Netty server-side startup process.
Source code analysis ChannelFuture f = b.bind(8888).sync()
AbstractBootstrap.java
public ChannelFuture bind(int inetPort) { return bind(new InetSocketAddress(inetPort)); }
Let's look at the overloaded bind.
public ChannelFuture bind(SocketAddress localAddress) { validate();//Examination of relevant parameters if (localAddress == null) { throw new NullPointerException("localAddress"); } return doBind(localAddress);//The following is an analysis }
This function looks at two main points: validate() and doBind(localAddress)
validate() method
//Function function: Check whether the relevant parameters are set @SuppressWarnings("unchecked") public B validate() { if (group == null) {//There group Refer to: b.group(bossGroup, workerGroup)In the code bossGroup throw new IllegalStateException("group not set"); } if (channelFactory == null) { throw new IllegalStateException("channel or channelFactory not set"); } return (B) this; }
This method mainly checks two parameters, one is group and the other is channelFactory. Here we can think about where and when these two parameters are assigned. The answer is assigned in the following code blocks, in which the bossGroup is assigned to the group and the Bootstrap ChannelFactory is assigned to the channelFactory.
ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)
DoBind (local Address) method
The source code of the doBind method is as follows:
private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister();//1 final Channel channel = regFuture.channel();//2 if (regFuture.cause() != null) { return regFuture; } final ChannelPromise promise; if (regFuture.isDone()) { promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); } else { // Registration future is almost always fulfilled already, but just in case it's not. promise = new PendingRegistrationPromise(channel); regFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { doBind0(regFuture, channel, localAddress, promise); } }); } return promise; }
The doBind function is the focus of our analysis. The main work of this function is as follows:
1. An instance of Channel Future, regFuture, is obtained by initAndRegister() method.
2. Use the regFuture.cause() method to determine whether an exception is generated when the initAndRegister method is executed. If an exception is generated, it is returned directly, and if no exception is generated, step 3 is performed.
3. Judge whether the initAndRegister method has been executed by regFuture.isDone(), return true if it has been executed, and then call doBind0 for socket binding. If not, return to false for step 4.
4. regFuture adds a ChannelFutureListener listener. When initAndRegister is completed, it calls the operation Complete method and executes doBind0 for socket binding.
The third and fourth thing you want to do is call the doBind0 method for socket binding.
Below will be divided into four parts for each line of code to do a detailed analysis of what work.
initAndRegister()
The specific code of this method is as follows:
final ChannelFuture initAndRegister() { //CONCLUSION: Here channel For one NioServerSocketChannel Objects for specific analysis see below final Channel channel = channelFactory().newChannel();//1 try { init(channel);//2 } catch (Throwable t) { channel.unsafe().closeForcibly(); // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); } ChannelFuture regFuture = group().register(channel);//3 if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } } return regFuture; }
From the name of the function and the function called internally, you can guess that the function did two things:
1. Initialize a Channel. If you want to initialize, you must get a Channel first.
final Channel channel = channelFactory().newChannel();//1 init(channel);//2
2. Register Channel.
ChannelFuture regFuture = group().register(channel);//3
Next we'll analyze what's going on inside these lines of code.
final Channel channel = channelFactory().newChannel();
In the last article( Netty Source Code Analysis (II) --- Server Bootstrap ) In the analysis, we know that the function of b.channel(NioServerSocketChannel.class) is to set the parent class property channelFactory as an object of BootstrapChannelFactory class. Here, the BootstrapChannelFactory object includes a clazz attribute: NioServerSocketChannel.class
Therefore, the final Channel channel = channelFactory().newChannel(); is the new Channel () method in the Bootstrap ChannelFactory class that is called. The specific content of this method is as follows:
private static final class BootstrapChannelFactory<T extends Channel> implements ChannelFactory<T> { private final Class<? extends T> clazz; BootstrapChannelFactory(Class<? extends T> clazz) { this.clazz = clazz; } @Override public T newChannel() { try { return clazz.newInstance(); } catch (Throwable t) { throw new ChannelException("Unable to create Channel from class " + clazz, t); } } @Override public String toString() { return StringUtil.simpleClassName(clazz) + ".class"; } }
Looking at this class, we can conclude that final Channel channel = channelFactory().newChannel(); this line of code acts as an instance of the NioServerSocketChannel class generated by reflection.
NioServer Socket Channel Constructor
Now let's look at what the constructor of the NioServerSocketChannel class does.
The inheritance architecture of the NioServerSocketChannel class is as follows:
Its parametric constructor is as follows:
public NioServerSocketChannel() { this(newSocket(DEFAULT_SELECTOR_PROVIDER)); }
SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider() in the parametric constructor.
The function of newSocket is to generate a SocketChannelImpl object using SelectorProvider.
private static ServerSocketChannel newSocket(SelectorProvider provider) { try { return provider.openServerSocketChannel(); } catch (IOException e) { throw new ChannelException( "Failed to open a server socket.", e); } } public SocketChannel openSocketChannel() throws IOException { return new SocketChannelImpl(this); }
The parametric constructor generates a SocketChannelImpl object through the newSocket function
Then we call the following constructor, and we'll continue to look at it.
public NioServerSocketChannel(ServerSocketChannel channel) { super(null, channel, SelectionKey.OP_ACCEPT); config = new NioServerSocketChannelConfig(this, javaChannel().socket()); } //Parent class AbstractNioMessageChannel Constructive function protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) { super(parent, ch, readInterestOp); } //Parent class AbstractNioChannel Constructive function protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { super(parent); this.ch = ch; this.readInterestOp = readInterestOp;//SelectionKey.OP_ACCEPT try { ch.configureBlocking(false);//Setting the current ServerSocketChannel Nonblocking } catch (IOException e) { try { ch.close(); } catch (IOException e2) { if (logger.isWarnEnabled()) { logger.warn( "Failed to close a partially initialized socket.", e2); } } throw new ChannelException("Failed to enter non-blocking mode.", e); } } //Parent class AbstractChannel Constructive function protected AbstractChannel(Channel parent) { this.parent = parent; unsafe = newUnsafe(); pipeline = new DefaultChannelPipeline(this); }
When new NioServerSocketChannel() generates an instance object, calling so many constructors above mainly does two things:
1. An instance of the SocketChannelImpl class is generated, set to the ch attribute, and set to non-blocking.
this.ch = ch;
ch.configureBlocking(false);
2. Setting the config attribute
config = new NioServerSocketChannelConfig(this, javaChannel().socket()
3. Setting SelectionKey.OP_ACCEPT events
this.readInterestOp = readInterestOp;//SelectionKey.OP_ACCEPT
4. Setting unsafe properties
@Override protected AbstractNioUnsafe newUnsafe() { return new NioMessageUnsafe(); }
The main function is to take charge of the underlying connect, register, read and write operations.
5. Setting pipeline properties
pipeline = new DefaultChannelPipeline(this);
Each Channel has its own pipeline. When a request event occurs, the pipeline is responsible for calling the corresponding hander for processing.
These attributes will be used later, and the implementation of unsafe and pipeline attributes in NioServerSocketChannel object will be analyzed later.
Conclusion: final Channel channel = channelFactory (). new Channel (); the function of this line of code is to generate an instance of the NioServerSocketChannel class through reflection, in which the NioServerSocketChannel class object has several attributes: SocketChannel, NioServerSocketChannelConfig, SelectionKey.OP_ACCEPT event, and Ni oServerSocketChannel class object. OMessageUnsafe, Default Channel Pipeline
init(channel)
The specific code of the init method is as follows:
@Override void init(Channel channel) throws Exception { //1,Setting up new access channel Of option final Map<ChannelOption<?>, Object> options = options(); synchronized (options) { channel.config().setOptions(options);//NioServerSocketChannelConfig } //2,Setting up new access channel Of attr final Map<AttributeKey<?>, Object> attrs = attrs(); synchronized (attrs) { for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); } } //3,Set up handler reach pipeline upper ChannelPipeline p = channel.pipeline(); if (handler() != null) {//There handler()It's the second part that's returned..handler(new SimpleServerHandler())Set SimpleServerHandler p.addLast(handler()); } final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry<ChannelOption<?>, Object>[] currentChildOptions; final Entry<AttributeKey<?>, Object>[] currentChildAttrs; synchronized (childOptions) { currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size())); } synchronized (childAttrs) { currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size())); } //p.addLast()towards serverChannel A pipeline processor is added ServerBootstrapAcceptor,As you can see from the name, this is an access device that accepts new requests and throws them to an event loop. p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new ServerBootstrapAcceptor( currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); }
The function of this function is:
1. Setting options for channel
If not set, options is empty, which is defined in the ServerBootstrap class as follows
Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
options may be as follows:
public <T> boolean setOption(ChannelOption<T> option, T value) { validate(option, value); if (option == CONNECT_TIMEOUT_MILLIS) { setConnectTimeoutMillis((Integer) value); } else if (option == MAX_MESSAGES_PER_READ) { setMaxMessagesPerRead((Integer) value); } else if (option == WRITE_SPIN_COUNT) { setWriteSpinCount((Integer) value); } else if (option == ALLOCATOR) { setAllocator((ByteBufAllocator) value); } else if (option == RCVBUF_ALLOCATOR) { setRecvByteBufAllocator((RecvByteBufAllocator) value); } else if (option == AUTO_READ) { setAutoRead((Boolean) value); } else if (option == AUTO_CLOSE) { setAutoClose((Boolean) value); } else if (option == WRITE_BUFFER_HIGH_WATER_MARK) { setWriteBufferHighWaterMark((Integer) value); } else if (option == WRITE_BUFFER_LOW_WATER_MARK) { setWriteBufferLowWaterMark((Integer) value); } else if (option == MESSAGE_SIZE_ESTIMATOR) { setMessageSizeEstimator((MessageSizeEstimator) value); } else { return false; } return true; }
2. Setting up attrs for channel
If not set, attrs is empty, which is defined in the ServerBootstrap class as follows
private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>();
3. Setting handler to channel pipeline
Here the handler is: in the blog( Netty Source Code Analysis (II) --- Server Bootstrap ) SimpleServerHandler object set by b.handler(new SimpleServerHandler()) in the analysis
4. Add a ChannelInitializer object to the pipeline, where the initChannel method is overridden. This method adds a ServerBootstrapAcceptor to the pipeline processor of server Channel through p.addLast().
As you can see from the name, this is an access device that accepts new requests and throws them to an event loop.
Looking at this, we find that init only initializes some basic configurations and attributes, and adds an accessor to the pipeline to accept new connections, but does not start the service.
group().register(channel)
Back in the initAndRegister method, continue looking at the line config().group().register(channel). The config method returns ServerBootstrapConfig, which calls the group method, which is actually the bossGroup. BossGroup calls the register method.
From the previous analysis, we know that the group is NioEvenLoop Group, which inherits MultithreadEventLoop Group. The register method in this class is as follows:
@Override public ChannelFuture register(Channel channel) { return next().register(channel);//Called NioEvenLoop In the object register Method,NioEventLoop extends SingleThreadEventLoop }
The next() method is coded as follows, and its function is to select the next NioEventLoop object.
@Override public EventExecutor next() { return chooser.next();//call MultithreadEventExecutorGroup Medium next Method }
chooser is selected according to whether the number of threads nThreads is a power of 2. The two choosers are PowerOf TwoEvent Executor chooser and GenericEvent Executor chooser. These two choosers have the same function, but the way of redundancy is different.
The next() method returns a NioEvenLoop object
private final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser { @Override public EventExecutor next() { return children[childIndex.getAndIncrement() & children.length - 1];//Utilizing 2 N The characteristics of this method and its application&It's quicker to find surplus. } } private final class GenericEventExecutorChooser implements EventExecutorChooser { @Override public EventExecutor next() { return children[Math.abs(childIndex.getAndIncrement() % children.length)]; } }
Conclusion: Because NioEventLoop Group maintains multiple NioEventLoops, the next method calls back the chooser policy to find the next NioEventLoop and executes the register method of the object for registration.
Because NioEventLoop extends SingleThreadEventLoop, NioEventLoop does not override this method, so look at the register method in the SingleThreadEventLoop class.
@Override public ChannelFuture register(Channel channel) { return register(channel, new DefaultChannelPromise(channel, this)); } @Override public ChannelFuture register(final Channel channel, final ChannelPromise promise) { if (channel == null) { throw new NullPointerException("channel"); } if (promise == null) { throw new NullPointerException("promise"); } channel.unsafe().register(this, promise); return promise; }
The unsafe attribute is set in the NioServerSocketChannel instantiation in Part 1 of this blog, specifically by calling the following method, so channel.unsafe() here is the NioMessageUnsafe instance.
@Override protected AbstractNioUnsafe newUnsafe() { return new NioMessageUnsafe(); }
The code channel.unsafe().register(this, promise) calls the register method in the AbstractUnsafe class. The code is as follows:
@Override public final void register(EventLoop eventLoop, final ChannelPromise promise) { if (eventLoop == null) { throw new NullPointerException("eventLoop"); } //Judging this channel Has it been registered? EventLoop in if (isRegistered()) { promise.setFailure(new IllegalStateException("registered to an event loop already")); return; } if (!isCompatible(eventLoop)) { promise.setFailure( new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); return; } //1 take eventLoop Setting NioServerSocketChannel upper AbstractChannel.this.eventLoop = eventLoop; //Determine whether the current thread is this EventLoop Threads owned in the thread are registered directly if they are, and if not, a task is added to the thread. if (eventLoop.inEventLoop()) { register0(promise); } else { try { eventLoop.execute(new OneTimeTask() { //A key @Override public void run() { register0(promise);//Analysis } }); } catch (Throwable t) { logger.warn( "Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, t); closeForcibly(); closeFuture.setClosed(); safeSetFailure(promise, t); } } }
The focus above is the register0(promise) method. The basic logic is:
1. By calling EvetLoop. inEventLoop () method to determine whether the current thread is a thread owned by the EventLoop, if so, register directly, and if not, indicate that the EventLoop is waiting without execution rights, then proceed to the second step.
AbstractEventExecutor.java
@Override public boolean inEventLoop() { return inEventLoop(Thread.currentThread()); }
SingleThreadEventExecutor.java
@Override public boolean inEventLoop(Thread thread) { return thread == this.thread; }
2. Since the thread in the EventLoop does not have the right to execute at this time, we can submit a task to the thread. When the thread in the EventLoop has the right to execute, it will naturally execute the task. The task is responsible for calling the register0 method, which achieves the purpose of calling the register0 method.
Let's look at the register0 method. The specific code is as follows:
private void register0(ChannelPromise promise) { try { // check if the channel is still open as it could be closed in the mean time when the register // call was outside of the eventLoop if (!promise.setUncancellable() || !ensureOpen(promise)) { return; } doRegister(); registered = true; safeSetSuccess(promise); //After execution, console output: channelRegistered pipeline.fireChannelRegistered(); if (isActive()) { //Analysis pipeline.fireChannelActive(); } } catch (Throwable t) { // Close the channel directly to avoid FD leak. closeForcibly(); closeFuture.setClosed(); safeSetFailure(promise, t); } }
In the above code, the registration of NioServerSocketChannel is completed by calling the doRegister() method. The specific code of this method is as follows:
@Override protected void doRegister() throws Exception { boolean selected = false; for (;;) { try { selectionKey = javaChannel().register(eventLoop().selector, 0, this); return; } catch (CancelledKeyException e) { if (!selected) { // Force the Selector to select now as the "canceled" SelectionKey may still be // cached and not removed because no Select.select(..) operation was called yet. eventLoop().selectNow(); selected = true; } else { // We forced a select operation on the selector before but the SelectionKey is still cached // for whatever reason. JDK bug ? throw e; } } } } protected SelectableChannel javaChannel() { return ch; }
In the instantiation analysis of NioServerSocketChannel in Part 1 of this blog, we know that the ch returned by the Java Channel () method here is an instance of the SocketChannelImpl class generated when instantiating NioServerSocketChannel, and is set to be non-blocking, as shown in Part 1 of this blog.
selectionKey = javaChannel().register(eventLoop().selector, 0, this); Server Socket Channel is registered with Selector.
In retrospect, what is eventLoop().selector here? The answer is: the KQueueSelectorImpl object.
NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) { super(parent, threadFactory, false); if (selectorProvider == null) { throw new NullPointerException("selectorProvider"); } provider = selectorProvider; selector = openSelector(); } private Selector openSelector() { final Selector selector; try { selector = provider.openSelector(); } catch (IOException e) { throw new ChannelException("failed to open a new selector", e); } //...A part of the code has been omitted return selector; }
After the ServerSocketChannel is registered, the pipeline.fireChannelRegistered method is executed.
public final ChannelPipeline fireChannelRegistered() { AbstractChannelHandlerContext.invokeChannelRegistered(this.head); return this; }
We see that the parameter passed by invokeChannel Registered (this. head) is head, which we will talk about in the next article and continue to look at.
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) { EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRegistered(); } else { executor.execute(new Runnable() { public void run() { next.invokeChannelRegistered(); } }); } }
Look at next. invokeChannel Registered ();
private void invokeChannelRegistered() { if (this.invokeHandler()) { try { ((ChannelInboundHandler)this.handler()).channelRegistered(this); } catch (Throwable var2) { this.notifyHandlerException(var2); } } else { this.fireChannelRegistered(); } }
Then look at this.handler(), which is actually the handler() of the head.
public ChannelHandler handler() { return this; }
This is returned, and then look at channel Registered (this) in the head.
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { DefaultChannelPipeline.this.invokeHandlerAddedIfNeeded(); ctx.fireChannelRegistered(); }
Keep looking at ctx. fireChannel Registered ();
public ChannelHandlerContext fireChannelRegistered() { invokeChannelRegistered(this.findContextInbound()); return this; }
Let's look at this.findContextInbound()
private AbstractChannelHandlerContext findContextInbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while(!ctx.inbound); return ctx; }
We see ctx = ctx.next; in fact, we start looking at the head and find the hander of the first inbound.
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) { EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRegistered(); } else { executor.execute(new Runnable() { public void run() { next.invokeChannelRegistered(); } }); } }
Finally, next.invokeChannelRegistered();
The handler list is maintained in pipeline. Remember that the handler initialized by. handler (new SimpleServer Handler ()) was added to the pipeline in the analysis of Part 1.2 of this blog post. By traversing the linked list, the channel Registered method of InBound handler is executed.
So when it's done here, our console returns the output: Channel Registered, this line of information.
At this point, we will analyze the doBind method final ChannelFuture regFuture = initAndRegister(); the conclusions are as follows:
1. A NioServerSocketChannle object is generated by reflection.
2. The initialization has been completed.
3. Register NioServer Socket Channel.
Next, we analyze what the rest of the doBind method does.
The source code is as follows:
private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister();//1 final Channel channel = regFuture.channel();//2 if (regFuture.cause() != null) { return regFuture; } final ChannelPromise promise; if (regFuture.isDone()) { promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); } else { // Registration future is almost always fulfilled already, but just in case it's not. promise = new PendingRegistrationPromise(channel); regFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { doBind0(regFuture, channel, localAddress, promise); } }); } return promise; }
doBind0(regFuture, channel, localAddress, promise);
private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. channel.eventLoop().execute(new Runnable() { @Override public void run() { if (regFuture.isSuccess()) { channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { promise.setFailure(regFuture.cause()); } } }); }
This function mainly submits a Runnable task to the NioEventLoop thread for processing. Let's first look at the execute method of the NioEventLoop class
@Override public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } boolean inEventLoop = inEventLoop();//Determine whether the current thread is this NioEventLoop The associated thread, if it is, adds tasks to the task queue. If not, starts the thread first, and then adds tasks to the task queue. if (inEventLoop) { addTask(task); } else { startThread(); addTask(task); //If if (isShutdown() && removeTask(task)) { reject(); } } if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }
When a submitted task is executed by a thread, the code channel. bind (local Address, promise). addListener (Channel FutureListener. CLOSE_ON_FAILURE) is executed. The function of this code is to bind channel to port.
Specifically as follows:
AbstractChannel.java @Override public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { return pipeline.bind(localAddress, promise); }
In this method, the bind method of pipeline is directly called, where pipeline is an instance of Default Channel Pipeline.
DefaultChannelPipeline.java @Override public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { return tail.bind(localAddress, promise); }
In the above method, the bind method of TailContext instance tail is directly invoked, and tail will be introduced in detail in the next blog post. Continue to look at the bind method for tail instances
AbstractChannelHandlerContext.java @Override public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) { //...Elimination of validity check final AbstractChannelHandlerContext next = findContextOutbound();// EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeBind(localAddress, promise); } else { safeExecute(executor, new OneTimeTask() { @Override public void run() { next.invokeBind(localAddress, promise); } }, promise, null); } return promise; }
This line of code in the bind function above: final AbstractChannelHandlerContext next = findContextOutbound(); the task is to find the first outbound=true handler node from tail in the bidirectional list held by pipeline with AbstractChannelHandlerContext as the node.
private AbstractChannelHandlerContext findContextOutbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while (!ctx.outbound); return ctx; }
In the Default Channel Pipeline constructor, two objects are instantiated: head and tail, forming the head and tail of a bidirectional list. Head is an example of HeadContext. It implements the Channel OutboundHandler interface and Channel InboundHandler interface, and its outbound field is true. Tail is an example of TailContext. It implements the Channel InboundHandler interface, and its outbound field is false, and its inbound field is true. Based on this, the AbstractChannelHandlerContext object found by calling the findContextOutbound method in the bind function above is actually the head.
Continue to look, after finding the first Abstract ChannelHandlerContext node head with outbound=true in pipelie's two-way list, then call the invokeConnect method of this node. The code of this method is as follows:
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } }
The handler() method code in the HeadContext class is as follows:
@Override public ChannelHandler handler() { return this; }
This method returns itself, because HeadContext has both Context and Handler features because it inherits AbstractChannelHandlerContext and implements the ChannelHandler interface.
Continue to look at the bind method in the HeadContext class. The code is as follows:
@Override public void bind( ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { unsafe.bind(localAddress, promise); }
The unsafe field is initialized in the HeadContext constructor as follows:
HeadContext(DefaultChannelPipeline pipeline) { super(pipeline, null, HEAD_NAME, false, true); unsafe = pipeline.channel().unsafe(); }
The code pipeline.channel().unsafe() in this constructor returns an example initialized in the constructor of the NioServerSocketChannel class studied earlier in this blog post, as follows:
unsafe = newUnsafe();//newUnsafe()Method returns NioMessageUnsafe Object.
Next, look at the bind method in the NioMessageUnsafe class (in AbstractUnsafe, to be exact), which has the following specific method code:
@Override public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { //...Some code is omitted boolean wasActive = isActive(); try { doBind(localAddress);//Core code } catch (Throwable t) { safeSetFailure(promise, t); closeIfClosed(); return; } if (!wasActive && isActive()) { invokeLater(new OneTimeTask() { @Override public void run() { pipeline.fireChannelActive(); } }); } safeSetSuccess(promise); }
The core code above is doBind(localAddress); it should be noted that this doBind method is a doBind method in the NioServerSocketChannel class, not in other classes.
The doBind method code in the NioServerSocketChannel class is as follows:
@Override protected void doBind(SocketAddress localAddress) throws Exception { javaChannel().socket().bind(localAddress, config.getBacklog()); }
The javaChannel() method in the above method returns the Java NIO Server Socket Channel instance (more specifically, the Server Socket Channel Imple instance) generated when the NioServerSocket Channel instance is initialized. The equivalent statement serverSocketChannel.socket().bind(localAddress) completes the binding of the specified port, thus starting to listen on the port. When the binding port is successful, we call our custom handler's channelActive method here. Before binding, the isActive() method returns false, and after binding, it returns true.
@Override public boolean isActive() { return javaChannel().socket().isBound(); }
In this way, you enter the code block of the if condition as follows
if (!wasActive && isActive()) { invokeLater(new OneTimeTask() { @Override public void run() { pipeline.fireChannelActive(); } }); } private void invokeLater(Runnable task) { try { //Some code is omitted eventLoop().execute(task); } catch (RejectedExecutionException e) { logger.warn("Can't invoke task later as EventLoop rejected it", e); } }
Then start executing pipeline.fireChannelActive(); this line of code, the specific call chain of this line of code is as follows:
DefaultChannelPipeline.java
@Override public ChannelPipeline fireChannelActive() { head.fireChannelActive(); if (channel.config().isAutoRead()) { channel.read(); } return this; } @Override public ChannelHandlerContext fireChannelActive() { final AbstractChannelHandlerContext next = findContextInbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelActive(); } else { executor.execute(new OneTimeTask() { @Override public void run() { next.invokeChannelActive(); } }); } return this; } private void invokeChannelActive() { try { ((ChannelInboundHandler) handler()).channelActive(this); } catch (Throwable t) { notifyHandlerException(t); } }
summary
Finally, let's conclude that netty starts the process through which a service goes.
1. Setting the parameters of the startup class, the most important thing is to set the channel.
2. Create the channel corresponding to server, and create major components, including ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe, etc.
3.init initializes the NioServer Socket Channel, sets some attrs, options, and sets attrs, options of sub-channels, adds a new channel accessor to the server channel, and triggers the addHandler event.
4.config().group().register(channel) gets NioEventLoop by using the bossGroup of Server Bootstrap to model the group length, registers NioServer Socket Channel on the selector of NioEventLoop, and then triggers the channelRegistered event.
5. Call to the bottom of jdk for port binding and trigger active events