`
vinceall
  • 浏览: 10714 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Netty源码分析1---服务端绑定流程

阅读更多

前言:

一直自认为水平很差,什么都不懂,想找个源码来看看,无奈时间不够(码农搬砖很辛苦的诶),最近终于抽了点时间,看了一下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

 

本人辛苦分析、码字,请尊重他人劳动成果,转载不注明出处的诅咒你当一辈子一线搬砖工,嘿嘿~

 

欢迎讨论、指正~

 

下篇预告:服务端读写流程分析

  • 大小: 30 KB
分享到:
评论
3 楼 vinceall 2014-06-13  
beyondyuefei 写道
vinceall 写道
求高手指正。。。

洋洋洒洒写了这么多,值得鼓励,我也准备研究netty,最近有本书《netty权威指南》快出版了,能满足你

嗯,前几天看到了,正在申请部门买书,过几天就拿到了~~不过我看的是netty3.x,以后还要转换到nett4or5才行啊~
2 楼 beyondyuefei 2014-06-13  
vinceall 写道
求高手指正。。。

洋洋洒洒写了这么多,值得鼓励,我也准备研究netty,最近有本书《netty权威指南》快出版了,能满足你
1 楼 vinceall 2014-06-10  
求高手指正。。。

相关推荐

    netty源码深入分析

    《Netty源码深入分析》是由美团基础架构部的闪电侠老师所分享的一系列关于Netty源码解析的视频教程。以下将根据标题、描述、标签以及部分内容等信息,对Netty及其源码进行深入剖析。 ### Netty简介 Netty是基于...

    netty源码分析之服务端启动全解析

    Netty是一款高性能的网络应用程序框架,它使用Java编程语言开发,主要用于网络应用程序的快速和易于开发,支持TCP和UDP...通过对Netty源码的深入分析,可以更好地理解其工作机制,对开发高性能的网络应用有极大的帮助。

    以netty4.1源码中的EchoServer为例对netty的源码进行分析.docx

    在本文中,我们将深入分析 Netty 4.1 源码中的 EchoServer 示例,以理解其核心组件和工作原理。 首先,我们关注 EchoServer 服务端的初始化,这涉及到两个关键组件:`bossGroup` 和 `workerGroup`。它们都是 `...

    socket长连接,netty服务器与android源码

    在分析源码时,应关注以下几个关键点: 1. Android客户端如何创建Socket连接并保持连接。 2. 如何在Android中处理网络错误和异常,确保长连接的稳定性。 3. Netty服务器如何处理新连接,以及数据的读写逻辑。 4. ...

    精通并发与netty视频教程(2018)视频教程

    56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其在Netty中的应用 58_Reactor模式与Netty之间的关系详解 59_Acceptor与Dispatcher角色分析 60_Netty的自适应缓冲区分配策略与堆外内存创建方式 61_...

    Netty5.0架构剖析和源码解读

    Netty源码分析 ##### 3.1. 服务端创建 Netty是一个高性能的网络应用程序框架,其核心优势在于异步事件驱动的I/O处理机制。接下来我们将深入分析Netty服务端的创建过程。 ###### 3.1.1. 服务端启动辅助类...

    精通并发与netty 无加密视频

    第56讲:Netty服务器地址绑定底层源码分析 第57讲:Reactor模式透彻理解及其在Netty中的应用 第58讲:Reactor模式与Netty之间的关系详解 第59讲:Acceptor与Dispatcher角色分析 第60讲:Netty的自适应缓冲区分配...

    精通并发与 netty 视频教程(2018)视频教程

    53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其...

    聊聊 Netty 那些事儿之 Reactor 在 Netty 中的实现(创建篇).doc

    Netty 是一个高性能、异步事件驱动的网络应用框架...主从 Reactor 线程组设计保证了服务端可以高效处理大规模并发连接,而通过源码分析,我们可以更深入地理解其内部机制,从而更好地利用 Netty 开发高性能的网络应用。

    seata源码研究.docx

    Netty是一个高性能、异步事件驱动的网络应用框架,它使得开发健壮的服务端和客户端变得更加容易。 - **NIO(New I/O)**:NIO提供了比传统的Java I/O更高的性能和灵活性。在NIO中,I/O操作是非阻塞的,这意味着在...

    java开源包3

    用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...

    WPF+Socket+DB(聊天软件).zip

    【客户端源码分析】 客户端源码通常包括UI界面的创建、Socket连接的建立、数据收发逻辑以及与数据库的交互等部分。开发者可能会使用WPF提供的控件(如TextBox、Button、ListView等)来构建聊天界面,利用Socket类...

    java开源包1

    用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...

    java开源包8

    用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...

    java开源包10

    用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...

    java开源包11

    用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...

    java开源包2

    用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...

    java开源包6

    用来计算 MD5、SHA 哈希算法的 Java 类库,支持 "MD5", "SHA", "SHA-1", "SHA-256", "SHA-384", "SHA-512". 高性能RPC框架 nfs-rpc nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用...

Global site tag (gtag.js) - Google Analytics