ChannelHandler和ChannelPipeline

329 阅读5分钟

image.png

ChannelHandler

ChannelHandler用于处理i/o事件或者拦截i/o操作;

ChannelHandler本身不提供很多的方法, 所以通常你必须去实现它的子类

  • ChannelInboundHandler 用于处理输入方向的i/o事件
  • ChannelOutboundHandler 用于处理输出的i/o操作

或者,为了给你提供便利,也提供了一下的适配器类供你使用:

  • ChannelInboundHandlerAdapter 用于处理输入方向的 i/o 事件
  • ChannelOutboundHandlerAdapter 用于处理输出方向的i/o操作
  • ChannelDuplexHandler 用于处理输入和输出两个方向的i/o事件

ChannelHandlerContext

image.png ChannelHandler和ChannelHandlerContext是一起被提供了,并且在ChannelPipeline中,ChannelHandler和ChannelPipline交互是通过ChannelHandlContext来的,

ChannelHandler通过ChannelHandlerContext就可以向上游和下游传递事件、动态地修改pipeline、使用AttributeKey来存储信息。

ChannelHandler的状态管理

ChannelHandler中通常需要去存储一些状态信息,

netty建议使用成员变量出存状态信息

比如:

public class DataServerHandler extends SimpleChannelInboundHandler {
 
    private boolean loggedIn;
 
    @Override
    public void channelRead0({ChannelHandlerContext ctx, Message message) {
        if (message instanceof LoginMessage) {
            authenticate((LoginMessage) message);
            loggedIn = true;
        } else (message instanceof GetDataMessage) {
            if (loggedIn) {
                ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
            } else {
                fail();
            }
        }
    }
  ///...
}

如上因为ChannelHandler有一个状态变量,而这个状态变量是属于某一个连接的,因此就需要为每一个新的连接去创建一个ChannelHandler

如下实例代码为每一个新的连接去创建一个ChannelHandler实例:

public class DataServerInitializer extends ChannelInitializer {
    @Override
    public void initChannel(Channel channel) {
      channel.pipeline().addLast("handler", new DataServerHandler());
    }
}

使用AttributeKey

虽然netty推荐使用成员变量去存储ChannelHandler的状态,但是因为某些原因你不想去去创建很多的

ChannelHandler实例,那么这种情况你可以使用ChannelHandlerContext中的AttributeKey,

public interface Message {
    // your methods here
}
 
@Sharable
public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
    private final AttributeKey<Boolean> auth = AttributeKey.valueOf("auth");
 
 
    @Override
    public void channelRead(ChannelHandlerContext ctx, Message message) {
        Attribute<Boolean> attr = ctx.attr(auth);
        if (message instanceof LoginMessage) {
            authenticate((LoginMessage) o);
            attr.set(true)
        } else (message instanceof GetDataMessage) {
            if (Boolean.TRUE.equals(attr.get())) {
                ctx.writeAndFlush(fetchSecret((GetDataMessage) o));
            } else {
                fail();
            }
        }
    }
    ...
}

这样的话,你就可以对所有的连接的使用相同的ChannelHandler

public class DataServerInitializer extends ChannelInitializer<Channel> {
  
      private static final DataServerHandler SHARED = new DataServerHandler();
  
      @Override
      public void initChannel(Channel channel) {
          channel.pipeline().addLast("handler", SHARED);
      }
  }

@Sharable注解

ChannelHandler如果标注了@Sharable注解,则意味着它只需要被实例化一次,

你可以把这个实例加到多个ChannelPipeline中。

这个注解提供了一个说明功能。

ChannelPipeline

image.png ChannelHandler用于处理和拦截一个Channel上的输入事件和输出操作,而ChannelPipeline就是一个ChannelHandler的列表;

它是一种责任链模式:

  1. 用于赋予用户足够的自由去处理事件;
  2. 使得同一个ChannelPipeline的ChannelHandler可以进行交互;

ChannelPipeline的创建

每一个Channel都会有自己的ChannelPipeline,在一个Channel被创建的时候,属于它的ChannelPipeline会被自动创建;

事件如何在ChannelPipeline中流传

image.png 如上图所示,画出了一个Channel的事件是如何在ChannelPipeline中流转的,如果是输入方向的读操作会有ChannelInboundHandler处理,如果是输出方向的写操作会由ChannelOutboundHandler处理。

在输入方向上一个ChannelInboundHandler可以通过调用

ChannelHandlerContext#fireChannelRead(Object)

将事件转移到下一个ChannelInboundHandler;

相应地,在输出方向上一个ChannelOutboundHandler可以通过调用

ChannelHandlerContext#write(Object)

将事件转到下一个ChannelOutboundHandler;

在输入方向上,一个事件被处理的方向是自底向上的,ChannelInboundHandler通过是用于处理由i/o线程产生的输入数据的,输入数据则由一些实际的读操作产生,比如说:

SocketChannel#read(ByteBuffer)

如果一个输入事件被流转到了顶部的ChannelInboundHandler的之外,则这个输入事件会被丢弃;

在输出方向上,一个事件被处理的方向是自顶向下的,ChannelOutboundHandler通常被用于生产和转换输出数据,如果输出事件已经被流转到了底部的ChannelOutboundHandler之外了,那么

这个输出事件就会被i/o线程进行处理,通过调用一些实际的写操作去将数据输出到网络上,比如:

SocketChannel#write(ByteBuffer)

举个例子:

比如有这样一个ChannelPipeline:

ChannelPipeline} p = ...;
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());

如果是输入的话,那么则由于输入是自底向上处理的,那么则ChannelHandler的处理顺序是

1 2 5

如果是输出的话,那么由于输出是自顶向下处理的,那么ChannelHandler的处理顺序是

5 4 3

如何转移事件到下一个ChannelHandler

一个ChannelHandler必须调用事件传播方法把事件传播到下一个ChannelHandler,这些事件传播方法包括:

输入事件传播方法:

  1. ChannelHandlerContext#fireChannelRegistered()
    
  2. ChannelHandlerContext#fireChannelActive()
    
  3. ChannelHandlerContext#fireChannelRead(Object)
    
  4. ChannelHandlerContext#fireChannelReadComplete()
    
  5. ChannelHandlerContext#fireExceptionCaught(Throwable)
    
  6. ChannelHandlerContext#fireUserEventTriggered(Object)
    
  7. ChannelHandlerContext#fireChannelWritabilityChanged()
    
  8. ChannelHandlerContext#fireChannelInactive()
    
  9. ChannelHandlerContext#fireChannelUnregistered()
    

输出事件传播方法:

  1. ChannelHandlerContext#bind(SocketAddress, ChannelPromise)
    
  2. ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise)
    
  3. ChannelHandlerContext#write(Object, ChannelPromise)
    
  4. ChannelHandlerContext#flush()
    
  5. ChannelHandlerContext#read()
    
  6. ChannelHandlerContext#disconnect(ChannelPromise)
    
  7. ChannelHandlerContext#close(ChannelPromise)
    
  8. ChannelHandlerContext#deregister(ChannelPromise)
    

使用示例:

public class MyInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("Connected!");
        ctx.fireChannelActive();
    }
 }
public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
        System.out.println("Closing ..");
        ctx.close(promise);
    }
 }

如何构建一个ChannelPipeline

用户需要一个或一个以上的ChannelHandler去处理事件或者处理i/o操作;

一个典型的服务器通常需要这些处理器:

Protocol Decoder 编码器将二进制数据转化为java的对象

Protocol Encoder 解码器 将java对象转化为二进制数据

Business Logic Handler 业务逻辑处理器 进行实际业务逻辑的处理,比如访问数据库

具体编码:

static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
如果你需要在其他的线程而不是在i/o线程中跑业务逻辑处理器,以便i/o线程不会因为一个大量耗时的业务逻辑处理

而阻塞,那么你就需要像这样做:

pipeline.addLast(group, "handler", new MyBusinessLogicHandler());

如果业务逻辑操作是完全异步的后者身份会非常快速地执行完,那么也可以在i/o线程中去处理:

pipeline.addLast(group, "handler", new MyBusinessLogicHandler());

需要注意的是:

使用EventExecutorGroup 在对一个Channel的请求的处理是串行的,如果你可以不考虑顺序的,要提供性能你可以使用

UnorderedThreadPoolEventExecutor

线程安全

ChannelPipeline是线程安全的,因此你可以在任何时候去添加或者删除ChannelHandler。