dotnetty源码解读一些要点

DefaultAttributeMap

它绑定在Channel或者ChannelHandlerContext上的一个附件。 ChannelHandlerContext都是ChannelHandler和ChannelPipeline之间连接的桥梁, 每一个ChannelHandlerContext都有属于自己的上下文, 每一个ChannelHandlerContext上如果有AttributeMap都是绑定上下文的, A的ChannelHandlerContext中的AttributeMap,B的ChannelHandlerContext是无法读取到的。 但是Channel上的AttributeMap是共享的,每一个ChannelHandler都能获取到。

AbstractUnsafe

unsafe 是内部接口, 从设计上说这个接口的所有实现都是netty的内部代码,只能被netty自己使用. netty的使用者时不应该 直接调用这些内部实现的.Unsafe 操作不能被用户代码调用.这些方法仅仅用于实现具体的transport, 而且必须被I/O线程调用.
unsafe 特别关键, 它封装了对底层 Socket 的操作, 因此实际上是沟通 Netty 上层和 底层的重要的桥梁.

  1. RegisterAsync 会把eventloop注册进来
  2. 其内部方法.大多是通过pipe管道的head调用的.channel.bind->pipe.bind->tail.bind->head.bind->unsafe.bind

SocketChannelAsyncOperation

继承与SocketAsyncEventArgs,当出发完成事件的时候会执行AbstractSocketChannel.IoCompletedCallback
1. 服务器端执行流程
* bootstrap.BindAsync()->channel.BindAsync->pipeline.BindAsync->tail.BindAsync->head.BindAsync->unsafe.BindAsync
* channel.DoBind() 使用socket绑定并监听.并将channel设置为active
* this.channel.pipeline.FireChannelActive() 出发管道中的信号激活
* channel.read->pipe.read->unsafe.BeginRead
* channel.DoBeginRead->channel.ScheduleSocketRead
* 使用ReadOperation(SocketChannelAsyncOperation实例)进行receiveasync
* unsafe.FinishRead 实例化TcpSocketChannel保存新建立的socket并触发pipe.FireChannelRead
* bootstrap的时候会添加ServerBootstrapAcceptor.这个时候会触发
* 将bootstrap中的child相关的属性设置给新的channel
* 在新的channel实例化的时候仍然会实例化SocketAsyncEventArgs然后就经过receivea完成事件
* AbstractSocketByteChannel.FinishRead 是完成读取字节的关键
TcpServerSocketChannelUnsafe.FinishRead

```
                        while (allocHandle.ContinueReading())
                    {
                        connectedSocket = ch.Socket.Accept();
                        message = this.PrepareChannel(connectedSocket);

                        connectedSocket = null;
                        ch.ReadPending = false;
                        pipeline.FireChannelRead(message);
                        allocHandle.IncMessagesRead(1);
                    }
```
这一段不知道干什么的..因为socket是非阻塞的.在调用第一句 ch.Socket.Accept();就会抛出异常..(SocketException ex) when (ex.SocketErrorCode == SocketError.WouldBlock)
https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.accept.aspx
  1. 客户端执行流程 待做

由上可见,netty中channel的任何调用都会先经过管道.再由管道按照顺序进行执行.而unsafe是socket底层的一些操作.也总是会被默认执行(或者在head中或者直接在pipe中)

DefaultChannelPipeline

  1. DefaultChannelPipeline 中, 还有两个特殊的字段, 即 head 和 tail, 而这两个字段是一个双向链表的头和尾. 其实在 DefaultChannelPipeline 中, 维护了一个以 AbstractChannelHandlerContext 为节点的双向链表, 这个链表是 Netty 实现 Pipeline 机制的关键.
  2. 即 header 是一个 outboundHandler, 而 tail 是一个inboundHandler,

AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量, 分别用于标识 Context 所对应的 handler 的类型, 即:
* inbound 为真时, 表示对应的 ChannelHandler 实现了 ChannelInboundHandler 方法.
* outbound 为真时, 表示对应的 ChannelHandler 实现了 ChannelOutboundHandler 方法.

                                                I/O Request
                                            via Channel or
                                        ChannelHandlerContext
                                                    |
+---------------------------------------------------+---------------+
|                           ChannelPipeline         |               |
|                                                  \|/              |
|    +---------------------+            +-----------+----------+    |
|head| Inbound Handler  N  |            | Outbound Handler  1  |head|
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  .               |
|               .                                   .               |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
|        [ method call]                       [method call]         |
|               .                                   .               |
|               .                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
|               |                                  \|/              |
|    +----------+----------+            +-----------+----------+    |
|tail| Inbound Handler  1  |            | Outbound Handler  M  |tail|
|    +----------+----------+            +-----------+----------+    |
|              /|\                                  |               |
+---------------+-----------------------------------+---------------+
                |                                  \|/
+---------------+-----------------------------------+---------------+
|               |                                   |               |
|       [ Socket.read() ]                    [ Socket.write() ]     |
|                                                                   |
|  Netty Internal I/O Threads (Transport Implementation)            |
+-------------------------------------------------------------------+

inbound 事件和 outbound 事件的流向是不一样的, inbound 事件的流行是从下至上, 而 outbound 刚好相反, 是从上到下. 并且 inbound 的传递方式是通过调用相应的 ChannelHandlerContext.fireIN_EVT() 方法, 而 outbound 方法的的传递方式是通过调用 ChannelHandlerContext.OUT_EVT() 方法. 例如 ChannelHandlerContext.fireChannelRegistered() 调用会发送一个 ChannelRegistered 的 inbound 给下一个ChannelHandlerContext,

Outbound 事件的传播方向是 tail -> customContext -> head.
Inbound 的特点是它传播方向是 head -> customContext -> tail.

Inbound 事件传播方法有:

ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()

Oubound 事件传输方法有:

ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)

注意, 如果我们捕获了一个事件, 并且想让这个事件继续传递下去, 那么需要调用 Context 相应的传播方法.
例如:

public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("Connected!");
        ctx.fireChannelActive();
    }
}

public clas MyOutboundHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
        System.out.println("Closing ..");
        ctx.close(promise);
    }
}

上面的例子中, MyInboundHandler 收到了一个 channelActive 事件, 它在处理后, 如果希望将事件继续传播下去, 那么需要接着调用 ctx.fireChannelActive().

对于 Outbound事件:
* Outbound 事件是请求事件(由 Connect 发起一个请求, 并最终由 unsafe 处理这个请求)
* Outbound 事件的发起者是 Channel
* Outbound 事件的处理者是 unsafe
* Outbound 事件在 Pipeline 中的传输方向是 tail -> head.
* 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.xxx (例如 ctx.connect) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.
* Outbound 事件流: Context.OUT_EVT -> Connect.findContextOutbound -> nextContext.invokeOUT_EVT -> nextHandler.OUT_EVT -> nextContext.OUT_EVT

对于 Inbound 事件:
* Inbound 事件是通知事件, 当某件事情已经就绪后, 通知上层.
* Inbound 事件发起者是 unsafe
* Inbound 事件的处理者是 Channel, 如果用户没有实现自定义的处理方法, 那么Inbound 事件默认的处理者是 TailContext, 并且其处理方法是空实现.
* Inbound 事件在 Pipeline 中传输方向是 head -> tail
* 在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.fireIN_EVT (例如 ctx.fireChannelActive) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.
* Outbound 事件流: Context.fireIN_EVT -> Connect.findContextInbound -> nextContext.invokeIN_EVT -> nextHandler.IN_EVT -> nextContext.fireIN_EVT
* outbound 和 inbound 事件十分的镜像, 并且 Context 与 Handler 直接的调用关系是否容易混淆, 因此读者在阅读这里的源码时, 需要特别的注意.

AbstractChannelHandlerContext

  1. FindContextInbound 同下
  2. FindContextOutbound 从 DefaultChannelPipeline 内的双向链表的 tail 开始, 不断向前寻找第一个 outbound 为 true 的 AbstractChannelHandlerContext, 然后调用它的 invokeConnect 方法,在 DefaultChannelPipeline 的构造器中, 会实例化两个对象: head 和 tail, 并形成了双向链表的头和尾. head 是 HeadContext 的实例, 它实现了 ChannelOutboundHandler 接口, 并且它的 outbound 字段为 true. 因此在 findContextOutbound 中, 找到的 AbstractChannelHandlerContext 对象其实就是 head.
  3. HeadContext 和 TailContext 继承于 AbstractChannelHandlerContext 的同时也实现了 ChannelHandler 接口了, 因此它们有 Context 和 Handler 的双重属性.

ChannelInitializer

此类就是一个向管道注册处理器的适配器,在channel执行Registered的时候,向管道中注入一些处理.然后把该管道处理方法删除

bossGroup 与 workerGroup

  1. 其实呢, bossGroup 是用于服务端 的 accept 的, 即用于处理客户端的连接请求. 我们可以把 Netty 比作一个饭店, bossGroup 就像一个像一个前台接待, 当客户来到饭店吃时, 接待员就会引导顾客就坐, 为顾客端茶送水等. 而 workerGroup, 其实就是实际上干活的啦, 它们负责客户端连接通道的 IO 操作: 当接待员 招待好顾客后, 就可以稍做休息, 而此时后厨里的厨师们(workerGroup)就开始忙碌地准备饭菜了.
  2. 服务器端 bossGroup 不断地监听是否有客户端的连接, 当发现有一个新的客户端连接到来时, bossGroup 就会为此连接初始化各项资源, 然后从 workerGroup 中选出一个 EventLoop 绑定到此客户端连接中. 那么接下来的服务器与客户端的交互过程就全部在此分配的 EventLoop 中了

     

  3. 服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 所以对只有一个服务的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费.:netty作者说:我们在不同的服务器引导之间共享NioEventLoopGroup,多个boss线程是有用的,

  4. bossgroup中的socket是非阻塞的..因为其只是接受新的socket..如果在处理accept的时候阻塞了就会影响新的socket建立.所以不能是阻塞的.workgroup内的socket是阻塞的因为他要等待一个请求处理完成,要不然多个请求,修改程序某个变量会造成锁的问题.

bootstrap

serverbootstrap在channel初始化的时候会.向channel中写入各种配置包括基本的管道处理
* 服务器端的 handler 与 childHandler 的区别与联系:
* 在服务器 NioServerSocketChannel 的 pipeline 中添加的是 handler 与 ServerBootstrapAcceptor.
* 当有新的客户端连接请求时, ServerBootstrapAcceptor.channelRead 中负责新建此连接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 对应的 pipeline 中, 并将此 channel 绑定到 workerGroup 中的某个 eventLoop 中.
* handler 是在 accept 阶段起作用, 它处理客户端的连接请求.
* childHandler 是在客户端连接建立以后起作用, 它负责客户端连接的 IO 交互.

SingleThreadEventLoop

在 AbstractScheduledEventExecutor 中, Netty 实现了 NioEventLoop 的 schedule 功能, 即我们可以通过调用一个 NioEventLoop 实例的 schedule 方法来运行一些定时任务. 而在 SingleThreadEventLoop 中, 又实现了任务队列的功能, 通过它, 我们可以调用一个 NioEventLoop 实例的 execute 方法来向任务队列中添加一个 task, 并由 NioEventLoop 进行调度执行.

通常来说, NioEventLoop 肩负着两种任务, 第一个是作为 IO 线程, 执行与 Channel 相关的 IO 操作, 包括 调用 select 等待就绪的 IO 事件、读写数据与数据的处理等; 而第二个任务是作为任务队列, 执行 taskQueue 中的任务, 例如用户调用 eventLoop.schedule 提交的定时任务也是这个线程执行的.

Netty 的 IO 处理循环
netty 中必然有一个 Selector 线程, 用于不断调用 Java NIO 的 Selector.select 方法, 查询当前是否有就绪的 IO 事件.

posted @ 2018-08-08 17:18  沙滩裤  阅读(516)  评论(0编辑  收藏  举报