前言:
一直自认为水平很差,什么都不懂,想找个源码来看看,无奈时间不够(码农搬砖很辛苦的诶),最近终于抽了点时间,看了一下Netty,感觉代码确实写得干净规范,看着舒服(吐槽一下有些开源代码,简直惨不忍睹,神马代码风格和规范通通木有,无力吐槽)。之前看的Netty源码分析感觉没说透彻,索性干脆自己写,也不知道对不对,和大家分享讨论一下吧,也算是复习巩固,欢迎交流。。。
注:
以下分析基于Netty 3.6.6 Final版本,虽然Netty4,5都出来了,但是3.x是经典啊(况且不知道用4 or 5的多不多,感觉一般公司都不太愿意用太新的东西吧),读代码是看别人怎么设计架构,业务逻辑实现不是最重要的,so,就这么愉快地决定了。。。
大家知道Netty是基于NIO的异步网络通信框架,下层使用Java NIO库(可以看成实际的网络通信层),上层对应于Netty自己的一些逻辑抽象。知道了这两层,对后面的理解就容易多了,下面是个草图(这两层之间的关系后面再讲):
下面是一个EchoServer的代码(EchoHandler原样回写,代码就不贴了):
ServerBootstrap bootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new EchoHandler());
}
});
bootstrap.bind(new InetSocketAddress(1210));
1. ServerBootstrap是一个辅助类,用于设置一些启动服务端时需要的参数,本身与服务端逻辑无关。这里用NioServerSocketChannelFactory初始化,传入了两个线程池(一个boss,一个worker,boss监听端口,worker处理具体的连接),隐约感觉有点往Reactor模式上靠的倾向。
2. 然后就是设置pipeline,即配置自己的业务逻辑handler。
3. 最后bind
1,2步很简单,直接看bind方法:
ChannelFuture future = bindAsync(localAddress);
future.awaitUninterruptibly();
if (!future.isSuccess()) {
future.getChannel().close().awaitUninterruptibly();
throw new ChannelException("Failed to bind to: " + localAddress,
future.getCause());
}
return future.getChannel();
发现同步bind不过也就在异步bind上封了一下,所以现在你知道为啥Netty是全异步的了吧。。。bindAsync是关键,继续进去:
Binder binder = new Binder(localAddress);
ChannelHandler parentHandler = getParentHandler();
ChannelPipeline bossPipeline = pipeline();
bossPipeline.addLast("binder", binder);
if (parentHandler != null) {
bossPipeline.addLast("userHandler", parentHandler);
}
Channel channel = getFactory().newChannel(bossPipeline);
final ChannelFuture bfuture = new DefaultChannelFuture(channel, false);
binder.bindFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future)
throws Exception {
if (future.isSuccess()) {
bfuture.setSuccess();
} else {
bfuture.getChannel().close();
bfuture.setFailure(future.getCause());
}}});
return bfuture;
bindAsnyc首先new了一个binder,光看名字就知道是干啥的了,然后把binder和parent handler挂到了pipeline上,binder是一个UpStreamHandler:
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent evt) {
......
evt.getChannel().bind(localAddress).addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
bindFuture.setSuccess();
} else {
bindFuture.setFailure(future.getCause());
}}});
}
binder监听channelOpen事件,得到channel,然后bind,binder是UpStream的,这个up或down就对应上面的两层结构,所以不难猜到up就是Java NIO(下面简称网络层)到Netty的,down反之。所以可以假设是网络层去触发的chennalOpen事件,由binder响应(后面验证)。evt.getChannel().bind依次调用Channels.bind:
channel.getPipeline().sendDownstream(
new DownstreamChannelStateEvent(channel, future,
ChannelState.BOUND, localAddress));
构造了一个DownStream事件,再调DefaultChannelPipeline.sendDownstream向下发送:
DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail);
if (tail == null) {
try {
getSink().eventSunk(this, e);
return;
} catch (Throwable t) {
notifyHandlerException(e, t);
return;
}
}
sendDownstream(tail, e);
sendDownstream(tail,e)中先处理事件,再向下传递事件,从这我们就知道handler链表是到底怎么处理的,而不只是一个宽泛的概念。继续,如果tail为null,就将事件传递到sink中(sink是水槽的意思,计算机里叫汇,还有个对应的叫源,想到水槽放水时的漩涡你就知道这个汇是干啥的了。。。无比形象),sink作为所有handler最终的汇集,起到连接Netty和网络层的作用。getSink().eventSunk()最终调到NioServerSocketPipelineSink.evenSunk,其中的关键是:
switch (state) {
case BOUND:
if (value != null) {
((NioServerBoss) channel.boss).bind(channel, future, (SocketAddress) value);
} else {
((NioServerBoss) channel.boss).close(channel, future);
}
break;
前面构造的下行BOUND事件(前面红色部分)在这里匹配,然后调boss.bind:
registerTask(new RegisterTask(channel, future, localAddress));
registerTask往taskQueue中投递了一个Runnable任务(代码不贴了),Runnable干了3件事:
channel.socket.socket().bind(localAddress,channel.getConfig().getBacklog());
fireChannelBound(channel, channel.getLocalAddress());
channel.socket.register(selector, SelectionKey.OP_ACCEPT,channel);
1. 调Java NIO的接口完成真正的bind操作。
2. 触发了一个channelBound上行事件。
3. 在监听端口的socket上注册了OP_ACCEPT的事件,等待连接上来。
调boss.bind的线程(即用户线程,后面验证)投递runnable到queue,必然有另一个线程poll出来,典型的生产者消费者模式,用queue解耦。再看NioServerBoss,它和NioClientBoss,AbstractNioWorker都继承自AbstractNioSelector,都有自己的taskQueue。不难猜到,poll queue的线程就是构造ServerBootstrap时传入的boss线程池,对应地,假设poll AbstractNioWorker.taskQueue是worker线程池(这两个线程池后面再验证)。
channelBound上行事件类似downStream,在handler中处理,从DefaultChannelPipeline.sendUpstream中可以看到,无head的时候会自动丢弃报文。这里会传给binder,binder没实现,继续向上传。
到这里bind流程结束,接下来看前面遗留的待验证的问题:
1. Netty层和网络层的关系
Netty层代表对应网络层的抽象,比如ServerSocket抽象成NioServerSocketChannel等,网络层代表真正的网络操作。从网络层到Netty层称为UpStream,反之DownStream。以UpStream为例,表示网络层操作导致了物理状态的变化,然后网络层通过上行事件将状态的转换通知给Netty层。所以上下行事件反映的是这两层之间的状态流转,互为因果。
2. Binder响应channelOpen,如何确定是用户线程触发的fireChannelOpen?
ServerBootstrap.bindAsync中调了getFactory().newChannel(),即NioServerSocketChannelFactory.newChannel(),其中在new NioServerSocketChannel时直接调了fireChannelOpen,所以是用户线程触发的。(另外一个角度:还没bind,只剩用户线程去触发了)
3. 如何确定poll各自的taskQueue的线程是boss和worker线程?这两个线程池是何时启动的?
NioServerBoss,AbstractNioWorker和NioClientBoss是AbstractNioSelector的子类,在new子类实例时会先调AbstractNioSelector.openSelector,其中:
DeadLockProofWorker.start(executor, newThreadRenamingRunnable(id, determiner));
这里传入了各自对应的executor,而newThreadRenamingRunnable则将这三个类自身传了进去(AbstractNioSelector实现了Runnable,其run方法中poll了queue),因此是boss或worker线程守在各自的 taskQueue上。
总结:
简单来说,整个流程可以看成是:用户线程调bind---serverSocket.open,fireChannelOpen---上行事件触发binder,binder产生下行事件---最终到serverSocket.bind,以用户线程为起点,先上后下才bind成功。
为什么设计得这么复杂?
个人理解的原因是:
(1) Netty由事件驱动,除了收发的消息,bind和connect等本身也是一种事件,两种事件有必要统一。将socket操作也构造成事件,就能使用统一的处理流程而不会有例外情况需要考虑,且handler链表的机制也更容易扩展代码。
(2) 这样设计更漂亮,理解透了会感觉很舒服,如果分开会觉得别扭(人类的强迫症,就像为啥物理学家一直在研究统一场论一样。。。)
参考资料:
http://ifeve.com/netty-reactor-4
本人辛苦分析、码字,请尊重他人劳动成果,转载不注明出处的诅咒你当一辈子一线搬砖工,嘿嘿~
欢迎讨论、指正~
下篇预告:服务端读写流程分析