Netty——序列化的作用及自定义协议

news2024/11/18 13:25:35

序列化的作用及自定义协议

  • 序列化的重要性
    • 大小对比
    • 效率对比
  • 自定义协议
    • 序列化
    • 数据结构
    • 自定义编码器
    • 自定义解码器
    • 安全性
    • 验证
      • NettyClient
      • NettyServer
      • NettyClientTestHandler
      • NettyServerTestHandler
      • 结果

上一章已经说了怎么解决沾包和拆包的问题,但是这样离一个成熟的通信还是有一点距离,我们还需要让服务端和客户端使用同一个"语言"来沟通,要不然一个讲英文一个讲中文,两个都听不懂岂不是很尴尬?这种语言就叫协议。

Netty自身就支持很多种协议比如Http、Websocket等等,但如果用来作为自己的RPC框架通常会自定义协议,所以这也是本文的重点!

序列化的重要性

在说协议之前,我们需要先知道什么是序列化,序列化是干嘛的?

我们要知道数据在传输的过程中是以0和1的形式传输的,而把对象转化成二进制的过程就叫序列化,将二进制转化为对象的过程就叫反序列化。

为什么要说这个很重要呢?因为序列化和反序列化是需要耗时的,而序列化后的字节大小也会影响到传输的效率,所以选对一种高效的序列化方式是非常之重要的,下面我们以JDK自带的序列化和我们常用的JSON序列化来做一个对比,序列化后大小的对比、序列化效率的对比

大小对比

我们先准备一个实体类SerializeTestVO实现Serializable 接口

public class SerializeTestVO implements Serializable {
    private Integer id;

    private String name;

    private Integer age;

    private Integer sex;

    private Integer bodyWeight;

    private Integer height;

    private String school;
    
    //Set、get方法省略
}

测试方法

public static void main(String[] args) throws IOException {
        // 普普通通的实体类
        SerializeTestVO serializeTestVO = new SerializeTestVO();
        serializeTestVO.setAge(18);
        serializeTestVO.setBodyWeight(120);
        serializeTestVO.setHeight(180);
        serializeTestVO.setId(10000);
        serializeTestVO.setName("张三");
        serializeTestVO.setSchool("XXXXXXXXXXXX");

        // JDK序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(serializeTestVO);
        objectOutputStream.flush();
        objectOutputStream.close();
        System.out.println("JDK 序列化大小: "+(byteArrayOutputStream.toByteArray().length));
        byteArrayOutputStream.close();

        //JSON序列化
        System.out.println("JSON 序列化大小: " + JSON.toJSONString(serializeTestVO).getBytes().length);
}

结果:
在这里插入图片描述

可以看到序列化后大小相差了好几倍,这也意味着传输效率的几倍

效率对比

实体类保持不变,我们序列化300W次,看看结果

public static void main(String[] args) throws IOException {
        SerializeTestVO serializeTestVO = new SerializeTestVO();
        serializeTestVO.setAge(18);
        serializeTestVO.setBodyWeight(120);
        serializeTestVO.setHeight(180);
        serializeTestVO.setId(10000);
        serializeTestVO.setName("张三");
        serializeTestVO.setSchool("XXXXXXXXXXXX");

        long start = System.currentTimeMillis();
        for (int i = 0; i < 3000000; i++) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(serializeTestVO);
            objectOutputStream.flush();
            objectOutputStream.close();
            byte[] bytes = byteArrayOutputStream.toByteArray();
            byteArrayOutputStream.close();
        }
        System.out.println("JDK 序列化耗时: " + (System.currentTimeMillis() - start));

        long start1 = System.currentTimeMillis();
        for (int i = 0; i < 3000000; i++) {
            byte[] bytes = JSON.toJSONString(serializeTestVO).getBytes();
        }
        System.out.println("JSON 序列化耗时: " + (System.currentTimeMillis() - start1));
    }

结果:

在这里插入图片描述

几乎6倍的差距,结合序列化后的大小综合来看,选择一种好的序列化方式是多么的重要

自定义协议

其实到现在我们已经掌握了自定义协议里面最关键的几个点了,序列化、数据结构、编解码器,我们一个一个来

序列化

直接采用我们常用且熟悉的JSON序列化

数据结构

我们设置为消息头和消息体,结构如下:

在这里插入图片描述

消息头包含:开始标志、时间戳、消息体长度

消息体包含:通信凭证、消息ID、消息类型、消息

实体类如下

@Data
public class NettyMsg {
    private NettyMsgHead msgHead=new NettyMsgHead();
    private NettyBody nettyBody;

    public NettyMsg(ServiceCodeEnum codeEnum, Object msg){
        this.nettyBody=new NettyBody(codeEnum, msg);
    }
}

@Data
public class NettyMsgHead {
    // 开始标识
    private short startSign = (short) 0xFFFF;
    // 时间戳
    private final int timeStamp;

    public NettyMsgHead(){
        this.timeStamp=(int)(DateUtil.current() / 1000);
    }
}

@Data
public class NettyBody {
    // 通信凭证
    private String token;

    // 消息ID
    private String msgId;

    // 消息类型
    private short msgType;

    // 消息 这里序列化采用JSON序列化
    // 所以这个msg可以是实体类的msg 两端通过消息类型来判断实体类类型
    private String msg;
    
    public NettyBody(){

    }
    public NettyBody(ServiceCodeEnum codeEnum,Object msg){
        this.token=""; // 鉴权使用
        this.msgId=""; // 拓展使用
        this.msgType=codeEnum.getCode();
        this.msg= JSON.toJSONString(msg);
    }
}

消息类型枚举

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ServiceCodeEnum {
    TEST_TYPE((short) 0xFFF1, "测试");


    private final short code;
    private final String desc;

    ServiceCodeEnum(short code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    public short getCode() {
        return code;
    }

}

自定义编码器

编码器的作用就是固定好我们的数据格式,无需在每次发送数据的时候还需要去对数据进行格式编码

public class MyNettyEncoder extends MessageToByteEncoder<NettyMsg> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, NettyMsg msg, ByteBuf out) throws Exception {
        // 写入开头的标志
        out.writeShort(msg.getMsgHead().getStartSign());
        // 写入秒时间戳
        out.writeInt(msg.getMsgHead().getTimeStamp());

        byte[] bytes = JSON.toJSON(msg.getNettyBody()).toString().getBytes();
        // 写入消息长度
        out.writeInt(bytes.length);
        // 写入消息主体
        out.writeBytes(bytes);
    }
}

自定义解码器

解码器的第一个作用就是解决沾包和拆包的问题,第二个作用就是对数据有效性的校验,比如数据协议是否匹配、数据是否被篡改、数据加解密等等

所以我们直接继承LengthFieldBasedFrameDecoder类,重写decode方法,利用父类来解决沾包和拆包问题,自定义来解决数据有效性问题

public class MyNettyDecoder extends LengthFieldBasedFrameDecoder {
    // 开始标记
    private final short HEAD_START = (short) 0xFFFF;


    public MyNettyDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
    }

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

    public MyNettyDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
    }

    public MyNettyDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
        super(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        // 经过父解码器的处理 我们就不需要在考虑沾包和半包了
        // 当然,想要自己处理沾包和半包问题也不是不可以
        ByteBuf decode = (ByteBuf) super.decode(ctx, in);
        if (decode == null) {
            return null;
        }
        // 开始标志校验  开始标志不匹配直接 过滤此条消息
        short startIndex = decode.readShort();
        if (startIndex != HEAD_START) {
            return null;
        }
        // 时间戳
        int timeIndex = decode.readInt();
        // 消息体长度
        int lenOfBody = decode.readInt();
        // 读取消息
        byte[] msgByte = new byte[lenOfBody];
        decode.readBytes(msgByte);
        String msgContent = new String(msgByte);
        // 将消息转成实体类 传递给下面的数据处理器
        return JSON.parseObject(msgContent, NettyBody.class);
    }
}

安全性

上述的协议里面,我只预留了三种简单的校验,一个是开始标识,二是消息凭证,三是时间戳,实时上这太简单了,下面我说几种可以加上去拓展的:

消息整体加密:消息头添加一个加密类型,客户端和服务端都内置几种加解密手段,在发送消息的时候随机一种加密方式对加密类型、消息长度以外的其他内容加密,接收的时候再解密,但是要注意加密后不能影响沾包和拆包的处理

消息体加密:添加结束标识放入消息体,和上述方式类似,但是是对消息体中的内容再次加密,可和上述方式结合,形成二次加密

时间戳:可以对长时间才接收到的消息拒收,或者要求重发根据消息ID

加签和验签:对具体的消息加签和验签,防止篡改

凭证:这个很熟悉了,就比如登录凭证

复杂格式:上述的数据格式还是过于简单,实际可以整了更加复杂

验证

主体代码呢还是之前的,我们改动几个地方

NettyClient

解码器是继承的LengthFieldBasedFrameDecoder,所以参数也一样,不懂的看一下上一篇

在这里插入图片描述

NettyServer

在这里插入图片描述

NettyClientTestHandler

发送100次是为了验证沾包和拆包,发送不同的开始标志,是为了验证接收的时候是否有过滤无效数据

在这里插入图片描述

NettyServerTestHandler

有了编码器,发送可以直接发送实体类,有了解码器我们可以直接用实体类接收数据,因为解码器里面往下传递的是过滤了消息头的实体类

在这里插入图片描述

结果

一共接收到了50条消息,而且都是偶数消息,说明无效消息被过滤了,也没有沾包和拆包
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【华为OD机试模拟题】用 C++ 实现 - 停车场最大距离(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 分积木(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 吃火锅(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - RSA 加密算法(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 构成的正方形数量(2023.Q1) 【华为OD机试模拟…

MyBatis快速开发

查询user表中的所有数据 步骤&#xff1a; 创建user表 打开Navicat&#xff0c;新建查询&#xff0c;将下面SQL代码复制粘贴并执行&#xff1a; create database mybatis; use mybatis;drop table if exists tb_user;create table tb_user(id int primary key auto_incremen…

抽象类与接口的区别

抽象类什么是抽象类&#xff1f;抽象类是特殊的类&#xff0c;只是不能被实例化&#xff1b;除此以外&#xff0c;具有类的其他特性&#xff1b;重要的是抽象类可以包括抽象方法&#xff0c;这是普通类所不能的。抽象方法只能声明于抽象类中&#xff0c;且不包含任何实现&#…

Docker安装YApi

目录0、Docker 环境准备1、数据库准备 MongoDB2、启动 YAPI3、官网教程0、Docker 环境准备 Docker 容器之间网络互通需要使用 docker network create yapi 创建一个自定义网络 docker network create yapi1、数据库准备 MongoDB YAPI 的数据库是 MongoDB&#xff0c;准备镜像…

Java线程池使用与原理解析1(线程池优点、使用方法、参数含义及线程池运转机制)

为什么要使用线程池&#xff1f; JDK1.5后JUC包添加了线程池相关接口&#xff0c;在Java诞生之初并没有线程池这个概念。刚开始Java程序都是自行创建线程去处理任务。随着应用使用的线程越来越多&#xff0c;JDK开发者们发现有必要使用一个统一的类来管理这些线程&#xff0c;…

从应届毕业生到大型开源社区 Committer,我做对了什么?

熟悉李成龙的人都叫他小龙。“小龙&#xff0c;2.2.3 Milvus 版本&#xff0c;日志会一直积累在磁盘上吗&#xff1f;默认会定期清除吗&#xff1f;”“小龙&#xff0c;请教一个问题&#xff0c;为啥取不到 field 中的 type 字段&#xff1f;”“小龙……”熟悉 Milvus 社区群…

django项目中如何添加自定义的django command

项目目录 1.我们自己建立的application叫做app&#xff0c;首先在这个app目录下&#xff0c;我们需要新建management目录&#xff0c;这个目录里应该包括&#xff1a;__ init__.py&#xff08;内容为空&#xff0c;用于打包&#xff09;和commands目录&#xff0c;然后在comma…

Linux中使用Docker部署Mysql数据库

前言 和朋友一起搞一个项目&#xff0c;分了一下工作&#xff0c;但是mysql迟迟安装不上&#xff0c;程序都在一个环境里确实容易出现很多问题&#xff0c;浪费时间和经历在这些配置上&#xff0c;好在有docker了&#xff0c;就在docker里搭建一个Mysql数据库使用吧&#xff0…

【计算机网络】计算机网络

目录一、概述计算机网络体系结构二、应用层DNS应用文件传输应用DHCP 应用电子邮件应用Web应用当访问一个网页的时候&#xff0c;都会发生什么三、传输层UDP 和 TCP 的特点UDP 首部格式TCP 首部格式TCP 的三次握手TCP 的四次挥手TCP 流量控制TCP 拥塞控制三、网络层IP 数据报格式…

李宏毅《机器学习》Bert笔记

李宏毅《机器学习》Bert笔记和工作原理解释1.参考2. self-supervised learning--bert3. bert的一些用法3.1情感分析3.2词性标注3.3常识推理和NLI3.4 QA问题4.bert的工作原理解释1.参考 bert论文 李宏毅《机器学习》自监督训练-bert 2. self-supervised learning–bert Bert模…

图解JVM

1.JVM的一些概念 ​ JVM:Java虚拟机,可以存储代码以及在运行是可以动态调用一些指令的项目集。 ​ JDK:Java运行时所依赖的环境,[jar包、类]。 ​ JVM的作用:负责将.class文件解释成机器能够识别的机器码。 ​ JVM的位置:在操作系统之上,介于应用与系统之间。 ​ 机器码…

python自动化测试学习路线(从入门到精通)

目录&#xff1a;导读 一、Python的应用场景 二、自动化测试的那些事 三、主流自动化测试框架 三、Python自动化测试学习路线 写在最后 一、Python的应用场景 Python用于简单脚本编程&#xff0c;如编写2048小游戏或12306的自动抢票软件&#xff1b; Python用于系统编程&…

数组(二)-- LeetCode[303][304] 区域和检索 - 数组不可变

1 区域和检索 - 数组不可变 1.1 题目描述 题目链接&#xff1a;https://leetcode.cn/problems/range-sum-query-immutable/ 1.2 思路分析 最朴素的想法是存储数组 nums 的值&#xff0c;每次调用 sumRange 时&#xff0c;通过循环的方法计算数组 nums 从下标 iii 到下标 jjj …

javaEE 初阶 — 关于 IPv4、IPv6 协议、NAT(网络地址转换)、动态分配 IP 地址 的介绍

文章目录1. IPv42. IPv63. NAT4. 动态分配 IP 地址1. IPv4 在互联网的世界中只有 0 和1 &#xff0c;所以每个人都有一个由 0 和 1 组成的地址来让别人找到你。 这段由 0 和 1 组成的地址叫 IP 地址&#xff0c;这是互联网的基础资源&#xff0c;可以简单的理解为互联网的土地。…

《Qt 6 C++开发指南》简介

我们编写的新书《Qt 6 C开发指南》在2月份终于正式发行销售了&#xff0c;这本书是对2018年5月出版的《Qt 5.9 C开发指南》的重磅升级。以下是本书前言的部分内容&#xff0c;算是对《Qt 6 C开发指南》的一个简介。1&#xff0e;编写本书的目的《Qt 5.9C开发指南》是我写的第一…

(三)代表性物质点邻域的变形分析

本文主要内容如下&#xff1a;1. 伸长张量与Cauchy-Green 张量2. 线元长度的改变2.1. 初始/当前构型下的长度比2.2. 主长度比与 Lagrange/Euler 主方向2.3. 初始/当前构型下任意方向的长度比3. 线元夹角的改变4. 面元的改变5. 体元的改变1. 伸长张量与Cauchy-Green 张量 由于变…

新C++(10):Map\Set的封装

"湖人总冠军"一、Map\Set的介绍Set是C标准库中的一种关联容器。所谓关联容器就是通过键&#xff08;key&#xff09;来读取和修改元素。与map关联容器不同&#xff0c;它只是单纯键的集合。取自这里Map是STL 的一个关联容器&#xff0c;它提供一对一&#xff08;其中…

第二回:艺术画笔见乾坤

import numpy as np import pandas as pd import re import matplotlib import matplotlib.pyplot as plt from matplotlib.lines import Line2D from matplotlib.patches import Circle, Wedge from matplotlib.collections import PatchCollection一、概述 1. matplotlib…

软件测试:用“bug”来表示“在电脑程序里的错误”

计算机基础知识计算机&#xff08;personal computer&#xff09;俗称电脑&#xff08;pc&#xff09;&#xff0c;是现代一种用于高速计算的电子机器&#xff0c;可以进行数值计算&#xff0c;又可以进行逻辑判断&#xff0c;还具有存储记忆功能&#xff0c;且能够按照程序的运…

【模拟集成电路】频率综合器(Frequency Synthesizer,FS)设计

应用于无线局域网的频率综合器设计前言频率综合器简介各部分链接链接&#xff1a;前言 本文主要内容是对频率综合器或称为PLL 做出简单介绍&#xff0c;为课程设计部分章节内容&#xff0c;后需给出各部分的设计方案&#xff0c;以及测试结果。 频率综合器简介 无线收发系统中…