ChannelHandler
ChannelHandler用于处理i/o事件或者拦截i/o操作;
ChannelHandler本身不提供很多的方法, 所以通常你必须去实现它的子类
- ChannelInboundHandler 用于处理输入方向的i/o事件
- ChannelOutboundHandler 用于处理输出的i/o操作
或者,为了给你提供便利,也提供了一下的适配器类供你使用:
- ChannelInboundHandlerAdapter 用于处理输入方向的 i/o 事件
- ChannelOutboundHandlerAdapter 用于处理输出方向的i/o操作
- ChannelDuplexHandler 用于处理输入和输出两个方向的i/o事件
ChannelHandlerContext
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
ChannelHandler用于处理和拦截一个Channel上的输入事件和输出操作,而ChannelPipeline就是一个ChannelHandler的列表;
它是一种责任链模式:
- 用于赋予用户足够的自由去处理事件;
- 使得同一个ChannelPipeline的ChannelHandler可以进行交互;
ChannelPipeline的创建
每一个Channel都会有自己的ChannelPipeline,在一个Channel被创建的时候,属于它的ChannelPipeline会被自动创建;
事件如何在ChannelPipeline中流传
如上图所示,画出了一个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,这些事件传播方法包括:
输入事件传播方法:
-
ChannelHandlerContext#fireChannelRegistered() -
ChannelHandlerContext#fireChannelActive() -
ChannelHandlerContext#fireChannelRead(Object) -
ChannelHandlerContext#fireChannelReadComplete() -
ChannelHandlerContext#fireExceptionCaught(Throwable) -
ChannelHandlerContext#fireUserEventTriggered(Object) -
ChannelHandlerContext#fireChannelWritabilityChanged() -
ChannelHandlerContext#fireChannelInactive() -
ChannelHandlerContext#fireChannelUnregistered()
输出事件传播方法:
-
ChannelHandlerContext#bind(SocketAddress, ChannelPromise) -
ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise) -
ChannelHandlerContext#write(Object, ChannelPromise) -
ChannelHandlerContext#flush() -
ChannelHandlerContext#read() -
ChannelHandlerContext#disconnect(ChannelPromise) -
ChannelHandlerContext#close(ChannelPromise) -
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。