Netty 编码和解码

1. 前言

本节内容,主要是讲解 Netty 的编码和解码,前面我们讲解了 ByteBuf,Netty 是面向 ByteBuf 来编程的,发送的内容会被编码成 ByteBuf,从 Channel 接受的数据流则被封装成了 ByteBuf,需要把它解码成我们所熟悉的格式。

2. 编码和解码的作用

首先,我们先通过一个实例来进行说明。

实例:

ch.pipeline().addLast(new CodecHandler());

public class CodecHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("接受:"+msg.toString());
    }
}

客户端发送数据:
图片描述

执行结果:

接受:PooledUnsafeDirectByteBuf(ridx: 0, widx: 5, cap: 1024)

通过以上测试,发现客户端往服务端发送普通的字符串,服务端接受的时候并不是正常字符串,而是把 ByteBuf 类型打印出来。

主要原因是,Netty 的数据类型是 ByteBuf,无法直接强转,需要通过解码的方式去转换才能得到正常的数据,编码也是同样道理。

因此,本节学编码和解码的知识可以了解 Netty 如何去接受和发送参数。

3. 解码示例

实例:

public class ServerLoginHandler extends ChannelInboundHandlerAdapter {
    //1.读取客户端发送过来的数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //1.转换ByteBuf
        ByteBuf buffer=(ByteBuf)msg;
        //2.定义一个byte数组,长度是ByteBuf的可读字节数
        byte[] bytes=new byte[buffer.readableBytes()];
        //3.往自定义的byte[]读取数据
        buffer.readBytes(bytes);

        //4.字节流->字符串
       	String str=new String(bytes);     
    }
}

通过以上代码,我们发现能正常接收并且打印客户端发送过来的字符串数据。但是如果是其它的类型数据(比如:Map,实体,List 等)那么还得手工写另外的转换方法,相对比较麻烦。

4. 编码解码流程

4.1 整体流程

无论是使用 Netty 还是原始的 Socket 编程,基于 TCP 通信的数据包格式均为二进制,但是我们平时开发不可能基于二进制去开发,而是封装一个一个的实体。这样的话,我们就需要实现实体和二进制之间的编码和解码了。

  1. 客户端往服务端发送消息,手写需要把实体转换成 byte [],并且把 byte [] 写入到 ByteBuf 容器里面,最终转换二进制。其实,整个过程就是一个编码的过程;
  2. 服务端接受到消息,二进制是给机器去识别的,人眼无法快速去识别它,然而实体是我们所熟悉并且一看就能看出有哪些属性,因此需要把二进制转换我们所熟悉的实体,整个过程就是一个解码的过程。

图片描述

4.2 编码流程

实例:

//封装编码方法
public ByteBuf encode(Object obj) {
    // 1. 创建 ByteBuf 对象
    ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();
    // 2. 序列化 Java 对象
    byte[] bytes = SerializeUtils.serialize(obj);
    // 3. 实际编码过程
    byteBuf.writeBytes(bytes);
    return byteBuf;
}

//序列化工具类
public class SerializeUtils{
    //序列化方法
    public static byte[] serialize(Object obj){
        //省略序列化过程
        
        return null;
    }
}

代码说明:

  1. 创建一个 ByteBuf(前面章节详细讲解过);
  2. 把内容序列化成字节数组;
  3. 把字节数组写入到 ByteBuf。

4.3 解码流程

实例:

//解码
public <T> T decode(ByteBuf byteBuf,Class clazz) {
    // 数据包长度
    int length = byteBuf.readableBytes();

    byte[] bytes = new byte[length];
    byteBuf.readBytes(bytes);

    return SerializeUtils.desrialize(bytes,clazz);
}

//序列化工具类
public class SerializeUtils{
    //序列化方法
    public static <T> T desrialize(byte[] bytes,Class clazz){
        //省略反序列化过程
        
        return null;
    }
}

代码说明:

  1. 根据 ByteBuf 获取可读的数据长度;
  2. 根据数据长度创建相应的字节数组;
  3. 把 ByteBuf 里面的内容读取到自定义的字节数组里面;
  4. 通过反序列化的手段,把字节数组反序列化成对象。

5. 序列化和反序列化

上面讲编码和解码的时候,涉及两个空方法没有实现,分别是 serialize() 序列化和 desrialize() 反序列化,其实序列化和反序列化技术选择很多,常见的解决方案大概如下:

  1. 通过对象流来手工实现序列化,但是实体必须实现 Serializeable 序列化接口,否则无法被正常序列化和反序列化;
  2. 对象 -> 转换 json 格式的字符串,Java 里面 String 类型字符串可以自动转换字节数组,常见的开源框架分别有 Fastjson、Jackjson 等;
  3. 对象 - 转存 xml 格式的字符串,常见框架有 XStream 等;
  4. 其他技术,如:Hessian 序列化、Kryo 序列化等。

这里就不详细展开展示序列化和反序列化的说明,如果有兴趣,可以参考我写的另外一篇文章:

接下来,主要说明的是,为了灵活扩展,我们最好不要写死某种序列化技术,为了方便后期更改技术框架,因为每种序列化技术的差距比较大,主要体现两点:

  1. 消耗时间: 序列化和反序列化的消耗时间长度;
  2. 数据长度: 序列化过后的字节数组长度,这个是会影响网络传输性能的。

一般情况下,通过面向接口 + 策略模式的方式去解耦,底层可以灵活的切换序列化技术。

实例:

//定义一个序列化接口
public interface SerializeService<T>{
    //序列化方法
    public byte[] serialize(T t);
    //反序列化方法
    public T deserialize(byte[] bytes,Class<T> clazz);
}

//具体序列化实现列
public class JsonSerializeService<T> implements SerializeService<T>{
    //序列化方法
    public byte[] serialize(T t){
        return null;
    }
    //反序列化方法
    public T deserialize(byte[] bytes,Class<T> clazz){
        return null;
    }
}
//序列化使用
@Component
public class Test{
    @Autowired
    private SerializeService serializeService;
    
    public ByteBuf encode(Object obj) {
        // 1. 创建 ByteBuf 对象
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();
        // 2. 序列化 Java 对象
        byte[] bytes = serializeService.serialize(obj);
        // 3. 实际编码过程
        byteBuf.writeBytes(bytes);
        return byteBuf;
    }
}

6. 小结

本节内容大家掌握好以下内容:

  1. 编码和解码的概念是什么?为什么需要编码和解码?
  2. Netty 如何去进行编码和解码,以及大体流程是什么?
  3. 编码和解码需要依赖序列化和反序列化技术,要了解序列化方面的技术有哪些。

思考题:能否把我们的编码和解码封装成独立的 Handler 呢?那么应该如何去封装呢?