博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《netty入门与实战》笔记-05:netty内置的channelHandler
阅读量:6119 次
发布时间:2019-06-21

本文共 7279 字,大约阅读时间需要 24 分钟。

  hot3.png

Netty 内置了很多开箱即用的 ChannelHandler。下面,我们通过学习 Netty 内置的 ChannelHandler 来逐步构建我们的 pipeline。

ChannelInboundHandlerAdapter 与 ChannelOutboundHandlerAdapter

首先是 ChannelInboundHandlerAdapter ,这个适配器主要用于实现其接口 ChannelInboundHandler 的所有方法,这样我们在编写自己的 handler 的时候就不需要实现 handler 里面的每一种方法,而只需要实现我们所关心的方法,默认情况下,对于 ChannelInboundHandlerAdapter,我们比较关心的是他的如下方法

ChannelInboundHandlerAdapter.java

@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {    ctx.fireChannelRead(msg);}

他的作用就是接收上一个 handler 的输出,这里的 msg 就是上一个 handler 的输出。大家也可以看到,默认情况下 adapter 会通过 fireChannelRead() 方法直接把上一个 handler 的输出结果传递到下一个 handler

ChannelInboundHandlerAdapter 类似的类是 ChannelOutboundHandlerAdapter,它的核心方法如下

ChannelOutboundHandlerAdapter.java

@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {    ctx.write(msg, promise);}

默认情况下,这个 adapter 也会把对象传递到下一个 outBound 节点,它的传播顺序与 inboundHandler 相反,这里就不再对这个类展开了。

我们往 pipeline 添加的第一个 handler 中的 channelRead 方法中,msg 对象其实就是 ByteBuf。服务端在接受到数据之后,应该首先要做的第一步逻辑就是把这个 ByteBuf 进行解码,然后把解码后的结果传递到下一个 handler,像这样

@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        ByteBuf requestByteBuf = (ByteBuf) msg;        // 解码        Packet packet = PacketCodeC.INSTANCE.decode(requestByteBuf);        // 解码后的对象传递到下一个 handler 处理        ctx.fireChannelRead(packet)}

不过在开始解码之前,我们来了解一下另外一个特殊的 handler

ByteToMessageDecoder

通常情况下,无论我们是在客户端还是服务端,当我们收到数据之后,首先要做的事情就是把二进制数据转换到我们的一个 Java 对象,所以 Netty 很贴心地写了一个父类,来专门做这个事情,下面我们来看一下,如何使用这个类来实现服务端的解码

public class PacketDecoder extends ByteToMessageDecoder {    @Override    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) {        out.add(PacketCodeC.INSTANCE.decode(in));    }}

当我们继承了 ByteToMessageDecoder 这个类之后,我们只需要实现一下 decode() 方法,这里的 in 大家可以看到,传递进来的时候就已经是 ByteBuf 类型,所以我们不再需要强转,第三个参数是 List 类型,我们通过往这个 List 里面添加解码后的结果对象,就可以自动实现结果往下一个 handler 进行传递,这样,我们就实现了解码的逻辑 handler

另外,值得注意的一点,对于 Netty 里面的 ByteBuf,我们使用 4.1.6.Final 版本,默认情况下用的是堆外内存,在 ByteBuf 这一小节中我们提到,堆外内存我们需要自行释放,在我们前面小节的解码的例子中,其实我们已经漏掉了这个操作,这一点是非常致命的,随着程序运行越来越久,内存泄露的问题就慢慢暴露出来了, 而这里我们使用 ByteToMessageDecoderNetty 会自动进行内存的释放,我们不用操心太多的内存管理方面的逻辑。

当我们通过解码工具把二进制数据转换到 Java 对象即指令数据包之后,就可以针对每一种指令数据包编写逻辑了。

SimpleChannelInboundHandler

回顾一下我们前面处理 Java 对象的逻辑

if (packet instanceof LoginRequestPacket) {    // ...} else if (packet instanceof MessageRequestPacket) {    // ...} else if ...

我们通过 if else 逻辑进行逻辑的处理,当我们要处理的指令越来越多的时候,代码会显得越来越臃肿,我们可以通过给 pipeline 添加多个 handler(ChannelInboundHandlerAdapter的子类) 来解决过多的 if else 问题,如下

XXXHandler.java

if (packet instanceof XXXPacket) {    // ...处理} else {   ctx.fireChannelRead(packet); }

这样一个好处就是,每次添加一个指令处理器,逻辑处理的框架都是一致的.

但是,大家应该也注意到了,这里我们编写指令处理 handler 的时候,依然编写了一段我们其实可以不用关心的 if else 判断,然后还要手动传递无法处理的对象 (XXXPacket) 至下一个指令处理器,这也是一段重复度极高的代码,因此,Netty 基于这种考虑抽象出了一个 SimpleChannelInboundHandler 对象,类型判断和对象传递的活都自动帮我们实现了,而我们可以专注于处理我们所关心的指令即可。

下面,我们来看一下如何使用 SimpleChannelInboundHandler 来简化我们的指令处理逻辑

LoginRequestHandler.java

public class LoginRequestHandler extends SimpleChannelInboundHandler
{ @Override protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) { // 登录逻辑 }}

SimpleChannelInboundHandler 从字面意思也可以看到,使用它非常简单,我们在继承这个类的时候,给他传递一个泛型参数,然后在 channelRead0() 方法里面,我们不用再通过 if 逻辑来判断当前对象是否是本 handler 可以处理的对象,也不用强转,不用往下传递本 handler 处理不了的对象,这一切都已经交给父类 SimpleChannelInboundHandler 来实现了,我们只需要专注于我们要处理的业务逻辑即可。

上面的 LoginRequestHandler 是用来处理登录的逻辑,同理,我们可以很轻松地编写一个消息处理逻辑处理器

MessageRequestHandler.java

public class MessageRequestHandler extends SimpleChannelInboundHandler
{ @Override protected void channelRead0(ChannelHandlerContext ctx, MessageRequestPacket messageRequestPacket) { }}

MessageToByteEncoder

在前面几个小节,我们已经实现了登录和消息处理逻辑,处理完请求之后,我们都会给客户端一个响应,在写响应之前,我们需要把响应对象编码成 ByteBuf,结合我们本小节的内容,最后的逻辑框架如下

public class LoginRequestHandler extends SimpleChannelInboundHandler
{ @Override protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) { LoginResponsePacket loginResponsePacket = login(loginRequestPacket); ByteBuf responseByteBuf = PacketCodeC.INSTANCE.encode(ctx.alloc(), loginResponsePacket); ctx.channel().writeAndFlush(responseByteBuf); }}public class MessageRequestHandler extends SimpleChannelInboundHandler
{ @Override protected void channelRead0(ChannelHandlerContext ctx, MessageRequestPacket messageRequestPacket) { MessageResponsePacket messageResponsePacket = receiveMessage(messageRequestPacket); ByteBuf responseByteBuf = PacketCodeC.INSTANCE.encode(ctx.alloc(), messageRequestPacket); ctx.channel().writeAndFlush(responseByteBuf); }}

我们注意到,我们处理每一种指令完成之后的逻辑是类似的,都需要进行编码,然后调用 writeAndFlush() 将数据写到客户端,这个编码的过程其实也是重复的逻辑,而且在编码的过程中,我们还需要手动去创建一个 ByteBuf,如下过程

PacketCodeC.java

public ByteBuf encode(ByteBufAllocator byteBufAllocator, Packet packet) {    // 1. 创建 ByteBuf 对象    ByteBuf byteBuf = byteBufAllocator.ioBuffer();    // 2. 序列化 java 对象    // 3. 实际编码过程    return byteBuf;}

而Netty 提供了一个特殊的 channelHandler 来专门处理编码逻辑,我们不需要每一次将响应写到对端的时候调用一次编码逻辑进行编码,也不需要自行创建 ByteBuf,这个类叫做 MessageToByteEncoder,从字面意思也可以看出,它的功能就是将对象转换到二进制数据。

下面,我们来看一下,我们如何来实现编码逻辑

public class PacketEncoder extends MessageToByteEncoder
{ @Override protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) { PacketCodeC.INSTANCE.encode(out, packet); }}

PacketEncoder 继承自 MessageToByteEncoder,泛型参数 Packet 表示这个类的作用是实现 Packet 类型对象到二进制的转换。

这里我们只需要实现 encode() 方法,我们注意到,在这个方法里面,第二个参数是 Java 对象,而第三个参数是 ByteBuf 对象,我们在这个方法里面要做的事情就是把 Java 对象里面的字段写到 ByteBuf,我们不再需要自行去分配 ByteBuf,因此,大家注意到,PacketCodeCencode() 方法的定义也改了,下面是更改前后的对比

PacketCodeC.java

// 更改前的定义public ByteBuf encode(ByteBufAllocator byteBufAllocator, Packet packet) {    // 1. 创建 ByteBuf 对象    ByteBuf byteBuf = byteBufAllocator.ioBuffer();    // 2. 序列化 java 对象    // 3. 实际编码过程    return byteBuf;}// 更改后的定义public void encode(ByteBuf byteBuf, Packet packet) {    // 1. 序列化 java 对象    // 2. 实际编码过程}

我们可以看到,PacketCodeC 不再需要手动创建对象,不再需要再把创建完的 ByteBuf 进行返回。当我们向 pipeline 中添加了这个编码器之后,我们在指令处理完毕之后就只需要 writeAndFlush java 对象即可,像这样

public class LoginRequestHandler extends SimpleChannelInboundHandler
{ @Override protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) { ctx.channel().writeAndFlush(login(loginRequestPacket)); }}public class MessageRequestHandler extends SimpleChannelInboundHandler
{ @Override protected void channelRead0(ChannelHandlerContext ctx, MessageResponsePacket messageRequestPacket) { ctx.channel().writeAndFlush(receiveMessage(messageRequestPacket)); }}

通过我们前面的分析,可以看到, Netty 为了让我们逻辑更为清晰简洁,帮我们做了很多工作,能直接用 Netty 自带的 handler 来解决的问题,不要重复造轮子。在接下里的小节,我们会继续探讨 Netty 还有哪些开箱即用的 handler

总结

本小节,我们通过学习 netty 内置的 channelHandler 来逐步构建我们的服务端 pipeline,通过内置的 channelHandler 可以减少很多重复逻辑。

  1. 基于 ByteToMessageDecoder,我们可以实现自定义解码,而不用关心 ByteBuf 的强转和 解码结果的传递。
  2. 基于 SimpleChannelInboundHandler,我们可以实现每一种指令的处理,不再需要强转,不再有冗长乏味的 if else 逻辑,不需要手动传递对象。
  3. 基于 MessageToByteEncoder,我们可以实现自定义编码,而不用关心 ByteBuf 的创建,不用每次向对端写 Java 对象都进行一次编码。

以上内容来源于掘金小册《Netty 入门与实战:仿写微信 IM 即时通讯系统》,若想获得更多,更详细的内容,请用微信扫码订阅:

转载于:https://my.oschina.net/funcy/blog/2250302

你可能感兴趣的文章
网易跟贴这么火,背后的某个力量不可忽视
查看>>
企业级java springboot b2bc商城系统开源源码二次开发-hystrix参数详解(八)
查看>>
java B2B2C 多租户电子商城系统- 整合企业架构的技术点
查看>>
IOC —— AOP
查看>>
比特币现金将出新招,推动比特币现金使用
查看>>
数据库的这些性能优化,你做了吗?
查看>>
某大型网站迁移总结(完结)
查看>>
mysql的innodb中事务日志(redo log)ib_logfile
查看>>
部署SSL证书后,网页内容造成页面错误提示的处理办法
查看>>
MS SQLSERVER通用存储过程分页
查看>>
60.使用Azure AI 自定义视觉服务实现物品识别Demo
查看>>
Oracle 冷备份
查看>>
jq漂亮实用的select,select选中后,显示对应内容
查看>>
C 函数sscanf()的用法
查看>>
python模块之hashlib: md5和sha算法
查看>>
linux系统安装的引导镜像制作流程分享
查看>>
解决ros建***能登录不能访问内网远程桌面的问题
查看>>
pfsense锁住自己
查看>>
vsftpd 相关总结
查看>>
bash complete -C command
查看>>