前言
Bootstrap是netty程序的起点,当我们使用netty的时候会涉及到两个实现类ServerBootstrap和Bootstrap,它连接了之前所讲到的Channel,EventLoopGroup等,在对这些组件熟悉了之后,对于bootstarp的源码看起来就非常容易了。
正文
AbstractBootstrap
AbstractBootstrap() {
}
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);
}
AbstractBootstrap构造方法,平时我们是使用直接new的形式,它同时也支持克隆一个bootstrap的属性
public B group(EventLoopGroup group) {
ObjectUtil.checkNotNull(group, "group");
if (this.group != null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return self();
}
group方法,这里的group是AbstractBootstrap的主线程
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
这里传入的是一个class类,比如NioSocketChannel.class,通过ReflectiveChannelFactory去反射一个实例
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
bind方法,返回一个ChannelFuture,一般我们的使用方式是bind(port).sync()
bind方法调用doBind,第一步需要先注册:
我们传入的Channel就是在这里进行实例化,实例化以后调用init方法进行channel的初始化,init是一个抽象方法,由子类实现。初始化以后调用EventLoopGroup的register,这里register方法实际上是选择一个EventLoop调用register方法,而根据之前的介绍,EventLoop的register实际上是使用的unsafe
第二部调用doBind0方法:
调用channel的bind方法,上一篇文章讲过,channel将bind委托给pipeline,pipeline会触发channelRegistered事件并且传播,最终还是由unsafe使用java底层的bind方法
Bootstrap
首先看一下init方法,Bootstrap对Init做了什么
void init(Channel channel) {
ChannelPipeline p = channel.pipeline();
p.addLast(config.handler());
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
}
这里将我们克隆的另外一个bootstap的handler给添加到pipeline中
Bootstrap作为一个客户端使用,主要是用于和服务端进行连接然后传输消息,传送消息是使用channel的writeAndFlush方法,所以Bootstrap只需关注connect;这里同样调用了initAndRegister方法,在注册成功以后会调用doResolveAndConnect0
doResolveAndConnect0主要是通过resolver来解析远程地址,主要方法调用doConnect
同bind一样,最终还是交给了unsafe.connect进行处理
Bootstrap主要方法就是connect连接一个服务器地址进行交互,实现起来简单没有复杂的内容。
ServerBootstrap
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
return this;
}
ServerBootstrap拓展了一个childGroup来作为工作线程
ServerBootstrap拓展了childGroup,也有一个childHandler与之对应,这里将child属性使用ServerBootstrapAcceptor封装加入了pipeline中
ServerBootstrapAcceptor是一个Inbound,并且重写了channelRead事件。Server会为每一个到来的连接分配一个Channel,并且将我们定义的handler全部添加进去,然后将它注册到工作线程中,这样当channel调用读取消息并且处理的时候,能够达到我们想要的效果。
这一点可以通过阅读AbstractNioMessageChannel和NioServerSocketChannel来得到验证,这里可以自行去看,放一小段代码
触发channelRead方法
调用accept方法,创建了一个NioSocketChannel
根据之前的解析,整个流程就串联起来了
- NioEvnetLoop不断选择解析SelectionKey
- 调用processSelectedKey,当有accept事件的时候调用unsafe.read()
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
- 服务端使用NioMessageUnsafe,read方法核心调用了doReadMessages,正如上面介绍的一样,调用accept方法
ServerBootstrap作为服务端使用,用于接受客户端的连接,当每一个客户端连接进来的时候Server会创建一个NioSocketChannel
思考
通过ServerBootstrap可以知道线程池分为boss和work,而我们自定义逻辑处理是在work进行处理。通过前面的了解,EventLoopGroup是无锁化串行处理的,如果一个任务非常耗时,会阻塞后续的任务处理,所以对于耗时操作需要自定义线程池。
自定义线程池有两种实现方法:
- 直接在handler中注入线程池,业务逻辑中使用线程池。
- addLast中添加自定义线程池: 在pipeline的addLast中有这样的方法表示我们可以添加新的EventExecutorGroup来运行这个handler
ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers)
public NioEventLoopGroup(int nThreads, Executor executor)
NioEventLoopGroup可以添加自定义的ThreadFactory来创建线程,重写一个 ThreadFactory,使用new Thread也是没问题的
总结
netty的源码主要流程阅读的差不多了,这几篇文章只看了nio的部分,实际上netty支持更多的io模型。netty源码刚开始打开的时候不知所措,经过将模块给拆分,一步一步的去了解各个模块的功能最后再整合在一起,最终还是战胜了对于源码阅读的恐惧。阅读源码最重要的还是下载一份源码去细心看,看书以及博客都不及实际操作以及DEBUG。这几篇文章都没有很详细的解析一些细节的东西,重要的东西还是留给自己探索!