Netty学习摘记 —— 深入了解Netty核心组件

本文参考

本篇文章是对《Netty In Action》一书第三章"Netty的组件和设计"的学习摘记,主要内容为Channel、EventLoop、ChannelFuture、ChannelHandler和ChannelPipeline等组件的深入介绍

原书第三章中对编解码器有一定介绍,但是在原书后有一个专门的章节讲解,所以本篇文章未摘记此内容

Channel接口

A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind.

Netty 的Channel接口所提供的 API,降低了直接使用 Socket 类的复杂性(下面仅列举一部分,如Unsafe内核模式的方法未列出)。此外,Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根,如EmbeddedChannel、LocalServerChannel、NioDatagramChannel、NioSctpChannel、NioSocketChannel

ChannelId id() —— Returns the globally unique identifier of this Channel

EventLoop eventLoop() —— Return the EventLoop this Channel was registered to.

ChannelPipeline pipeline() —— Return the assigned ChannelPipeline.

ChannelConfig config() —— Returns the configuration of this channel.

boolean isRegistered() —— Returns true if the Channel is registered with an EventLoop.

boolean isOpen() —— Returns true if the Channel is open and may get active later

boolean isActive() —— Return true if the Channel is active and so connected.

SocketAddress localAddress() —— Returns the local address where this channel is bound to.

SocketAddress remoteAddress() —— Returns the remote address where this channel is connected to.

ChannelFuture closeFuture() —— Returns the ChannelFuture which will be notified when this channel is closed.

 

EventLoop接口

Will handle all the I/O operations for a Channel once registered. 

 

SingleThreadEventLoop implementation ——  NioEventLoop registers the Channel's to a Selector and so does the multi-plexing of these in the event loop. 

EventLoop用于处理连接的生命周期中所发生的I/O事件,并且所有事件都在一个Thread上被处理,这个线程不断检查状态变化并对其做出相应响应,在程序对状态的改变做出响应之后,选择器将会被重置,并重复这个过程。因为所有事件都在一个专有的线程上,所以消除了对于同步的需要

一个EventLoopGroup包含一个或者多个EventLoop

一个EventLoop在它的生命周期内只和一个Thread绑定(前面提到的选择器的概念)

一个Channel在它的生命周期内只注册于一个EventLoop(一个连接对应一个选择器)

一个EventLoop可能会被分配给一个或多个Channel(但一个选择器可能管理多个连接)

 

 

ChannelFuture接口

The result of an asynchronous Channel I/O operation.

Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了 ChannelFuture接口,其addListener()方法注册了一个ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知

例如上一篇文章中ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);无论服务端是否发送消息到客户端成功,发送操作结束后都会将该连接关闭

当然不只是通过addListener()方法来注册一个监听器,例如closeFuture方法也会返回一个ChannelFuture监听连接关闭

 

ChannelHandler接口

Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its ChannelPipeline.

ChannelHandler充当了所有处理入站和出站数据的应用程序逻辑的容器,可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常。其中,ChannelInboundHandler是一个经常使用的子接口,接收入站事件和数据,这些数据随后将会被我们的应用程序的业务逻辑所处理,例如要给连接的客户端发送响应时,可以从ChannelInboundHandler冲刷数据。因此,应用程序的业务逻辑通常驻留在一个或者多个ChannelInboundHandler中

 

ChannelPipeline接口

A list of ChannelHandles which handles or intercepts inbound events and outbound operations of a Channel

 

Each channel has its own pipeline and it is created automatically when a new channel is created.

ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的 API。当 Channel被创建时,它会被自动地分配到它专属的ChannelPipeline

ChannelHandler安装到ChannelPipeline中的过程:

一个ChannelInitializer的实现被注册到了ServerBootstrap中 -> 当ChannelInitializer.initChannel()方法被调用时,ChannelInitializer 将在ChannelPipeline中安装一组自定义的ChannelHandler -> ChannelInitializer将它自己从ChannelPipeline中移除

在上篇文章中,我们通过ServerBootstrap的实例方法childHandler()添加ChannelHandler到ChannelPipeline上
b.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(serverHandler);
}});

出入站描述图(来自源码注释):

源码注释中举了一个ChannelHandler依次执行业务逻辑的例子,倘若在ServerBootstrap中注册了5个ChannelHandler

Each channel has its own pipeline and it is created automatically when a new channel is created.

 

p.addLast("1", new InboundHandlerA());

p.addLast("2", new InboundHandlerB());

p.addLast("3", new OutboundHandlerA());

p.addLast("4", new OutboundHandlerB());

p.addLast("5", new InboundOutboundHandlerX()); // 既实现了ChannelInboundHandler,又实现了ChannelOutboundHandler

 

3 and 4 don't implement ChannelInboundHandler, and therefore the actual evaluation order of an inbound event will be: 1, 2, and 5

1 and 2 don't implement ChannelOutboundHandler, and therefore the actual evaluation order of a outbound event will be: 5, 4, and 3

但是使得事件流经 ChannelPipeline 是 ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段由addLast()方法安装。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的,因此,按上面的引用来讲,第一个被入站事件看到的ChannelHandler将是 1,而第一个被出站事件看到的ChannelHandler将是5。可见,被我们称为ChannelPipeline的是这些ChannelHandler的编排顺序

那么,我们如何将数据传递给链中的下一个ChannelHandler呢?通过源码我们可以看到,ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter的方法中都提供了简单地将事件传递给下一个ChannelHandler 的实现,倘若不重载那些方法,则会自动传递到下一个ChannelHandler中。例如在之前的文章中我们提到过的异常处理机制,如果我们在当前的ChannelHandler中不重载exceptionCaught()方法,则会交由链中的下一个ChannelHandler处理,下面是exceptionCaught()方法实现的源码

/**

   * Calls fireExceptionCaught(Throwable) to forward

   * to the next ChannelHandler in the ChannelPipeline.

   */

  @Skip

  @Override

  @SuppressWarnings("deprecation")

  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

      ctx.fireExceptionCaught(cause);

  }

ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter的很多方法都加上了@Skip注解,作用为,当未重载被注解的方法时,将自动"跳过"该方法,交给下一个ChannelHandler来处理

Indicates that the annotated event handler method in ChannelHandler will not be invoked by ChannelPipeline and so MUST only be used when the ChannelHandler method does nothing except forward to the next ChannelHandler in the pipeline.

 

Note that this annotation is not inherited. If a user overrides a method annotated with @Skip, it will not be skipped anymore. Similarly, the user can override a method not annotated with @ Skip and simply pass the event through to the next handler, which reverses the behavior of the supertype.

 

ChannelHandlerContext接口

Enables a ChannelHandler to interact with its ChannelPipeline and other handlers. Among other things a handler can notify the next ChannelHandler in the ChannelPipeline as well as modify the ChannelPipeline it belongs to dynamically.

当ChannelHandler被添加到ChannelPipeline时,它将会被分配一个ChannelHandlerContext,它代表了ChannelHandler和ChannelPipeline之间的绑定,并且这个绑定永远不会改变,所以缓存对它的引用是安全的

虽然这个对象可以被用于获取底层的Channel,但是它主要还是被用于写出站数据。 在Netty中,有两种发送消息的方式:你可以直接写到Channel中,Channel接口也具有writeAndFlush()方法,或者可以写到和ChannelHandler相关联的ChannelHandlerContext对象中。前一种方式将会导致消息从ChannelPipeline的尾端开始流动,而后者将导致消息从ChannelPipeline中的下一个ChannelHandler开始流动,因此,ChannelHandlerContext 的方法将产生更短的事件流,能够减少将事件传经对它不感兴趣的ChannelHandler所带来的开销,应该尽可能地利用这个特性来获得最大的性能

 

引导

有两种类型的引导:一种用于客户端(简单地称为 Bootstrap),而另一种 (ServerBootstrap)用于服务器。无论你的应用程序使用哪种协议或者处理哪种类型的数据, 唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器,换句话说,是监听传入的连接还是建立到一个或者多个进程的连接

ServerBootstrap需要绑定到一个本地端口,Bootstrap连接到远程主机和端口,看上去很相似,但是引导一个客户端只需要一个 EventLoopGroup,而引导一个 ServerBootstrap需要两个EventLoopGroup,因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字,而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel

与 ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建 Channel 的 EventLoop。一旦连接被接受,第二个 EventLoopGroup 就会给它的 Channel 分配一个EventLoop

posted @ 2020-04-02 12:16  咕~咕咕  阅读(438)  评论(0编辑  收藏  举报