ChannelHandler实现
-
前面一篇文章介绍了ChannelHandler接口的继承体系,本文选择几个典型的ChannelHandler看一看源码
-
下面是继承体系图,选取的的最底部的实现类,有些是抽象类
MessageToByteEncoder:编码消息( Message -> ByteBuf )
ByteToMessageDecoder:解码消息( ByteBuf -> Message )
一、SimpleChannelInboundHandler
- 我们分析一下抽象类SimpleChannelInboundHandler,看是如何使用ChannelHandler。
- SimpleChannelInboundHandler是抽象类,是可以处理指定类型消息的处理器,我们可以实现SimpleChannelInboundHandler后,实现对指定类型的消息的自定义处理。
/**
* {@link ChannelInboundHandlerAdapter} which allows to explicit only handle a specific type of messages.
* 只允许处理指定类型的消息,下面是一个简单的示例实现,只处理String类型的消息:
* For example here is an implementation which only handle {@link String} messages.
* <pre>
* public class StringHandler extends {@link SimpleChannelInboundHandler}<{@link String}> {
*
* {@code @Override}
* protected void channelRead0({@link ChannelHandlerContext} ctx, {@link String} message)
* throws {@link Exception} {
* System.out.println(message);
* }
* }
* </pre>
*
* SimpleChannelInboundHandler可以处理指定类型的消息。可以实现SimpleChannelInboundHandler来对指定类型的消息的自定义处理。
*/
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {
/**
* 类型匹配器对象
*/
private final TypeParameterMatcher matcher;
/**
* 使用完消息,是否自动释放,如果自动释放,则会调用ReferenceCountUtil.release(msg)释放引用计数
*/
private final boolean autoRelease;
/**
* 构造方法
*/
protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType, boolean autoRelease) {
matcher = TypeParameterMatcher.get(inboundMessageType);
this.autoRelease = autoRelease;
}
/**
* 返回true表示消息应该被处理
* 返回false,消息会被传递给ChannelPipeline中的下一个ChannelInboundHandler
*/
public boolean acceptInboundMessage(Object msg) {
return matcher.match(msg);
}
/**
* 实现好的骨架方法,子类实现channelRead0即可
* 方法内部已经将资源的释放实现好了,子类在channelRead0中不需要释放资源
* */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//1.是否要释放消息
boolean release = true;
try {
//2.判断是否为匹配的消息
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
//3.处理消息
channelRead0(ctx, imsg);
} else {
//4.不需要释放消息,因为消息都不需要处理
release = false;
//5.触发Channel Read到下一个节点,由下一个节点处理消息
ctx.fireChannelRead(msg);
}
} finally {
//6.判断,是否要释放消息,两个标志都为true时,才释放
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
/**
* 需要子类实现的抽象方法,在接收到数据时被调用
*/
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
}
- 下面是一个使用的例子
/**
* @author by mozping
* Sharable标识这类的实例之间可以在 channel 里面共享
*/
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* channel活跃之后执行
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
//1.当被通知该 channel 是活动的时候就发送信息
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
}
/**
* 在接收到数据时被调用
*/
@Override
public void channelRead0(ChannelHandlerContext ctx,ByteBuf in) {
System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
}
/**
* 异常时候执行
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
//1.记录日志错误并关闭 channel
cause.printStackTrace();
ctx.close();
}
}
- 我们可以按照自己的想法重写对应的方法,继承SimpleChannelInboundHandler后只有channelRead0方法是必须重写的,通过参数来指定处理消息的类型。
二、MessageToByteEncoder
- MessageToByteEncoder 是一个出站处理器,将消息进行编码,预留了 encode 方法交给子类重写,主处理流程已经 在MessageToByteEncoder 中实现好了。
/**
* {@link ChannelOutboundHandlerAdapter} which encodes message in a stream-like fashion from one message to an
* {@link ByteBuf}.
* ChannelOutboundHandlerAdapter 将消息编码为ByteBuf,下面是实现的示例,将 Integer 编码为ByteBuf
* 下面是实现的示例,将Integer编码为ByteBuf
* Example implementation which encodes {@link Integer}s to a {@link ByteBuf}.
*
* <pre>
* public class IntegerEncoder extends {@link MessageToByteEncoder}<{@link Integer}> {
* {@code @Override}
* public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link ByteBuf} out)
* throws {@link Exception} {
* out.writeInt(msg);
* }
* }
* </pre>
*/
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {
/**
* 类型匹配器
*/
private final TypeParameterMatcher matcher;
/**
* 是否偏向使用Direct直接内存
*/
private final boolean preferDirect;
/**
* 构造方法
*/
protected MessageToByteEncoder() {
this(true);
}
/**
* 构造方法
*/
protected MessageToByteEncoder(boolean preferDirect) {
// <1> 获得 matcher
matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
this.preferDirect = preferDirect;
}
/**
* 返回true表示消息需要被处理
* 返回false就会将消息传递给下一个ChannelOutboundHandler处理
*/
public boolean acceptOutboundMessage(Object msg) throws Exception {
return matcher.match(msg);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
//1.判断是否为匹配的消息,匹配才需要处理
if (acceptOutboundMessage(msg)) {
@SuppressWarnings("unchecked")
I cast = (I) msg;
//2.分配buf
buf = allocateBuffer(ctx, cast, preferDirect);
try {
//3.编码,子类实现
encode(ctx, cast, buf);
} finally {
//4.释放msg
ReferenceCountUtil.release(cast);
}
//5.buf可读,说明有编码到数据
if (buf.isReadable()) {
//6.写入buf到下一个节点
ctx.write(buf, promise);
} else {
//7.反之就释放buf
buf.release();
//8.写入EMPTY_BUFFER到下一个节点,为了promise的回调
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
//9.置空buf
buf = null;
} else {
//10.不匹配,就提交write事件,交给下一个节点处理
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
} finally {
//11.释放buf
if (buf != null) {
buf.release();
}
}
}
/**
*分配一个ByteBuf,作为encode 方法的参数,子类可以重写该方法来返回一个ByteBuf
*/
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg, boolean preferDirect) throws Exception {
if (preferDirect) {
return ctx.alloc().ioBuffer();
} else {
return ctx.alloc().heapBuffer();
}
}
/**
* 子类重写的方法,将message消息编码成ByteBuf
*/
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
protected boolean isPreferDirect() {
return preferDirect;
}
}
-
看到其实 MessageToByteEncoder 的代码模式和 SimpleChannelInboundHandler 类似,都将方法主体实现好,预留一个方法给子类实现,通过这种模板设计让具体的子类实现起来就更加简单,比如看一个 MessageToByteEncoder的实现 NumberEncoder,它将数字编码为ByteBuf
-
NumberEncoder:NumberEncoder 将数字编码为ByteBuf ,代码十分简单,只需要重写code 方法即可
/**
* 将数字编码为ByteBuf,并写到对应的ByteBuf里面,
* 这里回将数字编码为对应的二进制编码,
* Encodes a {@link Number} into the binary representation prepended with
* a magic number ('F' or 0x46) and a 32-bit length prefix. For example, 42
* will be encoded to { 'F', 0, 0, 0, 1, 42 }.
*/
public class NumberEncoder extends MessageToByteEncoder<Number> {
@Override
protected void encode(ChannelHandlerContext ctx, Number msg, ByteBuf out) {
// 转化成 BigInteger 对象
// Convert to a BigInteger first for easier implementation.
BigInteger v;
if (msg instanceof BigInteger) {
v = (BigInteger) msg;
} else {
v = new BigInteger(String.valueOf(msg));
}
// 转换为字节数组
// Convert the number into a byte array.
byte[] data = v.toByteArray();
int dataLength = data.length;
// Write a message.
out.writeByte((byte) 'F'); // magic number,魔数开头
out.writeInt(dataLength); // data length,数字长度
out.writeBytes(data); // data
}
}
三、LoggingHandler
- LoggingHandler 是双向处理器 ,用于打印日志,在指定事件触发的时候,LoggingHandler中对应的回调方法会被调用,这些回调方法就会打印日志。
- 下面是类声明和构造方法,有很多重载的构造方法,这里只给出默认的构造方法,默认是DEBUG 日志级别
public class LoggingHandler extends ChannelDuplexHandler {
public LoggingHandler() {
this(DEFAULT_LEVEL);
}
public LoggingHandler(LogLevel level) {
if (level == null) {
throw new NullPointerException("level");
}
// 获得 logger
logger = InternalLoggerFactory.getInstance(getClass());
this.level = level;
internalLevel = level.toInternalLevel();
}
}
- 当事件发生时,就会打印日志,LoggingHandler就是在对应事件的回调方法里面打印日志的
/**
* 注册成功后回调打印日志
*/
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
if (logger.isEnabled(internalLevel)) {
logger.log(internalLevel, format(ctx, "REGISTERED"));
}
//调用下一个Handler传递事件
ctx.fireChannelRegistered();
}
/**
* 注销成功后回调打印日志
*/
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
if (logger.isEnabled(internalLevel)) {
logger.log(internalLevel, format(ctx, "UNREGISTERED"));
}
//调用下一个Handler传递事件
ctx.fireChannelUnregistered();
}
/**
* 连接活跃后回调打印日志
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 打印日志
if (logger.isEnabled(internalLevel)) {
logger.log(internalLevel, format(ctx, "ACTIVE"));
}
//调用下一个Handler传递事件
ctx.fireChannelActive();
}
/**
* 不活跃后回调打印日志
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (logger.isEnabled(internalLevel)) {
logger.log(internalLevel, format(ctx, "INACTIVE"));
}
//调用下一个Handler传递事件
ctx.fireChannelInactive();
}
/**
* 绑定后回调打印日志
*/
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
if (logger.isEnabled(internalLevel)) {
logger.log(internalLevel, format(ctx, "BIND", localAddress));
}
//调用下一个Handler传递事件
ctx.bind(localAddress, promise);
}
/**
* 读事件产生后回调打印日志
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (logger.isEnabled(internalLevel)) {
logger.log(internalLevel, format(ctx, "READ", msg));
}
//调用下一个Handler传递事件
ctx.fireChannelRead(msg);
}
/**
* 写事件产生后回调打印日志
*/
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (logger.isEnabled(internalLevel)) {
logger.log(internalLevel, format(ctx, "WRITE", msg));
}
//调用下一个Handler传递事件
ctx.write(msg, promise);
}
- 从代码可以看出,针对所有的事件回调方法,LoggingHandler 需要做的只是打印对应的日志,然后将事件传递给下一个处理器,通过内部的format 方法格式化日志,这个比较繁琐也不是特别重要,就不贴代码了
- 下面是使用示例,很简单只要在 ChannelPipeline 添加即可
new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast("logging",new LoggingHandler(LogLevel.INFO));
ch.pipeline().addLast(new EchoClientHandler());
}
}
- 打印的日志示例如下:
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] REGISTERED
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] ACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] READ: 12B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21 |Netty rocks! |
+--------+-------------------------------------------------+----------------+
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] WRITE: 12B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21 |Netty rocks! |
+--------+-------------------------------------------------+----------------+
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] READ COMPLETE
Server received: Netty rocks!
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] WRITE: 0B
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] FLUSH
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] CLOSE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 ! R:/127.0.0.1:63773] INACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 ! R:/127.0.0.1:63773] UNREGISTERED
四、小结
- 给出了几种常见Handler的代码简单分析,从中看出,对应 ChannelHandler 的编码实现主要是重写对应的回调方法,弄清楚数据的走向在指定的回调方法做对应的逻辑处理即可。