08-netty基础-自定义序列化和反序列化

 netty系列文章:

01-netty基础-socket
02-netty基础-java四种IO模型
03-netty基础-多路复用select、poll、epoll
04-netty基础-Reactor三种模型
05-netty基础-ByteBuf数据结构
06-netty基础-编码解码
07-netty基础-自定义编解码器
08-netty基础-自定义序列化和反序列化
09-netty基础-手写rpc-原理-01
10-netty基础-手写rpc-定义协议头-02
11-netty基础-手写rpc-支持多序列化协议-03
12-netty基础-手写rpc-编解码-04
13-netty基础-手写rpc-消费方生成代理-05
14-netty基础-手写rpc-提供方(服务端)-06

1 背景

07-netty基础-自定义编解码器的基础上增加序列化和反序列化工厂,支持java、JSON形式的序列化和反序列化;核心思想就是想透过这种简单实现,了解其他框架的实现原理

2 依赖包

 <dependencies>
        <dependency>
            <groupId>org.bonnie</groupId>
            <artifactId>netty-message-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
    </dependencies>

3 核心代码

3.1 序列化工厂设计

3.1.1 定义序列化和反序列化的接口

Serializer接口

package com.bonnie.netty.common.serializer;

import java.io.IOException;

public interface Serializer {

    /**
     * 反序列化:一个是要反序列化的对象类型,还一个是反序列化的字节数组
     * @param clazz
     * @param bytes
     * @return
     * @param <T>
     * @throws IOException
     */
    <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException;

    /**
     * 序列化:需要的参数就是序列化的对象
     * @param object
     * @return
     * @param <T>
     * @throws IOException
     */
    <T> byte[] serialize(T object) throws IOException;


}

3.1.2 JAVA实现

package com.bonnie.netty.common.serializer;

import java.io.*;

/**
 * java序列化
 */
public class JavaSerializer implements Serializer {

    @Override
    public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
            return (T) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    @Override
    public <T> byte[] serialize(T object) throws IOException {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(object);
            return bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

3.1.3 JSON实现

package com.bonnie.netty.common.serializer;

import com.alibaba.fastjson.JSON;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class JsonSerializer implements Serializer {

    @Override
    public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
        String json = new String(bytes, StandardCharsets.UTF_8);
        return (T) JSON.parseObject(json, clazz);
    }

    @Override
    public <T> byte[] serialize(T object) throws IOException {
        return JSON.toJSONString(object).getBytes(StandardCharsets.UTF_8);
    }

}

3.1.4 简单工厂-按策略走对应的序列化方式(重点)

package com.bonnie.netty.common.serializer;

import com.bonnie.netty.common.enums.SerializableTypeEnum;

import java.io.IOException;

public class SerializerUtil {

    public static <T> T deserializer(Class<T> clazz, byte[] bytes, byte serializerType) throws IOException {
        SerializableTypeEnum serializableTypeEnum = SerializableTypeEnum.valueOf(serializerType);
        switch (serializableTypeEnum) {
            case JAVA:
                return new JavaSerializer().deserialize(clazz, bytes);
            case JSON:
                return new JsonSerializer().deserialize(clazz, bytes);
            default:
                break;
        }
        return null;
    }

    public static <T> byte[] serializer(T object, byte serializerType) throws IOException {
        SerializableTypeEnum serializableTypeEnum = SerializableTypeEnum.valueOf(serializerType);
        switch (serializableTypeEnum) {
            case JAVA:
                return new JavaSerializer().serialize(object);
            case JSON:
                return new JsonSerializer().serialize(object);
            default:
                break;
        }
        return null;
    }

}

3.2 序列化方式的传输方式 

 HeaderData中包了协议序列化方式(serializableType),在编码的时候可以通过msg.getHeader().getHeaderData().getSerializableType()方式轻松的拿到,然后进行相应方式的编解码;但是在解码的时候,需要先获取到序列化方式(serializableType),才能拿到,因为序列化方式(serializableType)是隐藏在HeaderData中,进而导致无法获取到serializableType。

解决方式一:headerData可以按照每个字段传输(固定形式,获取跟serializableType是没有关系的),这样获取的时候就不需要知道编解码
解决方式二:将serializableType放在headerLength的第一位即可

以下采用的是方式二:

3.2.1 headerLength的第一位是serializableType

  /**
     * @param headerLength:头的长度
     * @param type:序列化方式
     * @return
     */
    public static byte[] markProtocolType(int headerLength,byte type){

        byte[] result = new byte[4];
        // 第一个8位放了序列化的方式
        result[0] = type;

        // 第二部分开始就放头的长度,把头的长度的第二个字节放好了
        result[1] = (byte) ((headerLength >> 16) & 0xFF);

        // 第三个字节放头的第三个字节
        result[2] = (byte) ((headerLength >> 8) & 0xFF);

        // 第四个字节放头的第四个字节
        result[3] = (byte) (headerLength & 0xFF);

        return result;
    }

3.2.2 从headerLength中获取serializableType

   private static byte getProtocolType(int oriHeaderLen) {
        return(byte)((oriHeaderLen>>24) & 0xFF);
    }

3.2.3  从headerLength中获取真实的长度

  private static byte getProtocolType(int oriHeaderLen) {
        return(byte)((oriHeaderLen>>24) & 0xFF);
    }

4 重点代码

4.1 编码

package com.bonnie.netty.common.coder;

import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.serializer.SerializerUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;

/**
 * 编码器: 发送的信息构建成BonnieMessageRecord, 补充length以及headerLength
 */
public class BonnieEncoder extends MessageToByteEncoder<BonnieMessageRecord> {

    @Override
    protected void encode(ChannelHandlerContext ctx, BonnieMessageRecord msg, ByteBuf out) throws Exception {
        // 在网络传输过程中,一定要对数据进行序列化
        ByteBuffer header = encodeHead(msg);
        out.writeBytes(header);

        byte serializableType = msg.getHeader().getHeaderData().getSerializableType();
        byte[] body = SerializerUtil.serializer(msg.getBody(), serializableType);
        if(body!=null && body.length>0){
            out.writeBytes(body);
        }

    }

    private ByteBuffer encodeHead(BonnieMessageRecord msg) throws IOException {

        // 整个报文长度字段的长度
        int length = 4;
        
        byte serializableType = msg.getHeader().getHeaderData().getSerializableType();
        // 序列化头部信息
        byte[] headerData =SerializerUtil.serializer(msg.getHeader(), serializableType);
        // 加上头的长度
        length += headerData.length;

        // 序列化body
        int bodyLength = SerializerUtil.serializer(msg.getBody(), serializableType).length;
        length += bodyLength;

        ByteBuffer byteBuffer = ByteBuffer.allocate(4 + length - bodyLength);

        // 整体的报文长度,也就是 headerLength+headerData + body
        byteBuffer.putInt(length);
        // 头的长度
//        byteBuffer.putInt(headerData.length);
        // 把序列化的方式藏在了头长度的第一个字节里面
        byteBuffer.put(markProtocolType(headerData.length,serializableType));
        // 头的数据
        byteBuffer.put(headerData);
        // 切换成读模式
        byteBuffer.flip();

        return byteBuffer;
    }

    public static <T> byte[] seriablizer(T object){
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(object);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param headerLength:头的长度
     * @param type:序列化方式
     * @return
     */
    public static byte[] markProtocolType(int headerLength,byte type){

        byte[] result = new byte[4];
        // 第一个8位放了序列化的方式
        result[0] = type;

        // 第二部分开始就放头的长度,把头的长度的第二个字节放好了
        result[1] = (byte) ((headerLength >> 16) & 0xFF);

        // 第三个字节放头的第三个字节
        result[2] = (byte) ((headerLength >> 8) & 0xFF);

        // 第四个字节放头的第四个字节
        result[3] = (byte) (headerLength & 0xFF);

        return result;
    }

}

4.2 解码

package com.bonnie.netty.common.coder;

import com.bonnie.netty.common.domain.User;
import com.bonnie.netty.common.entity.BonnieHeader;
import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.serializer.SerializerUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.ByteBuffer;

/**
 * 解码:长度域解码器,将数据还原成对象BonnieMessageRecord的形式
 */
@Slf4j
public class BonnieDecoder extends LengthFieldBasedFrameDecoder {

    public BonnieDecoder() {
        super(Integer.MAX_VALUE, 0, 4, 0, 4);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf byteBuf = (ByteBuf) super.decode(ctx, in);
        if (byteBuf == null) {
            return null;
        }
        ByteBuffer byteBuffer = byteBuf.nioBuffer();
        return decode(byteBuffer);
    }

    private static BonnieMessageRecord decode(ByteBuffer byteBuffer) throws IOException {
        // 获取到byteBuf的长度
        int length = byteBuffer.limit();
        // 头和序列化方式组成的字节的长度
        int oriHeaderLen = byteBuffer.getInt();
        // 头真正的长度
        int headerLength = getHeaderLength(oriHeaderLen);
        // 头的字节数据
        byte[] headerDataByte = new byte[headerLength];
        byteBuffer.get(headerDataByte);

        BonnieMessageRecord bonnieMessageRecord = new BonnieMessageRecord();
        bonnieMessageRecord.setLength(length);
        // 反序列化头数据得到头对象
        BonnieHeader gpHeader = SerializerUtil.deserializer(BonnieHeader.class,headerDataByte,getProtocolType(oriHeaderLen));
        gpHeader.setHeaderLength(headerLength);
        bonnieMessageRecord.setHeader(gpHeader);
        // 反序列化body对象
        // 首先要知道body的长度
        int bodyLength = length - 4 - headerLength;
        byte[] bodyData = null;
        if(bodyLength >0 ){
            bodyData = new byte[bodyLength];
            // 从byteBuffer中读取数据到bodyData里面
            byteBuffer.get(bodyData);
        }
        bonnieMessageRecord.setBody(SerializerUtil.deserializer(User.class,bodyData,getProtocolType(oriHeaderLen)));

        return bonnieMessageRecord;
    }

    private static int getHeaderLength(int oriHeaderLen) {
        return oriHeaderLen & 0xFFFFFF;
    }

    private static byte getProtocolType(int oriHeaderLen) {
        return(byte)((oriHeaderLen>>24) & 0xFF);
    }
    /**
     * 反序列化
     * @param clazz
     * @param data
     * @return
     */
    private <T> T deserializable(Class<T> clazz, byte[] data) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new ByteArrayInputStream(data));
            return (T)ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

4.3 服务端

package com.bonnie.server.netty;

import com.bonnie.netty.common.coder.BonnieDecoder;
import com.bonnie.netty.common.coder.BonnieEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline
                                .addLast(new BonnieEncoder())
                                .addLast(new BonnieDecoder())
                                .addLast(new ServerHandler());
                    }
                });
        try {
            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

}
ServerHandler
package com.bonnie.server.netty;

import com.bonnie.netty.common.domain.User;
import com.bonnie.netty.common.entity.BonnieHeader;
import com.bonnie.netty.common.entity.BonnieHeaderData;
import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.enums.LanguageCodeEnum;
import com.bonnie.netty.common.enums.ReqTypeEnum;
import com.bonnie.netty.common.enums.SerializableTypeEnum;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        BonnieMessageRecord record = (BonnieMessageRecord) msg;
        System.out.println("收到客户端消息:"+record);

        // 给客户端返回数据
        BonnieMessageRecord messageRecord = new BonnieMessageRecord();

        BonnieHeader header = new BonnieHeader();
        BonnieHeaderData headerData = new BonnieHeaderData();

        User user = (User) record.getBody();
        Integer age = user.getAge();
        headerData.setVersion(2);
        headerData.setLanguageCode(LanguageCodeEnum.JAVA.getCode());
        headerData.setSerializableType(SerializableTypeEnum.JAVA.getCode());
        headerData.setReqType(ReqTypeEnum.RES.getCode());
        header.setHeaderData(headerData);

        messageRecord.setBody("客户端消息接收成功,用户的年龄是"+age);

        ctx.writeAndFlush(record);

        super.channelRead(ctx, msg);
    }
}

4.4 客户端

package com.bonnie.client.netty;

import com.bonnie.netty.common.coder.BonnieDecoder;
import com.bonnie.netty.common.coder.BonnieEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

public class NettyClient {

    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(worker)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline
                                .addLast(new BonnieEncoder())
                                .addLast(new BonnieDecoder())
                                .addLast(new ClientHandler());
                    }
                });

        try {
            ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            worker.shutdownGracefully();
        }
    }

}
package com.bonnie.client.netty;

import com.bonnie.netty.common.domain.User;
import com.bonnie.netty.common.entity.BonnieHeader;
import com.bonnie.netty.common.entity.BonnieHeaderData;
import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.enums.LanguageCodeEnum;
import com.bonnie.netty.common.enums.ReqTypeEnum;
import com.bonnie.netty.common.enums.SerializableTypeEnum;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        for (int i=0; i<10; i++) {
            // 构建报文内容 length | headerLength | headerData | bodyData
            BonnieMessageRecord messageRecord = new BonnieMessageRecord();

            // 请求头信息
            BonnieHeader header = new BonnieHeader();
            BonnieHeaderData bonnieHeaderData = new BonnieHeaderData();
            bonnieHeaderData.setVersion(0);
            bonnieHeaderData.setLanguageCode(LanguageCodeEnum.JAVA.getCode());
            bonnieHeaderData.setReqType(ReqTypeEnum.REQ.getCode());
            bonnieHeaderData.setSerializableType(SerializableTypeEnum.JAVA.getCode());

            // 请求内容
            User user = new User();
            user.setName("我是请求数据:"+i);
            user.setAge(i);

//            header.setHeaderLength();
            header.setHeaderData(bonnieHeaderData);

//            messageRecord.setLength();
            messageRecord.setHeader(header);
            messageRecord.setBody(user);

//            System.out.println("客户端发送数据:"+messageRecord);

            ctx.writeAndFlush(messageRecord);
        }

        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 将数据写入到服务端后,服务端回写数据到客户端,在这个方法中去接收数据
        BonnieMessageRecord record = (BonnieMessageRecord) msg;
        System.out.println("接收服务端消息:" + record);
        super.channelRead(ctx, msg);
    }

}

5 代码下载位置

地址: https://gitee.com/huyanqiu6666/netty.git
分支: 20250804-custom-message-serializer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值