Let’s use a typical netty server code to illustrate:
EventLoopGroup parentGroup = new NioEventLoopGroup(1);
EventLoopGroup childGroup = new NioEventLoopGroup(3);
ServerBootstrap b = new ServerBootstrap();
b.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.option(ChannelOption.SO_BACKLOG, 128)
.attr(AttributeKey.valueOf("ssc.key"),"scc.value")
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.childOption(ChannelOption.SO_KEEPALIVE, true);
.childAttr(AttributeKey.valueOf("sc.key"),"sc.value")
.bind(port);
Copy the code
The reactor thread model is as follows
It basically consists of the following five steps:
- Setting the Server
ServerBootStrap
Launch parameters - through
ServerBootStrap
The bind method starts the serverparentGroup
Registered inNioServerScoketChannel
To listen for connection requests from clients - The Client initiates a CONNECT request,
parentGroup
In theNioEventLoop
The ACCEPT event is emitted if there is a new client request - After the ACCEPT event is triggered,
parentGroup
In theNioEventLoop
throughNioServerSocketChannel
The corresponding representative client is obtainedNioSocketChannel
And register it tochildGroup
In the childGroup
In theNioEventLoop
Constantly test your own managementNioSocketChannel
Are read and write events ready, and if so, call the correspondingChannelHandler
For processing
Here’s a breakdown of the steps:
1. Set startup parameters of the server ServerBootStrap
In general, ServerBootStrap is derived from AbstractBootstrap. It represents the startup class of the server, and when its bind method is called, it means that the server is started. Before starting, we call the methods group, Channel, Handler, option, attr, childHandler, childOption, childAttr, etc to set some startup parameters.
Group method:
In Netty, an EventLoopGroup functions like a thread pool. Each EventLoopGroup contains multiple EventLoop objects representing different threads.
As shown in the example above, we pass in construction parameters 1 and 3 when creating parentGroup and childGroup respectively. This corresponds to the parentGroup in red in the figure above, there is only one NioEventLoop. There are three NioEventloops in the green childGroup. If no parameter is specified or 0 is passed, the number of NioEventLoopGroup will be: number of CPU cores *2.
After that, we can call the ServerBootStrap group() method to get a reference to parentGroup, which AbstractBootstrap inherits. A reference to childGroup can also be obtained by calling ServerBootStrap’s own childGroup() method.
The relevant codes are as follows:
io.netty.bootstrap.ServerBootstrap
public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap.ServerChannel> {...private volatile EventLoopGroup childGroup;ServerBootStrap Maintain childGroup.public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);// Pass parentGroup to the parent class AbstractBootstrap processing
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup ! =null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup;// Assign a value to childGroup
return this; }...public EventLoopGroup childGroup(a) {/ / get childGroup
returnchildGroup; }}Copy the code
io.netty.bootstrap.AbstractBootstrap
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B.C>, C extends Channel> implements Cloneable {...private volatile EventLoopGroup group;// This field will be set to parentGroup.public B group(EventLoopGroup group) {
if (group == null) {
throw new NullPointerException("group");
}
if (this.group ! =null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return (B) this; }...public final EventLoopGroup group(a) {/ / get parentGroup
returngroup; }}Copy the code
Methods the channel:
The channel method, derived from AbstractBootstrap, is used to construct an instance of the channel’s factory class ChannelFactory. Later, when you need to create a channel instance, such as NioServerSocketChannel, By calling the ChannelFactory. NewChannel () method to create.
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B.C>, C extends Channel> implements Cloneable {...// the final channel instance created by the factory class is the NioServerSocketChannel specified by the channel method
private volatileChannelFactory<? extends C> channelFactory; .public B channel(Class<? extends C> channelClass) {...return channelFactory(new BootstrapChannelFactory<C>(channelClass));
}
public B channelFactory(ChannelFactory<? extends C> channelFactory) {...this.channelFactory = channelFactory;
return (B) this; }...final ChannelFactory<? extends C> channelFactory() {
returnchannelFactory; }}Copy the code
It can be found that except for the above two methods, the other three methods are one-to-one corresponding:
handler-->childHandler
: Used for settingNioServerSocketChannel
andNioSocketChannel
That is, when there is a NIO event, what steps should be taken to process it.option-->childOption
: Used for settingNioServerSocketChannel
andNioSocketChannel
TCP connection parameters, inChannelOption
Class you can see all the TCP connection parameters supported by Netty.attr-->childAttr
: used tochannel
Set a key/value that can later be obtained by key
Among them:
The handler, option, and attr methods are all inherited from AbstractBootstrap. The parameters set by these methods will be applied to the NioServerSocketChannel instance. Since NioServerSocketChannel is typically created only one, these parameters will usually only be applied once.
The childHandler, childOption, and childAttr methods are defined by ServerBootStrap. The parameters set by these methods will be applied to the NioSocketChannel instance. A NioSocketChannel instance is created, so each NioSocketChannel instance applies the parameters set by these methods.
2.Call the bind method of ServerBootStrap
Calling bind is equivalent to starting the server. The core logic for starting is all in the BIND method.
Inside the bind method, an instance of NioServerSocketChannel is created and registered in parentGroup. Note that this process is shielded from users.
When a parentGroup receives a registration request, it selects one of its managed NioEventloops to register. In our case, parentGroup has only one NioEventLoop, so it can only be registered to this one.
Once registration is complete, we can detect the arrival of new client connections through NioServerSocketChannel.
If you trace the call chain of the ServerBootstrap. bind method step by step, you will eventually locate the doBind method of the ServerBootStrap parent class AbstractBootstrap.
io.netty.bootstrap.AbstractBootstrap#doBind
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();// Initialize NioServerSocketChannel and register with bossGroup./ / to omit
return promise;
Copy the code
The most important method to call in the doBind method is initAndRegister, which does three things
1. Create an instance of NioServerSocketChannel using the newChannel method of the ChannelFactory instance you created earlier
2. Initialize NioServerSocketChannel, that is, apply the parameters previously set by handler, option, attr, etc to NioServerSocketChannel
3. Register NioServerSocketChannel with parentGroup. ParentGroup selects one of the NioEventLoops to perform the function that NioServerSocketChannel is intended to perform, that is, listen for client connections.
final ChannelFuture initAndRegister(a) {
Channel channel = null;
try {
channel = channelFactory.newChannel();// create NioServerSocketChannel instance
init(channel);//2. Initialize NioServerSocketChannel. This is an abstract method that ServerBootStrap overwrites
} catch (Throwable t) {
if(channel ! =null){
channel.unsafe().closeForcibly();
// We need to force GlobalEventExecutor because we have not yet registered channels
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
ChannelFuture regFuture = group().register(channel);//3. NioServerSocketChannel registered with parentGroup
if(regFuture.cause() ! =null) {
if (channel.isRegistered()) {
channel.close();
} else{ channel.unsafe().closeForcibly(); }}return regFuture;
}
Copy the code
ServerBootStrap Implements AbstractBootstrap abstract method init, which initializes NioServerSocketChannel. Those familiar with design patterns will recognize that this is a typical template design pattern, in which a parent class calls multiple methods while a subclass overrides a particular method.
In this case, the init method mainly sets the run parameters for NioServerSocketChannel, which we specified earlier by calling ServerBootStrap’s option, attr, handler, and so on.
@Override
void init(Channel channel) {
//1. Set the parameters set by the option method for NioServerSocketChannel
setChannelOptions(channel, options0().entrySet().toArray(newOptionArray(0)), logger);
//2. Set attr parameters for NioServerSocketChannel
setAttributes(channel, attrs0().entrySet().toArray(newAttrArray(0)));
// set the handler specified by the handler method for NioServerSocketChannel
ChannelPipeline p = channel.pipeline();
// Set ServerBootstrapAcceptor as the default handler for NioSocketChannel and pass the parameters to ServerBootstrapAcceptor via the constructor
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
finalEntry<ChannelOption<? >, Object>[] currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
finalEntry<AttributeKey<? >, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
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( ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); }}); }}); }Copy the code
In particular, at the end of the method, in addition to the ChannelHandler we specified for NioServerSocketChannel through the handler method, The ServerBootStrap init method always adds a default handler ServerBootstrapAcceptor to the NioServerSocketChannel handler chain.
As indicated by the name ServerBootstrapAcceptor, it is the handler for client connection requests. When a client request is received, Netty creates a NioSocketChannel object to represent the client. The parameters specified by ServerBoodStrap, such as channelHandler, childOption, childAtrr, and childGroup, also need to be set to NioSocketChannel. But obviously now, since the server has just started, has not received any client requests, and has not yet received any NioSocketChannel instances, these parameters should be saved to ServerBootstrapAcceptor, and then set when the client connection is received. We can see that these parameters are passed to ServerBootstrapAcceptor through the constructor.
After initialization, ServerBootStrap registers NioServerSocketChannel with parentGroup by calling the Register method.
ChannelFuture regFuture = config().group().register(channel);
Copy the code
Further tracking, parentGroup type is NioEventLoopGroup, NioEventLoopGroup register method inherited from MultithreadEventLoopGroup.
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {...@Override
public ChannelFuture register(Channel channel) {
returnnext().register(channel); }...@Override
public EventExecutor next(a) {
returnchooser.next(); }... }Copy the code
The return value of the next method is NioEventLoop, and you can see that the actual registration is done by NioEventLoop. The next() method also provides a mechanism for evenly allocating channels in the NioEventLoop.
NioEventLoopGroup created, its parent class instances will create a EventExecutorChooser MultithreadEventExecutorGroup, through its after to ensure NioEventLoop average registered to a different channel.
rotected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args){... chooser = chooserFactory.newChooser(children); . }Copy the code
DefaultEventExecutorChooserFactory chooserFactory USES the default implementation class
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory{
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
// Use a round-robin approach to ensure averages
if (isPowerOfTwo(executors.length)) {// If the specified number of threads is a power of 2
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return newGenericEventExecutorChooser(executors); }}private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next(a) {
return executors[idx.getAndIncrement() & executors.length - 1]; }}private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next(a) {
returnexecutors[Math.abs(idx.getAndIncrement() % executors.length)]; }}}Copy the code
As can be seen, for the case where the number of threads is a power of 2, the bit operation & is used to further optimize the modulus taking speed, which is consistent with the familiar HasnMap optimization method.