Introduction
Although netty is very powerful, it is very simple to use netty to build programs. You only need to master specific netty routines to write powerful netty programs. Every netty program needs a Bootstrap, what is a Bootstrap? Bootstrap translated into Chinese means shoehorn. In the computer world, Bootstrap refers to the bootstrap program, and it is easy to build and start the program through Bootstrap.
There are two kinds of Bootstrap in netty: Bootstrap on the client side and ServerBootstrap on the server side. What is the difference between the two? How do these two kinds of Bootstrap work in netty? Let's take a look together.
The connection between Bootstrap and ServerBootstrap
First look at the inheritance relationship between the Bootstrap and ServerBootstrap classes, as shown in the following figure:
<img src="https://img-blog.csdnimg.cn/4eda7812e7eb4825aa471bbc679e7cad.png" style="zoom:67%;" />
It can be seen that both Bootstrap and ServerBootstrap inherit from AbstractBootstrap, while AbstractBootstrap implements the Cloneable interface.
AbstractBootstrap
Careful students may ask, there is also a Channel in the above picture. What is the relationship between channel and AbstractBootstrap?
Let's look at the definition of AbstractBootstrap:
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable
AbstractBootstrap accepts two generic parameters, one is B inherited from AbstractBootstrap, the other is C inherited from Channel.
Let's first observe what elements are required for a simple Bootstrap startup:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new FirstServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口并开始接收连接
ChannelFuture f = b.bind(port).sync();
// 等待server socket关闭
f.channel().closeFuture().sync();
The above code is the most basic and standard netty server-side startup code. You can see that there are several elements related to Bootstrap:
- EventLoopGroup is mainly used for channel registration and traversal.
- channel or ChannelFactory, used to specify the type of channel used in Bootstrap.
- ChannelHandler, used to specify the processing logic of messages in a specific channel.
- ChannelOptions, indicating the attribute information corresponding to the channel used.
- SocketAddress, bootstrap startup is the bound ip and port information.
At present, it seems that Bootstrap is related to these five values, and the constructor of AbstractBootstrap also defines the assignment of these properties:
AbstractBootstrap(AbstractBootstrap<B, C> bootstrap) {
group = bootstrap.group;
channelFactory = bootstrap.channelFactory;
handler = bootstrap.handler;
localAddress = bootstrap.localAddress;
synchronized (bootstrap.options) {
options.putAll(bootstrap.options);
}
attrs.putAll(bootstrap.attrs);
}
The group, channel, option and other methods in the sample code actually assign values to these attributes, and do not do too many business operations.
Note that there is only one group attribute in AbstractBootstrap, so the two group attributes are extended attributes added in ServerBootstrap.
In Bootstrap, the channel actually has two assignment methods, one is to directly pass in the channel, and the other is to pass in the ChannelFactory. The essence of both is the same, let's see how channel is converted into ChannelFactory:
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
channelClass is encapsulated in a ReflectiveChannelFactory, which is ultimately the set channelFactory property.
The real way to start the service in AbstractBootstrap is bind. The bind method passes in a SocketAddress and returns a ChannelFuture. Obviously, a channel will be created in the bind method. Let's take a look at the specific implementation of the bind method:
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
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();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
In the doBind method, first call the initAndRegister method to initialize and register a channel.
Channels are created through the newChannel method of channelFactory:
channel = channelFactory.newChannel();
Then call the init method that initializes the channel. This init method is not implemented in AbstractBootstrap and needs to be implemented in a specific implementation class.
After you have the channel, register the channel in the EventLoop by calling the register method of the EventLoopGroup, and return the ChannelFuture generated by the registration.
Then, by judging the status of the returned regFuture, it is judged whether the channel is successfully registered. If the registration is successful, the doBind0 method is finally called to complete the final binding work:
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());
}
}
});
}
Because eventLoop itself is an Executor, it can execute a specific command. In its execute method, a new Runnable object is passed in, and in the run method, the channel.bind method is executed, and the channel is connected with the SocketAddress. bind.
At this point, Bootstrap's bind method is executed.
Let's review the basic process of the bind method:
- Create a channel through ChannelFactory.
- Register the channel with the EventLoopGroup in Bootstrap.
- If the channel registration is successful, call the execute method of EventLoopGroup to bind the channel and SocketAddress.
Is it clear?
After talking about AbstractBootstrap, let's continue to discuss Bootstrap and ServerBootstrap.
Bootstrap and ServerBootstrap
First look at Bootstrap, Bootstrap is mainly used in the client, or in the UDP protocol.
Let's first look at the definition of Bootstrap:
public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel>
Compared with AbstractBootstrap, Bootstrap mainly has one more property and one method.
One more property is resolver:
private static final AddressResolverGroup<?> DEFAULT_RESOLVER = DefaultAddressResolverGroup.INSTANCE;
private volatile AddressResolverGroup<SocketAddress> resolver =
(AddressResolverGroup<SocketAddress>) DEFAULT_RESOLVER;
There is an IdentityHashMap in AddressResolverGroup, whose key is EventExecutor and value is AddressResolver:
private final Map<EventExecutor, AddressResolver<T>> resolvers =
new IdentityHashMap<EventExecutor, AddressResolver<T>>();
In fact, AddressResolverGroup maintains a mapping relationship between EventExecutor and AddressResolver.
AddressResolver is mainly used to resolve the address of the remote SocketAddress. Because the remote SocketAddress may not be an IP address, it needs to be resolved using the AddressResolver.
The EventExecutor here is actually the EventLoop registered by the channel.
In addition, as a client-side application, Bootstrap needs to connect to the server-side, so there is an additional method in the Bootstrap class to connect to the remote SocketAddress:
public ChannelFuture connect(SocketAddress remoteAddress) {
ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
validate();
return doResolveAndConnect(remoteAddress, config.localAddress());
}
The logic of the connect method is similar to that of the bind method, except that there is an additional resolve process of the resolver.
After parsing, the doConnect method will be called to make the real connection:
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
It can be seen that the doConnect method is very similar to the doBind method. Both execute the connect or bind method of the channel through the eventLoop registered by the current channel.
Take another look at the definition of ServerBootstrap:
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
Because ServerBootstrap is used on the server side, we do not choose Bootstrap to parse SocketAddress, so there is no resolver attribute.
But for the server side, you can use the parent EventLoopGroup to accept connections, and then use the child EventLoopGroup to execute specific commands. So there is one more childGroup and corresponding childHandler in ServerBootstrap:
private volatile EventLoopGroup childGroup;
private volatile ChannelHandler childHandler;
Because ServerBootstrap has two groups, ServerBootstrap includes a group method with two EventLoopGroups:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
Remember the init method that the bind method needs to implement? Let's look at the specific logic of init in ServerBootstrap:
void init(Channel channel) {
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, newAttributesArray());
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
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() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
The first is to set some properties of the channel, then obtain the pipeline corresponding to the channel through the channel.pipeline method, and then add channelHandler to the pipeline.
These are all routine operations. What we should pay attention to is that the eventLoop registered through the channel finally adds the ServerBootstrapAcceptor to the pipeline.
Obviously ServerBootstrapAcceptor itself should be a ChannelHandler, and its main function is to accept connections:
private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter
Let's take a look at its channelRead method:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
setAttributes(child, childAttrs);
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
Because the server side accepts the connect operation of the client channel, the object in the corresponding channelRead is actually a channel. Here the received channel is called child. By adding childHandler, childOptions and childAttrs to this child channel, a logic that can handle child channel requests is formed.
Finally, the child channel is registered in the childGroup, and the entire ServerBootstrapAcceptor's task of accepting the channel is completed.
The best part here is to pass the channel of the client to the server through the channel of the server, and then equip the child channel with a handler on the server for specific business processing, which is very ingenious.
Summarize
Through a detailed analysis of the structure and implementation logic of AbstractBootstrap, Bootstrap and ServerBootstrap, I believe that everyone has a general understanding of the startup process of the netty service. We will explain the channel in netty and the very important eventLoop in detail later.
This article has been included in http://www.flydean.com/03-1-netty-boots…-serverbootstrap/
The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!
Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。