自定义协议编解码:Codec(encode\decode)及Sharble注解详解

自定义协议要素

  • 魔术:判断是否是无效数据包。
  • 版本号:可以支持协议的升级。
  • 序列化算法(对消息正文进行序列化以及反序列化):json、protobuf、hessian、jdk。
  • 指令类型:如登陆、注册、单聊、群聊跟业务相关的。
  • 请求序号:为了双工通信,提供异步能力。
  • 正文长度:
  • 消息正文:

JDK自带序列化以及反序列化算法

// 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(msg);

// 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bis);
        Message message = (Message) ois.readObject();

编解码器设计

        定义抽象父类message

@Data
public abstract class Message implements Serializable {

    /**
     * 根据消息类型字节,获得对应的消息 class
     * @param messageType 消息类型字节
     * @return 消息 class
     */
    public static Class<? extends Message> getMessageClass(int messageType) {
        return messageClasses.get(messageType);
    }

    private int sequenceId;

    private int messageType;

    public abstract int getMessageType();

    public static final int LoginRequestMessage = 0;
    public static final int LoginResponseMessage = 1;
    public static final int ChatRequestMessage = 2;
    public static final int ChatResponseMessage = 3;
    public static final int GroupCreateRequestMessage = 4;
    public static final int GroupCreateResponseMessage = 5;
    public static final int GroupJoinRequestMessage = 6;
    public static final int GroupJoinResponseMessage = 7;
    public static final int GroupQuitRequestMessage = 8;
    public static final int GroupQuitResponseMessage = 9;
    public static final int GroupChatRequestMessage = 10;
    public static final int GroupChatResponseMessage = 11;
    public static final int GroupMembersRequestMessage = 12;
    public static final int GroupMembersResponseMessage = 13;
    public static final int PingMessage = 14;
    public static final int PongMessage = 15;
    /**
     * 请求类型 byte 值
     */
    public static final int RPC_MESSAGE_TYPE_REQUEST = 101;
    /**
     * 响应类型 byte 值
     */
    public static final int  RPC_MESSAGE_TYPE_RESPONSE = 102;

    private static final Map<Integer, Class<? extends Message>> messageClasses = new HashMap<>();

    static {
        messageClasses.put(LoginRequestMessage, LoginRequestMessage.class);
        messageClasses.put(LoginResponseMessage, LoginResponseMessage.class);
        messageClasses.put(ChatRequestMessage, ChatRequestMessage.class);
        messageClasses.put(ChatResponseMessage, ChatResponseMessage.class);
        messageClasses.put(GroupCreateRequestMessage, GroupCreateRequestMessage.class);
        messageClasses.put(GroupCreateResponseMessage, GroupCreateResponseMessage.class);
        messageClasses.put(GroupJoinRequestMessage, GroupJoinRequestMessage.class);
        messageClasses.put(GroupJoinResponseMessage, GroupJoinResponseMessage.class);
        messageClasses.put(GroupQuitRequestMessage, GroupQuitRequestMessage.class);
        messageClasses.put(GroupQuitResponseMessage, GroupQuitResponseMessage.class);
        messageClasses.put(GroupChatRequestMessage, GroupChatRequestMessage.class);
        messageClasses.put(GroupChatResponseMessage, GroupChatResponseMessage.class);
        messageClasses.put(GroupMembersRequestMessage, GroupMembersRequestMessage.class);
        messageClasses.put(GroupMembersResponseMessage, GroupMembersResponseMessage.class);
        messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage.class);
        messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage.class);
    }

}

        设计编解码器: 

@Slf4j
public class MessageCodec extends ByteToMessageCodec<Message> {
    // 编码器
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        // 1. 4字节的魔术
        out.writeBytes(new byte[]{1, 2, 3, 4});
        // 2. 1字节的版本
        out.writeByte(1);
        // 3. 1字节的序列化方式 jdk 0, json 1
        out.writeByte(0);
        // 4. 1字节的指令类型
        out.writeByte(msg.getMessageType());
        // 5. 4字节的序列号
        out.writeInt(msg.getSequenceId());
        // 无意义,对其填充,使其前缀保持16字节
        out.writeByte(0xff);
        // 6. 获取内容的字节数组
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(msg);
        byte[] bytes = bos.toByteArray();
        // 7.长度
        out.writeInt(bytes.length);
        // 8. 写入内容
        out.writeBytes(bytes);
    }

    // 解码器
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 1. 获取魔术
        int magicNum = in.readInt();
        // 2. 1字节的版本
        byte version = in.readByte();
        // 3. 1字节的序列化方式 jdk 0, json 1
        byte serializerType = in.readByte();
        // 4. 1字节的指令类型
        byte messageType = in.readByte();
        // 5. 4字节的序列号
        int sequenceId = in.readInt();
        // 无意义,对其填充
        in.readByte();
        // 读取长度
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes, 0, length);
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bis);
        Message message = (Message) ois.readObject();
        log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
        log.debug("{}", message);
        out.add(message);
    }
}

 测试编解码器

        出战编码测试: 

         入站解码测试:

        将上述代码中帧解码器LengthFieldBasedFrameDecoder注释掉,此时报错如下:

 @Sharable注解

        @Sharable 注解用于标记一个 ChannelHandler 是可共享的,这意味着多个 ChannelChannelPipeline 可以重用同一个 ChannelHandler 实例。@Sharable 主要适用于那些没有维护状态或者只维护线程安全状态的处理器。

        当Handler不保存状态时,就可以安全地在多线程下被共享。此时可以将Handler提取为公共方法,从而达到复用的效果:

        帧解码器LengthFieldBasedFrameDecoder是否能抽取为公共方法?不可以主要原因如下:

  • 状态维护:LengthFieldBasedFrameDecoder 在其内部维护了一些状态信息(例如,解码过程中的临时数据),这些状态信息是特定于每个 Channel 的,因此它不能安全地被多个 Channel 共享。
  • 配置参数:LengthFieldBasedFrameDecoder 在初始化时需要一些特定的配置参数(如长度字段的偏移量和长度),这些参数可能依赖于特定的 Channel,因此每个 Channel 可能需要一个独立的解码器实例来满足不同的配置需求。

        查看LoggingHandler和LengthFieldBasedFrameDecoder源码,可以看到LoggingHandler内部加@Sharable注解,代表次handler内部不维护状态信息,可以被多个channel、channelPipeline重用。LengthFieldBasedFrameDecoder内部没有@Sharable注解,不可以重用。

         同时可以看到LengthFieldBasedFrameDecoder继承自ByteToMessageDecoder,查看后者的源码如下:

         可以看到Netty作者规定只要继承ByteToMessageDecoder的子类都不能被多个handler共享。

        所以我们上面自定义编解码器也不能加@Sharable注解使其被多个handler使用。如何设计可以被多个handler共用的帧解码器,可以将继承关系由ByteToMessageDecoder替换为MessageToMessageCodec。

 

  • 24
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值