前言:
一直自认为水平很差,什么都不懂,想找个源码来看看,无奈时间不够(码农搬砖很辛苦的诶),最近终于抽了点时间,看了一下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
本人辛苦分析、码字,请尊重他人劳动成果,转载不注明出处的诅咒你当一辈子一线搬砖工,嘿嘿~
欢迎讨论、指正~
下篇预告:服务端读写流程分析
相关推荐
《Netty源码深入分析》是由美团基础架构部的闪电侠老师所分享的一系列关于Netty源码解析的视频教程。以下将根据标题、描述、标签以及部分内容等信息,对Netty及其源码进行深入剖析。 ### Netty简介 Netty是基于...
Netty是一款高性能的网络应用程序框架,它使用Java编程语言开发,主要用于网络应用程序的快速和易于开发,支持TCP和UDP...通过对Netty源码的深入分析,可以更好地理解其工作机制,对开发高性能的网络应用有极大的帮助。
在本文中,我们将深入分析 Netty 4.1 源码中的 EchoServer 示例,以理解其核心组件和工作原理。 首先,我们关注 EchoServer 服务端的初始化,这涉及到两个关键组件:`bossGroup` 和 `workerGroup`。它们都是 `...
在分析源码时,应关注以下几个关键点: 1. Android客户端如何创建Socket连接并保持连接。 2. 如何在Android中处理网络错误和异常,确保长连接的稳定性。 3. Netty服务器如何处理新连接,以及数据的读写逻辑。 4. ...
56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其在Netty中的应用 58_Reactor模式与Netty之间的关系详解 59_Acceptor与Dispatcher角色分析 60_Netty的自适应缓冲区分配策略与堆外内存创建方式 61_...
Netty源码分析 ##### 3.1. 服务端创建 Netty是一个高性能的网络应用程序框架,其核心优势在于异步事件驱动的I/O处理机制。接下来我们将深入分析Netty服务端的创建过程。 ###### 3.1.1. 服务端启动辅助类...
第56讲:Netty服务器地址绑定底层源码分析 第57讲:Reactor模式透彻理解及其在Netty中的应用 第58讲:Reactor模式与Netty之间的关系详解 第59讲:Acceptor与Dispatcher角色分析 第60讲:Netty的自适应缓冲区分配...
53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其...
Netty 是一个高性能、异步事件驱动的网络应用框架...主从 Reactor 线程组设计保证了服务端可以高效处理大规模并发连接,而通过源码分析,我们可以更深入地理解其内部机制,从而更好地利用 Netty 开发高性能的网络应用。
Netty是一个高性能、异步事件驱动的网络应用框架,它使得开发健壮的服务端和客户端变得更加容易。 - **NIO(New I/O)**:NIO提供了比传统的Java I/O更高的性能和灵活性。在NIO中,I/O操作是非阻塞的,这意味着在...
用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...
【客户端源码分析】 客户端源码通常包括UI界面的创建、Socket连接的建立、数据收发逻辑以及与数据库的交互等部分。开发者可能会使用WPF提供的控件(如TextBox、Button、ListView等)来构建聊天界面,利用Socket类...
用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...
用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...
用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...
用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...
用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...
用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...