• Netty 学习(三):通信协议和编解码


    Netty 学习(三):通信协议和编解码

    作者: Grey

    原文地址:

    博客园:Netty 学习(三):通信协议和编解码

    CSDN:Netty 学习(三):通信协议和编解码

    无论使用 Netty 还是原生 Socket 编程,都可以实现自定义的通信协议。

    所谓协议就是:客户端和服务端商量好,每一个二进制数据包中的每一段字节分别代表什么含义的规则。

    有了规则,在服务端和客户端就可以通过这个设置好的规则进行二进制和对象的转换。

    通信协议格式可以参考如下格式

    image

    每个部分的说明如下

    魔数:用来标识这个数据包是否遵循我们设计的通信协议,类似 Java 字节码开头的4字节:0xcafebabe

    版本标识:用来标识这个协议是什么版本,用于后续协议的升级

    序列化算法:用于标识这个协议的数据包使用什么序列化算法,比如:JSON,XML等

    指令:用于标识这个数据在收到后应该使用什么处理逻辑。

    数据长度&数据内容:不赘述

    定好格式以后,

    接下来我们可以约定双方的序列化方法,这里我们可以用 JSON 序列化/反序列化 为例,其他格式的类似。

    使用 Gson 可以很方便将 JSON 字符串和对象进行互转:

        private static final Gson gson = new Gson();
     // 序列化
        public byte[] serialize(Object object) {
            return gson.toJson(object).getBytes(UTF_8);
        }
     // 反序列化
        public <T> T deserialize(Class<T> clazz, byte[] bytes) {
            return gson.fromJson(new String(bytes, UTF_8), clazz);
        }
    

    实现了对象和字节数组的互转以后,我们需要实现字节数组和 Netty 通信载体 ByteBuf 的互转,包括如下两个方法

    ByteBuf 编码(数据包) 
    

    上述编码方法需要做如下几个事情

    1. 分配 ByteBuf (分配一块内存区域,Netty 会直接创建一个堆外内存)

    2. 按照协议获取数据包对应的内容

    3. 严格按照协议规定的字节数填充到 ByteBuf 中

    数据包 解码(ByteBuf byteBuf)
    

    上述解码方法主要做如下几件事情

    1. 校验魔数

    2. 校验版本号

    3. 如果严格按照规范传输的 ByteBuf,上述两步校验一定是通过的,可以直接跳过。

    4. 获取序列化算法,指令和数据包长度,并将数据内容转换成字节数组

    5. 将字节数组转换成对应的数据包对象。

    因为不同的数据包内容有所不一样,所以应该设置一个抽象类,由各个子类实现具体数据包的内容。

    package protocol;
    
    import lombok.Data;
    
    /**
     * 数据包抽象类
     *
     * @author <a href="mailto:410486047@qq.com">Grey</a>
     * @date 2022/9/15
     * @since
     */
    @Data
    public abstract class Packet {
        /**
         * 协议版本
         */
        private Byte version = 1;
    
        /**
         * 指令,由子类实现
         *
         * @return
         */
        public abstract Byte getCommand();
    }
    

    对于一个具体的操作,比如登录操作,它需要的数据包需要继承并实现这个抽象类的抽象方法。

    package protocol;
    
    import lombok.Data;
    
    import static protocol.Command.LOGIN_REQUEST;
    
    /**
     * @author <a href="mailto:410486047@qq.com">Grey</a>
     * @date 2022/9/15
     * @since
     */
    @Data
    public class LoginRequestPacket extends Packet {
        // 登录操作需要的数据内容包括如下三个
        private Integer userId;
        private String username;
        private String password;
    
        @Override
        public Byte getCommand() {
            return LOGIN_REQUEST;
        }
    }
    

    对于调用者来说,只需要使用LoginRequestPacket即可,无须关注其底层的编码和解码工作。伪代码如下

    func() {
            LoginRequestPacket loginRequestPacket = new LoginRequestPacket();
            loginRequestPacket.setVersion(((byte) 1));
            loginRequestPacket.setUserId(123);
            loginRequestPacket.setUsername("zhangsan");
            loginRequestPacket.setPassword("password");
            // 编码
            ByteBuf byteBuf = 封装好的编解码工具类.编码(loginRequestPacket);
            // 解码
            Packet decodedPacket = 封装好的编解码工具类.解码(byteBuf); 
            // 序列化成我们需要的对象
            序列化和反序列化工具类.序列化(decodedPacket);
    }
    

    完整代码见:hello-netty

    本文所有图例见:processon: Netty学习笔记

    更多内容见:Netty专栏

    参考资料

    跟闪电侠学 Netty:Netty 即时聊天实战与底层原理

    深度解析Netty源码

  • 相关阅读:
    UML类图
    # linux下安装Nodejs环境
    [原创] 如何编写一份不可维护的代码
    [原创作品]观察者模式在Web App的应用
    Thinking In Web [原创作品]
    [原创作品]Javascript内存管理机制
    [小知识] 获取浏览器UA标识
    [原创作品] 对获取多层json值的封装
    Javascript 精髓整理篇之三(数组篇)postby:http://zhutty.cnblogs.com
    [原创作品]一个实用的js倒计时器 postby:zhutty.cnblogs.com
  • 原文地址:https://www.cnblogs.com/greyzeng/p/16696597.html
Copyright © 2020-2023  润新知