Netty - Netty编码器机制

Netty编码器机制

  • 编码和解码的基本介绍
    • 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o52Anr6n-1623035864972)(pic\Netty2\Netty编码解码.png)]
    • codec(编码器)的组成部分有两个:decoder(解码器)和encoder(编码器)。encoder负责把业务数据缓存字节码数据,decoder负责把字节码数据转换成业务数据
  • Netty本身的编码解码的机制和问题分析
    • Netty自身提供了一些codec(编解码器)
  • Netty提供的编码器
    • StringEncoder,对字符串数据进行编码
    • ObjectEncoder,对Java对象进行编码
  • Netty提供的解码器
    • StringDecoder,对字符串数据进行解码
    • ObjectDeocoder,对Java对象进行编码
  • Netty本身自带的ObjectDecoder和ObjectEncoder可以用实现POJO对象或业务对象的编码和解码,底层使用的仍然是Java序列化计数,而Java序列化技术本身效率就不高,存在如下问题
    • 无法跨语言
    • 序列化后体积太大,是二进制编码的5倍多
    • 序列化性能太低
    • 解决方法,Google的ProtoBuf
ProtoBuf机制
  • ProtoBuf基本介绍和使用示意图

    • ProtoBuf是Google发布的开源项目,全称Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化。或者说序列化,它很适合做数据存储或RPC(remote procedure call)【远程过程调用】数据交换格式

    • Protobuf是以message的方式管理数据的

    • 支持跨平台,跨语言,即【客户端和服务器端可以是不同的语言编写的】(支持目前绝大多数语言,例如C++,C#,Java,Python等)

    • 高性能,高可靠性

    • 使用protobuf编译期能自动生成代码,Protobu是将类的定义使用.proto文件进行描述。说明,在idea中编写.proto文件时,会自动提示下载.proto编写插件,可以让语法高亮

    • 然后通过proto.exe编译期根据.proto自动生成.java文件

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QF4akQrL-1623035864975)(pic\Netty2\Protobuf编码解码.png)]

    • Netty中ProtoBuf步骤

      • 在Maven项目引入Protobuf坐标,下载相关jar包

      • <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.6.1</version>
        </dependency>
        
      • 编写proto文件Student.proto

      • 使用proto.exe编译Student.proto文件,生成相应的StudentPOJO.java类

ProtoBuf实例
  • 要求

    • 客户端发送一个Student Pojo对象到服务器(通过ProtoBuf编码)
    • 服务端能接受Student POJO对象,并显示信息(通过ProtoBuf解码)
  • 代码

    • Student.proto

      • syntax="proto3";//版本
        option java_outer_classname = "StudentPOJO";
        
        message Student{
          int32 id =1 ;
          string name = 2;
        }
        
    • StudentPOJO.java

    • MyNettyClientHandler

      • package com.jl.java.web.codec;
        
        import io.netty.buffer.ByteBuf;
        import io.netty.buffer.Unpooled;
        import io.netty.channel.ChannelFuture;
        import io.netty.channel.ChannelHandlerContext;
        import io.netty.channel.ChannelInboundHandlerAdapter;
        import io.netty.util.CharsetUtil;
        
        /**
         * @author jiangl
         * @version 1.0
         * @date 2021/5/23 10:57
         */
        public class MyNettyClientHandler extends ChannelInboundHandlerAdapter {
        
            /**
             * 当通道就绪时就会触发该方法
             * @param ctx
             * @throws Exception
             */
            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                //发送一个Student对象到服务器
                StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(1).setName("dsfadfa").build();
        
                ctx.writeAndFlush(student);
            }
        
            /**
             * 当通道有读取事件时,会触发
             * @param ctx
             * @param msg
             * @throws Exception
             */
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                System.out.println("服务器回复的消息:"+buf.toString(CharsetUtil.UTF_8));
                System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
            }
        
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                cause.printStackTrace();
                ctx.close();
            }
        }
        
    • MyNettyServerHandler

      • public class MyNettyServerHandler2 extends SimpleChannelInboundHandler<StudentPOJO.Student> {
        
        
            /**
             * 读取数据实际(可以读取客户端发送的消息)
             * @param ctx 上下文对象,含有管道pipeline ,通道channel
             * @param msg 客户端发送的数据,默认Object类型
             * @throws Exception
             */
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception {
                System.out.println("客户端发送的数据:id="+msg.getId()+",name="+msg.getName());
            }
        }
        
Netty编解码器和handler调用机制
  • 基本说明

    • netty的组件设计:Netty的主要组件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipline等
    • ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),开发者可以接受入站事件和数据,这些数据会被业务逻辑处理。当要给客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。业务逻辑通常写在一个或者多个ChannelInboundHandler中。ChannelOutboundHandler原理一样,只不过它是用来处理出站数据的
    • ChannelPipeline提供了ChannelHandler链的容器,以客户端应用程序为例,如果事件的运动方法是从客户端到服务器端的,则称这些事件为出站,即客户端发送给故武器的数据会通过pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理,反之则称为入站
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UgP3VAcn-1623035864976)(pic\Netty2\ChannelPipeline.png)]
      • 入站,一定是读取管道中的数据进行解码
      • 出站,一定是把编码后的数据写入管道中
    • socket和channel通道的相对位置来说明出站和入站
      • 数据从channel通道流向socket内存就是入站
      • 数据从socket内存流向channel通道就是出站
    • 以服务器和客户端为例
      • 服务器接收消息就是入站
      • 服务器发送消息就是出站
      • 客户端接收消息就是入站
      • 客户端发送消息就是出站
  • 编码解码器

    • 当Netty发送或者接收一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节码转换为另一种格式(比如Java对象);如果是出站消息,就会被编码成字节码

    • Netty提供了一系列实用的编解码器,他们都实现了ChannelInboundHandler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站(ChannelPipeline中的ChannelInboundHandler)为例,对于每个从入站Channel读取的消息,channelRead方法都会被调用。随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。

    • 解码器ByteToMessageDecoder

      • 关系图
        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XKSCxcl5-1623035864978)(D:\personal\文档\文档\pic\Netty2\ByteToMessageDecoder.png)]
      • 由于不可能直到远程节点是否会一次性发送一个完整的信息,tcp有可能出现粘包和拆包的问题,ByteToMessageDecoder会对入站数据进行缓冲,直到它准备好被处理。
    • 一个关于ByteToMessageDecoder实例分析

      • package com.jl.java.web.codec;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.ByteToMessageDecoder;import java.util.List;/** * @author jiangl * @version 1.0 * @date 2021/5/27 14:08 */public class ToIntegerDecoder extends ByteToMessageDecoder {    @Override    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {        if(in.readableBytes() >=4){            out.add(in.readInt());        }    }}
        
      • 说明

        • 这个例子,每次入站从byteBuf中读取4字节,将其解码为一个int,然后将它添加到下一个list中。当没有更多元素可以添加到该list中时,它的内容将会被发送给下一个ChannelInboundHandler。int在被添加到List中时,会被自动装箱成Integer。在调用readInt()方法前必须验证所输入的ByteBuf是否具有足够的数据
Netty的Handler调用机制实例
  • 案例要求

    • 使用自定义的编码器和解码器来说明Netty的handler调用机制

      • 客户端发送long(8个字节)->服务器
      • 服务器发送long(8个字节) ->客户端
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Pvn3uA6-1623035864980)(pic\Netty2\Handler调用机制.png)]
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvI0ulWJ-1623035864982)(pic\Netty2\客户端服务端Handler过程.png)]
    • 代码

      • InBoundServerDecoder

        • package com.jl.java.web.inoutbound;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.handler.codec.ByteToMessageDecoder;import java.util.List;/** * 服务端入站解码器 * @author jiangl * @version 1.0 * @date 2021/5/27 14:47 */public class InBoundServerDecoder extends ByteToMessageDecoder {    /**     * decode 会根据接收的数据,被调用多次,直到确定没有新的元素被添加list,     * 或者ByteBuf没有更多的可读字节为止     * 如果list out不为空,就会得到list的内容传递给下一个     * ChannelInboundHandler处理,下一个ChannelInboundHandler的方法也会被调用多次,这次数和前一个一样     * @param ctx 上下文对象     * @param in 入站的Bytebuf     * @param out list集合,将解码后的数据传给下一个handler     * @throws Exception     */    @Override    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {        System.out.println("InBoundServerDecoder decode被调用");        //因为long 8个字节,需要判断有8个字节,才能读取一个long        if(in.readableBytes() >= 8){            out.add(in.readLong());        }    }}
          
      • OutBoundClientEncoder

        • package com.jl.java.web.inoutbound;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.ByteToMessageDecoder;import io.netty.handler.codec.MessageToByteEncoder;import java.util.List;/** * @author jiangl * @version 1.0 * @date 2021/5/27 15:07 */public class OutBoundClientEncoder extends MessageToByteEncoder<Long> {    @Override    protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {        System.out.println("OutBoundClientEncoder encode被调用");        System.out.println("客户端准备发送 msg="+msg);        out.writeLong(msg);    }}
          
    • 结论

      • 不论解码器handler还是编码器handler,其接收的消息类型必须与待处理的消息类型一致,否则这个handler不会被执行
        • 因为在编解码器的Handler中,会判断自己可以处理的数据类型和传入的数据类型是否一致,如果一致则这个handler会调用自己的逻辑处理数据,如果不一致则直接将数据传入给下一个handler处理数据
        • 在解码器进行数据解码时,需要判断缓存(Bytebuf)的数据是否足够,否则接收到的结果会和期望的结果不一致
解码器-ReplayingDecoder
  • public abstract class ReplayingDecoder extends ByteToMessageDecoder

  • ReplayingDecoder扩展了ByteToMessageDecoder类,使用ReplayingDecoder,开发者不必调用readableBytes()方法。参数S指定了用户状态管理的类型,其中void代表不需要状态管理

  • 应用实例

    • 使用ReplayingDecoder编写解码器,对前面的案例进行简化

    • 代码

      • package com.jl.java.web.inoutbound;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.ReplayingDecoder;import java.util.List;/** * @author jiangl * @version 1.0 * @date 2021/5/27 21:21 */public class MyByteToLongDecoder extends ReplayingDecoder<Void> {    @Override    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {        System.out.println("MyByteToLongDecoder 被调用");        //在ReplayingDecoder不需要判断数据是否足够读取,内部会处理判断        out.add(in.readLong());    }}
        
  • ReplayingDecoder使用方便,但是有局限性

    • 并不是所有ByteBuf操作都被支持,如果调用了一个不被支持的方法,将会抛出一个UnsupportedOperationException。
    • ReplayingDecoder在某些情况下可能稍慢于ByteToMessageDecoder,例如而网络缓慢并且消息格式复杂时,消息会被拆成了多个碎片,速度变慢
其他编码器
  • 其他编码器
    • LineBaseFrameDecoder:这个类在Netty内部也有使用,它使用行尾控制字符(\n或者\r\n)作为分隔符来解析数据
    • DelimiterBasedFrameDecoder:使用自定义的特殊字符作为消息的分隔符
    • HttpObjectDecoder:一个HTTP数据的解码器
    • LengthFieldBasedFrameDecoder:通过制定长度来标识整包消息,这样就可以自动的处理黏包和半包消息
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值