美文网首页vert.x & netty
Netty系列(5) 以一个例子分析 Netty 源码

Netty系列(5) 以一个例子分析 Netty 源码

作者: suxin1932 | 来源:发表于2020-03-29 10:09 被阅读0次

    前言

    本文 debug 的代码来自于
    [ https://www.jianshu.com/p/209576915459 ] 一文中的
    [ 2.1 LineBasedFrameDecoder + StringDecoder (基于回车换行符解决粘包拆包问题) ]
    

    1.服务端的启动

    Reactor模式.png

    1.1 Server启动流程

    1.初始化
    1.1 属性赋值
    >> 初始化创建 2 个 NioEventLoopGroup:
    其中 boosGroup 用于 Accetpt 连接建立事件并分发请求,workerGroup 用于处理 I/O 读写事件和业务逻辑。
    >> 基于 ServerBootstrap(服务端启动引导类):
    配置 EventLoopGroup、Channel 类型,连接参数、配置入站、出站事件 handler。
    1.2 初始化
    
    2.register
    

    1.2 Server启动之后

    Server 端包含 1 个 Boss NioEventLoopGroup 和 1 个 Worker NioEventLoopGroup。
    NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,
    每个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。
    
    #每个 boss NioEventLoop 循环执行的任务包含 3 步:
    1)轮询 Accept 事件;
    2)处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上;
    3)处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。
    
    #每个 worker NioEventLoop 循环执行的任务包含 3 步:
    1)轮询 Read、Write 事件;
    2)处理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理;
    3)处理任务队列中的任务,runAllTasks。
    
    #其中任务队列中的 Task 有 3 种典型使用场景:
    1) 用户程序自定义的普通任务:
    ctx.channel().eventLoop().execute(newRunnable() {
       @Override
       publicvoidrun() {
            //...
       }
    });
    2) 非当前 Reactor 线程调用 Channel 的各种方法:
    例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,
    然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。
    最终的 Write 会提交到任务队列中后被异步消费。
    3) 用户自定义定时任务:
    ctx.channel().eventLoop().schedule(newRunnable() {
      @Override
      publicvoidrun() {
        ...
      }
    }, 60, TimeUnit.SECONDS);
    

    3.数据在ChannelPipeline中的具体流动过程

    // Channel-ChannelPipeline-ChannelHandlerContext-ChannelHandler
    
    当client与server建立tcp连接后, 即在二者之间创建了一个channel, 
    server也会在该channl上, 创建一条channelpipeline,
    并在channlpipeline中添加一系列的channelhandlercontext及channelhandler.
    ...childHandler(new ChannelInitializer<NioSocketChannel>() {
        @Override
        protected void initChannel(NioSocketChannel ch) throws Exception {
            ch.pipeline()
            // 这里结合 LineBasedFrameDecoder + StringDecoder 实现粘包拆包的处理, 其中 LineBasedFrameDecoder 是基于 \n 或 \r\n 实现
            // 这里需要注意: 单条消息不能超过给定的最大限度, 否则会抛出异常
            .addLast("lineBasedFrameDecoder", new LineBasedFrameDecoder(1024))
            .addLast("stringDecoder", new StringDecoder())
            .addLast("serverhandler01", new ServerHandler01());
        }
    });
    
    #以fireChannelRead为例
    当client向server端通过Channel进行数据交互时(连接建立之后), 
    请求数据在server端的channelpipeline中的流转过程如下:
    
    step1.png step2.png step3.png step4.png step5.png step6.png step7.png step8.png step9.png 请求到达server后, 在childHandler中的ChannelPipeline中的堆栈信息.png
    step9之后, 然后重复 step5-step8, 直到链表的尾部.
    
    当从server端进行出站时, 反过来, 先从链表的尾部开始, 寻找outbound对应的handler, 
    直至链表头部, 至此, 在服务端的handler的出入站便结束了.
    
    客户端的出入站与之类似, 此处不再另行分析.
    
    #这里需要注意的是: 
    >> 如果自定义 ChannelInboundHandler 时, 请评估是否要在对应方法里调用firexxx方法, 
    以便于将事件继续传递给ChannelHandlerContext的双向链表中的下一个节点, 否则, 将可能停止事件传播, 带来问题.
    >> 同样的, 如果自定义 ChannelOutboundHandler时, 也请评估是否要在对应的方法里, 调用write或其他方法,
    以便于将事件继续传递给ChannelHandlerContext的双向链表中的下一个节点, 否则, 将可能停止事件传播, 带来问题.
    

    Netty中如何解决select空轮询导致cpu使用率升至100%的bug
    https://blog.csdn.net/qq_41884976/article/details/91913820

    参考资料
    李林锋. (2015). Netty权威指南(第2版).
    https://blog.csdn.net/u010739551/article/details/80519295
    https://segmentfault.com/a/1190000007282789
    https://segmentfault.com/a/1190000007283053

    相关文章

      网友评论

        本文标题:Netty系列(5) 以一个例子分析 Netty 源码

        本文链接:https://www.haomeiwen.com/subject/wcsyuhtx.html