netty protobuf 序列化协议之 自定义协议头、协议体

接上篇:netty protobuf 序列化协议之 多个message

本篇讲讲自定义协议头、协议体时候,怎么使用protobuf作为序列化和反序列化。

部分代码思路来源黑马netty教程。

gitee:https://gitee.com/hsjjsh123/itcast_netty/tree/master/code/my_netty_study/Netty-protobuf-selfDef

先看一下自定义协议编解码器:

 4 字节的魔数
 1 字节的版本
 1 字节的序列化方式 protobuf = 0
 4 字节的消息类型
 4 字节的消息体内容长度
 N 字节的消息体内容部分

我们的.proto(protobuf3)文件如下,每个消息message都是会对应java类,包含消息唯一id,还有一些业务字段:

syntax = "proto3";
// 生成的包名
option java_package="com.pancm.protobuf";
//生成的java名
option java_outer_classname = "UserInfo";

message UserMsgLogin{
      // 消息唯一ID
      int32 id = 1;
      // 内容
      string content = 2;
      // userID
      int32 user_id = 3;
      // userNME
      string user_name = 4;
      // 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
      int32 type = 5;
}
message UserMsgSingle{
      // 消息唯一ID
      int32 id = 1;
      // 内容
      string content = 2;
      // 来源
      int32 from_id = 3;
      // 来源
      string from_name = 4;
      // 去源
      int32 to_id = 5;
      // 去源
      string to_name = 6;
      // 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
      int32 type = 7;
}
message UserMsgMutiple{
      // 消息唯一ID
      int32 id = 1;
      // 内容
      string content = 2;
      // 来源
      int32 from_id = 3;
      // 来源
      string from_name = 4;
      // 去源group
      int32 to_group_id = 5;
      // 去源group
      string to_group_name = 6;
      // 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
      int32 type = 7;
}
message UserMsgQuit{
      // 消息唯一ID
      int32 id = 1;
      // bool
      bool quit = 2;
      // 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
      int32 type = 7;
}

message UserMsgServerAck{
      // 消息唯一ID
      int32 id = 1;
      // 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
      int32 type = 2;
}

注意:编解码器接受的是com.google.protobuf.GeneratedMessageV3对象,所有的.proto文件message转java类之后的父类是GeneratedMessageV3。

package com.pancm.protocol;

import com.google.protobuf.BytesValue;
import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.InvalidProtocolBufferException;
import com.pancm.protobuf.UserInfo;
import com.pancm.utils.MessageUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

@Slf4j
@ChannelHandler.Sharable
/**
 * 必须和 LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf 消息是完整的
 */
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, GeneratedMessageV3> {
    /**
     * @Description:protobuf编码
     * @author HeShengjin 2356899074@qq.com
     * @date 2021/7/29 9:51
     */
    @Override
    public void encode(ChannelHandlerContext ctx, GeneratedMessageV3 msg, List<Object> outList) throws Exception {
        ByteBuf out = ctx.alloc().buffer();
        // 1. 4 字节的魔数
        out.writeBytes(new byte[]{1, 2, 3, 4});
        // 2. 1 字节的版本,
        out.writeByte(1);
        // 3. 1 字节的序列化方式 protobuf 0
        out.writeByte(SerializeType.PROTOBUF.getSerializeType());
        // 4. 4 字节的消息类型
        out.writeInt(MessageUtil.parseMessageFeildTypeFromProtobufMessage(msg).intValue());

        // 5. 获取protobuf的字节数组
        byte[] bytes = msg.toByteArray();
        // 6. 长度
        out.writeInt(bytes.length);
        // 7. 写入protobuf
        out.writeBytes(bytes);
        outList.add(out);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 1. 4 字节的魔数
        int magicNum = in.readInt();
        // 2. 1 字节的版本,
        byte version = in.readByte();
        // 3. 1 字节的序列化方式 protobuf 0
        byte serializerAlgorithm = in.readByte(); // 0
        // 4. 4 字节的消息类型
        int messageType = in.readInt();

        // 5. 长度
        int length = in.readInt();
        byte[] bytes = new byte[length];
        // 6. 读取protobuf字节数组
        in.readBytes(bytes, 0, length);

        //反序列化成java对象
        out.add(MessageUtil.parseMessageType2Message(messageType, bytes));
    }

}

4 字节的消息类型字节的消息类型我们编码时候是从message转换的java对象读取的,解码时候反序列化成对应的protobuf的java类。 .proto 文件定义的type 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack

package com.pancm.utils;/**
 * @author HeShengjin 2356899074@qq.com
 * @Description TODO
 * @createTime 2021年07月29日 15:47:00
 */

import com.google.protobuf.Descriptors;
import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.InvalidProtocolBufferException;
import com.pancm.protobuf.UserInfo;
import com.pancm.protocol.MsgTypeFeilds;
import com.pancm.protocol.MsgTypes;

import java.util.Iterator;
import java.util.Map;

/**
 * @author hsj
 * @description:message工具
 * @date 2021/7/29 15:47
 */
public final class MessageUtil {
    /**
     * @Description:protobuf获取消息的类型type
     * @author HeShengjin 2356899074@qq.com
     * @date 2021/7/29 15:36
     */
    public static Integer parseMessageFeildTypeFromProtobufMessage(GeneratedMessageV3 userMsgSingle) {
        Map<Descriptors.FieldDescriptor, Object> map = userMsgSingle.getAllFields();
        Iterator<Map.Entry<Descriptors.FieldDescriptor, Object>> it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<Descriptors.FieldDescriptor, Object> next = it.next();
            if (next.getKey().getJsonName().equals(MsgTypeFeilds.TYPE.getName())) {
                return (Integer) next.getValue();
            }
        }
        return null;
    }

    /**
     * @Description:根据消息类型反序列化
     * @author HeShengjin 2356899074@qq.com
     * @date 2021/7/29 15:57
     */
    public static GeneratedMessageV3 parseMessageType2Message(int messageType, byte[] bytes) throws InvalidProtocolBufferException {
        if (MsgTypes.TYPE_LOGIN.getMsgType() == messageType) {
            return UserInfo.UserMsgLogin.parseFrom(bytes);
        }
        if (MsgTypes.TYPE_SINGLE.getMsgType() == messageType) {
            return UserInfo.UserMsgSingle.parseFrom(bytes);
        }
        if (MsgTypes.TYPE_MUTIPLE.getMsgType() == messageType) {
            return UserInfo.UserMsgMutiple.parseFrom(bytes);
        }
        if (MsgTypes.TYPE_QUIT.getMsgType() == messageType) {
            return UserInfo.UserMsgQuit.parseFrom(bytes);
        }
        if (MsgTypes.TYPE_SERVER_ACK.getMsgType() == messageType) {
            return UserInfo.UserMsgServerAck.parseFrom(bytes);
        }
        return null;
    }
}

package com.pancm.protocol;

/**
 * @author HeShengjin 2356899074@qq.com
 * @Description 消息type指令类型等, 类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
 * @createTime 2021年07月29日 09:14:00
 */
public enum MsgTypes {

    TYPE_LOGIN(1,"login"),
    TYPE_SINGLE(2,"single"),
    TYPE_MUTIPLE(3,"mutiple"),
    TYPE_QUIT(4,"quit"),
    TYPE_SERVER_ACK(5,"ack");

    //指令
    //类型,1 = login 、2 = single、3 = mutiple、4 = quit、5 = server_ack
    private int msgType;
    private String msgTypeName;


    MsgTypes(int msgType, String msgTypeName) {
        this.msgType = msgType;
        this.msgTypeName = msgTypeName;
    }

    public String getMsgTypeName() {
        return msgTypeName;
    }

    public void setMsgTypeName(String msgTypeName) {
        this.msgTypeName = msgTypeName;
    }

    public int getMsgType() {
        return msgType;
    }

    public void setMsgType(int msgType) {
        this.msgType = msgType;
    }
}

package com.pancm.protocol;

/**
 * @author HeShengjin 2356899074@qq.com
 * @Description 消息的字段域
 * @createTime 2021年07月29日 15:43:00
 */
public enum MsgTypeFeilds {
    //字段域type
    TYPE("type");

    private String name;

    MsgTypeFeilds(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

以上就是消息定义.proto文件和编解码相关代码。
这里使用netty解码器:LengthFieldBasedFrameDecoder
最大帧设置1024
长度域字段偏移字节10 = 4 + 1 + 1 + 4
长度域字段长度4字节
无需调整,使用0
无需去除字节,使用0
LengthFieldBasedFrameDecoder (1024,10,4,0,0)

import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

public class ProcotolFrameDecoder extends LengthFieldBasedFrameDecoder {

    public ProcotolFrameDecoder() {
        this(1024, 10, 4, 0, 0);
    }

    public ProcotolFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    }
}

服务端handler接受一个单聊类型的消息:

package com.pancm.handler;

import com.pancm.protobuf.UserInfo;
import com.pancm.protocol.Constant;
import com.pancm.protocol.MsgTypes;
import com.pancm.protocol.SequenceIdGenerator;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * @author hsj
 * @description:single聊天handler
 * @date 2021/7/29 10:13
 */
@ChannelHandler.Sharable
@Slf4j
public class UserMsgSingleServerHandler extends SimpleChannelInboundHandler<UserInfo.UserMsgSingle> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, UserInfo.UserMsgSingle msg) throws Exception {
        //自动释放ByteBuf
        //ACK时候,client不再发起消息
        log.info("接受到client的single聊天protobuf消息,{}",msg.getAllFields());

        //server回复client消息ACK
        UserInfo.UserMsgServerAck userMsgServerAck = UserInfo.UserMsgServerAck.newBuilder()
                //消息id
                .setId(SequenceIdGenerator.nextId())
                //ACK
                .setType(MsgTypes.TYPE_SERVER_ACK.getMsgType())
                .build();
        ctx.channel().writeAndFlush(userMsgServerAck);
    }
}

public class NettyServerFilter extends ChannelInitializer<SocketChannel> {
	
     @Override
     protected void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline ph = ch.pipeline();

         // 解码和编码,应和客户端一致
         //传输的协议 Protobuf
         ph.addLast("LengthFieldBasedFrameDecoder", new ProcotolFrameDecoder());
         ph.addLast("decoc-protobuf",new MessageCodecSharable());
         //业务逻辑实现类
         ph.addLast("UserMsgSingleServerHandler", new UserMsgSingleServerHandler());
     }
 }

客户端接受服务器的一个回应handler:

package com.pancm.handler;

import com.pancm.protobuf.UserInfo;
import com.pancm.protocol.Constant;
import com.pancm.protocol.MsgTypes;
import com.pancm.protocol.SequenceIdGenerator;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * @author hsj
 * @description:ACK聊天handler
 * @date 2021/7/29 10:13
 */
@ChannelHandler.Sharable
@Slf4j
public class UserMsgServerAckClientHandler extends SimpleChannelInboundHandler<UserInfo.UserMsgServerAck> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, UserInfo.UserMsgServerAck msg) throws Exception {
        //自动释放ByteBuf
         log.info("接受到服务器ACK的protobuf消息,{}",msg.getAllFields());
    }
}

public class NettyClientFilter extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline ph = ch.pipeline();

        // 解码和编码,应和客户端一致
        //传输的协议 Protobuf
        ph.addLast("LengthFieldBasedFrameDecoder", new ProcotolFrameDecoder());
        ph.addLast("decoc-protobuf",new MessageCodecSharable());
       
        //业务逻辑实现类
        ph.addLast("UserMsgServerAckClientHandler", new UserMsgServerAckClientHandler());
      
    }
}

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值