首页 > 其他分享 >数据传输序列化 Json和protobuf

数据传输序列化 Json和protobuf

时间:2024-11-21 18:47:49浏览次数:3  
标签:const protobuf json Value Json 序列化 string

1 序列化

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程,与之相对应的过程称之为反序列化(Unserialization)。序列化和反序列化主要用于解决在跨平台和跨语言的情况下, 模块之间的交互和调用,但其本质是为了解决数据传输问题

 1.1 如何实现序列化

  • 要有原始数据
    • 复合数据类型——常见
    • 基础数据类型
  • 获得的序列化后数据有什么作用?
    • 目的:进行分发,分发到不同的平台或终端,保证不同的平台能正常解析
    • 例如:网络传输、磁盘拷贝
  • 序列化是为了解决传输问题,不是为了加密
  • 序列化整体过程:
    • 发送端
      • 原始数据 -> 序列化(编码)-> 特殊格式的字符串
      • 发送这个字符串
    • 接收端
      • 接收数据
      • 特殊格式的字符串 -> 反序列化(解码)-> 原始数据
      • 对原始数据进行处理

1.2 为什么需要序列化

在网络传输过程中会发送什么问题?

  • 平台不同的跨平台传输,例如32bit/64bit的计算机对应的模型数据结构占的内存不同
  • 如果传输的不是字符串,就会有字节序的问题
    • 我们知道字符在内存中占一个字节,所以字符没有字符序问题
    • 如果发的数据时结构体类型,直接调用linux的send函数就会在传输中出现问题
  • 采用的编程语言不同也会导致相同数据类型所占的内存不同
    • C -> char -> 占一个字节
    • java -> char -> 占两个字节
  • 字节对齐问题

1.3 常用的序列化方式

  • XML(Extensible Markup Language )
  • Json( JavaScript Object Notation )
  • Protocol Buffer
  • ASN.1 抽象语法标记(Abstract Syntax Notation One)
  • boost 序列化的类

本文主要讨论的时Json和protobuf的使用方法

2 JSON

JSON起源于弱类型语言Javascript,它的产生来自于一种称之为"关联数组(Associative array)"的概念,其本质是就是采用"键值对"的方式来描述对象。JSON格式保持了XML的人眼可读的优点,非常符合工程师对对象的理解。相对于XML而言,序列化后的数据更加简洁(XML所产生序列化之后文件的大小接近JSON的两倍),而且其协议比较简单,解析速度比较快。JSON格式具备Javascript的先天性支持,所以被广泛应用于Web browser的应用常景中,是Ajax的事实标准协议。

2.1 Json组合数据格式

  •  json数组
    • 类似于C++数组
    • 区别:但是json数据内的数据类型可以不同
    • 可包含的数据类型: [int, double, float, bool, string, char*, json数组, json对象]
      • 举例:[12, 13.45, "hello, world", true, false, [1,2,"aa"], {"a":"b"}]
  • json对象
    • 使用{}表示
    • 分为两部分:key,value
      • 键值对间使用逗号间隔
      • key:必须时字符串
      • value:要保存的数据,可以有多种类型
        •  整形, 浮点, 字符串, 布尔, json数组, json对象

2.2 jsoncpp类使用

1)在内存中组织json格式的数据写入磁盘

  • 一般在组织数据前我们都必须确定先确定根节点——即使用json数据还是使用json对象
  • jsoncpp类中给我们提供了以下的方法供我们调用
// Json支持的数据类型
Type = {int, double, float, string, char*, bool, JsonArray, JsonObject}
// 构造函数
  Value(ValueType type = nullValue);
  Value(Int value);
  Value(UInt value);
#if defined(JSON_HAS_INT64)
  Value(Int64 value);
  Value(UInt64 value);
#endif // if defined(JSON_HAS_INT64)
  Value(double value);
  Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.)
  Value(const char* begin, const char* end);

// 将Value对象转换成对应类型的数据
  Int asInt() const;
  UInt asUInt() const;
#if defined(JSON_HAS_INT64)
  Int64 asInt64() const;
  UInt64 asUInt64() const;
#endif // if defined(JSON_HAS_INT64)
  LargestInt asLargestInt() const;
  LargestUInt asLargestUInt() const;
  float asFloat() const;
  double asDouble() const;
  bool asBool() const;

// 判断Value对象中存储的数据的类型
  bool isNull() const;
  bool isBool() const;
  bool isInt() const;
  bool isInt64() const;
  bool isUInt() const;
  bool isUInt64() const;
  bool isIntegral() const;
  bool isDouble() const;
  bool isNumeric() const;
  bool isString() const;
  bool isArray() const;
  bool isObject() const;

// 取值 
// 格式化 -> 将对象转换为字符串
// 适合于查看信息或者写文件
std::string toStyledString() const;

使用举例:

#include <iostream>  
#include <fstream>  
#include <json/json.h>  

int main() {  
    // 创建一个 JSON 对象  
    Json::Value root;   

    // 填充 JSON 数据  
    root["name"] = "Alice";  
    root["age"] = 30;  
    root["is_student"] = false;  

    // 创建一个 JSON 数组  
    Json::Value courses(Json::arrayValue);  
    courses.append("Mathematics");  
    courses.append("Physics");  
    courses.append("Computer Science");  
    root["courses"] = courses;  

    // 创建一个嵌套的 JSON 对象  
    Json::Value address;  
    address["street"] = "123 Main St";  
    address["city"] = "Anytown";  
    address["zip"] = "12345";  
    root["address"] = address;  

    // 使用 toStyledString() 方法将 JSON 对象转换为字符串  
    std::string jsonString = root.toStyledString();  

    // 将 JSON 数据写入文件  
    std::ofstream file("data.json");  
    if (!file.is_open()) {  
        std::cerr << "Error opening file for writing" << std::endl;  
        return 1;  
    }  

    // 写入 JSON 字符串到文件  
    file << jsonString;  
    file.close();  

    std::cout << "JSON data has been written to data.json" << std::endl;  

    return 0;  
}
  • 我们可以尝试阅读以上代码,是否能在脑海中构造出json格式的数据
  • 上面例子所示我们使用的时json对象为根节点。即所有的数据都是被一个大括号{}包含
    • json的根对象中有json数据(key和value组成的简单数据)
    • 有json数组courses
    • 有嵌套的json对象address
{  
   "address" : {  
      "city" : "Anytown",  
      "street" : "123 Main St",  
      "zip" : "12345"  
   },  
   "age" : 30,  
   "courses" : [  
      "Mathematics",  
      "Physics",  
      "Computer Science"  
   ],  
   "is_student" : false,  
   "name" : "Alice"  
}

2)读取磁盘中的json格式的文件写入内存

当项目或者某些业务场景中的某些数据要使得更加灵活时,或者是这些数据经常会改变时。我们常常会将这种数据写入json的配置文件中,例如TCP传输时候的端口、地址等信息,我们只需要读取其中json文件的内容进行初始化,有需要更改时更改json文件的数据即可

  • jsoncpp中给我们提供了reader类,我们可以使用该类进行解析数据
// json格式字符串 -> Value对象
// c++
bool parse(const std::string& document, Value& root, bool collectComments = true);
	参数:
		- document: json字符串, 传入参数
		- root: 传出参数, 转换完成之后的Value对象
// c用法
bool parse(const char* beginDoc, const char* endDoc, 
           Value& root, bool collectComments = true);
	参数:
		- beginDoc: 字符串起始地址
		- endDoc: 字符串结束地址
		- root: 传出参数, 转换完成之后的Value对象
// c++用法
bool parse(std::istream& is, Value& root, bool collectComments = true);
	参数:
		- is: 文件流对象, 使用这个流对象打开一个磁盘文件
		- root: 传出参数, 转换完成之后的Value对象

举例:

Server::Server(string json)
{
    //参数json为要打开的文件名字
	ifstream ifs(json);
	Reader r;
	Value val;
	r.parse(ifs, val);
	m_port = val["port"].asInt();
}
  • reader类使用起来其实非常简单,比如在上面的构造函数中,我们可以通过读取json文件对某些变量进行初始化(比如上面初始化的是通信的端口) 。只需要指定一个文件流和一个value值即可。文件流用于读文件,value对象用于传出。我们就可以调用reader类的parse函数将其解析出来

3 protobuf

Protocol Buffer( 简称 Protobuf) 是Google公司内部的混合语言数据标准,它是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或RPC数据交换格式。Protobuf是一个纯粹的展示层协议,可以和各种传输层协议一起使用,Protobuf的文档也非常完善。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。Protobuf支持的数据类型相对较少,不支持常量类型。由于其设计的理念是纯粹的展现层协议,目前并没有一个专门支持Protobuf的RPC框架。

更多资料可查看:https://developers.google.com/protocol-buffers/

3.1 protobuf数据组织格式

假设我们要序列化的数据为一个结构体(当然protobuf数据一般都是客户端和服务端相互协商):

// 要序列化的数据
struct Person
{
    int id;
    string name;
    string sex;	// man woman
    int age;
};
// protobuf的版本
syntax = "proto3";	// proto2
// 组织Persion结构体
// 语法格式
message 关键字(相当于被创建出的类的名字)
{
	// 成员变量
	数据类型 变量名 = 变量的编号;	// 编号从1开始, 不能重复
}
 
// .proto文件 生成 c++ 类的命令
protoc proto文件名 --cpp_out=生成目录

则自己创建一个.proto文件中定义以下数据结构

syntax = "proto3";
message Person
{
    int32 id = 1;   // 编号从1开始
    bytes name = 2;
    string sex = 3;
    int32 age = 4;
}

3.2 protobuf类的使用

在上面我们在命令行中输入"protoc proto文件名 --cpp_out=生成目录"命令后,就会产生两个protobuf类的文件(.h和.cc)。使用这两个类就可以对数据进行操作

#include <iostream>
#include "Person.pb.h"
using namespace std;
 
int main()
{
	//1.创建对象并且初始化
	Person p;
	p.set_age(18);
	p.set_name( "李四");
	p.set_sex("man");
	p.set_id(1);
	//2.将person数据进行序列号
	string output;
	p.SerializeToString(&output);
	cout << "序列化后的数据:" << output << endl;
	//3.数据传输
	// 网络传输,设置监听等等然后send数据,此处忽略
	//4.接收数据,解析->解码->原始数据
	Person pp;
	pp.ParseFromString(output);
	Info in = pp.info();
	//5.处理原始数据 ->打印
	cout << "名字为:" << pp.name() << ","<<endl;
	cout << "年龄为:" << pp.age() << endl;
	cout << "性别为:" << pp.sex() << endl;
	cout << "id号:" << pp.id() << endl;
	return 0;
}
  •  set_xxxx:可以对我们的规定的数据赋值
  • 调用protobuf类为我们提供的SerializeToString()将我们指定的数据进行序列化,括号内是一个传出参数,即为序列化后的参数
  • 序列化后就可以进行网络发送,此处省略
  • 获得数据后同样传出对象后调用protobuf类为我们提供的ParseFromString()方法,将序列化好的数据进行发序列化,就可以得到原始数据

3.3 以面向对象思想写protobuf类

// 该结构体主要是方便参数传入
struct Info
{
    int id;
    string name;
    string sex;	//
    int age;
};

class My_Protobuf
{
public:
    // 空构造
    My_Protobuf();
	// 构造出的对象用于 解码 的场景
    My_Protobuf(string enstr) {InitMyProto(enstr);}
    // 构造出的对象用于 编码 场景
    My_Protobuf(Info* info){InitMyProto(info);}
    ~My_Protobuf();
	// 编码时候使用
    void InitMyProto(Info* info)
    {
        m_person.set_id(info->id);
        m_person.set_name(info->name);
        m_person.set_sex(info->sex);
        m_person.set_age(info->age);        
    }
	// 解码使用
    void InitMyProto(string enstr)
    {
        m_enstr = enstr;
    }
    // 编码
    string encodeMsg()
    {
       string output;
       m_person.SerializeToString(&output);
       return output;
    }
    // 解码
    void* decodeMsg()
    {
        m_person.ParseFromString(m_enstr);
        retrun &m_person;
    }
private:
    //Person这个类由protobuf产生
    Person* m_person;
    string m_enstr;
}

3.4 protobuf其他用法

1.repeated
repeated bytes name = 2;// name可以在程序中创建多个, 在程序中作为动态数组来使用

2.enum枚举
enum Color
{
	Red = 0;	// protbuf中第一个枚举值必须为0
	Green = 6;
	Blue = 9;
}

3.命名空间
package itcast;	// 某个类属于itcast这个命名空间

假设在一个protobuf文件中有以下信息,通过命令行会生成6个对应的类,我们将其进行封装 

syntax="proto3";
package pb;
message SyncPid
{
    int32 Pid = 1;
    string Username = 2;
}

message Player
{
    int32 Pid = 1;
    Position P=2;
    string Username = 3;
}

message SyncPlayers
{
    /*嵌套多个子消息类型的Player消息*/
    repeated Player ps = 1;
}

message Position
{
    float X=1;
    float Y=2;
    float Z=3;
    float V=4;
}
message BroadCast
{
    int32 Pid=1;
    int32 Tp=2;
    /*根据Tp不同会选择一下其中一个
        content 消息内容
        Position 位置
    */
    oneof data{
        string content=3;
        Position P=4;
        /*预留*/
        int32 ActionData=5;
    }
    string Username=6;
}

message Talk
{
    string Content = 1;
}
  • 这里生成的6个类对应的不同的消息类型,所以我们构造的时候需要通过某些标志位构造不同的消息
  • 在protobuf的所有消息中都i有一个共同的父类google::protobuf::Message类,因此我们可以通过这个父类指针去获得上面6个类中的任意数据
class MyMsg{
public:
    enum MyMsgType {
        MY_MSG_LOGON_SYNCPID = 1,
        MY_MSG_TALK_CONTENT = 2,
        MY_MSG_NEW_POSTION = 3,
        MY_MSG_BROADCAST = 200,
        MY_MSG_LOGOFF_SYNCPID = 201,
        MY_MSG_SUR_PLAYER = 202,
    } m_MsgType;
    google::protobuf::Message *m_poMsg = NULL;
    MyMsg(MyMsgType _Type, google::protobuf::Message * _msg);
    MyMsg(MyMsgType _Type, std::string _szInputData);
    ~MyMsg();
 
    std::string Serialize();
};
 
  • 通过一个枚举值规定不同的数据类型,根据不同数据类型构造不同的消息
  • m_poMsg:类的成员,维护一个所有protobuf类的父类指针
  • 构造函数
    • 构造函数1:通过传进来的类型和子类对象参数,直接初始化m_MsgType和m_poMsg就可以直接获得对应消息
    • 构造函数2:根据Type不同new出不同的protobuf对象,然后用创建出来的对象调用ParseFromString方法将参2的string解析获得对应消息
MyMsg::MyMsg(MSG_TYPE _type, google::protobuf::Message* _msg):m_MsgType(_type),m_poMsg (_msg)
{
	
}

MyMsg::MyMsg(MSG_TYPE type, string str): m_MsgType(type)
{
	switch (type)
	{
	case GameMsg::MSG_TYPE_LOGIN_ID_NAME:
		m_Msg = new pb::SyncPid();
		break;
	case GameMsg::MSG_TYPE_CHAT_CONTENT:
		m_Msg = new pb::Talk();
		break;
	case GameMsg::MSG_TYPE_NEW_POSITION:
		m_Msg = new pb::Position();
		break;
	case GameMsg::MSG_TYPE_BROADCAST:
		m_Msg = new pb::BroadCast();
		break;
	case GameMsg::MSG_TYPE_LOGOFF_ID_NAME:
		m_Msg = new pb::SyncPid();
		break;
	case GameMsg::MSG_TYPE_SRD_POSTION:
		m_Msg = new pb::SyncPlayers();
		break;
	default:
		break;
	}
	m_Msg->ParseFromString(str);
}

string MyMsg::Serialize()
{
	string pret;
	m_Msg->SerializeToString(&pret);
	return pret;
}

MyMsg::~MyMsg()
{
	if (m_Msg != nullptr)
	{
		delete m_Msg;
		m_Msg = nullptr;
	}
}

标签:const,protobuf,json,Value,Json,序列化,string
From: https://blog.csdn.net/C_J_L12580/article/details/143868458

相关文章

  • 【Python】0基础学Python——函数参数传递、函数细分、类的信息、元类、自定义元类、p
    0基础学Python——函数参数传递、函数细分、类的信息、元类、自定义元类、pickle序列化、序列化升级函数参数传递参数传递类型标注函数细分task任务型函数consumer消费型函数functional功能型函数类的信息元类type作用自定义元类pickle序列化序列化反序列化序列化升......
  • python: Serialize and Deserialize complex JSON using jsonpickle
     #encoding:utf-8#版权所有2024©涂聚文有限公司#许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎#SerializeandDeserializecomplexJSONinPython#描述:pipinstalljsonpicklehttps://github.com/jsonpickle/jsonpickle#Author:geovindu,......
  • PHP反序列化(ing)
    类类的结构<?phpclasshero{//定义类(类名)var$name;//声明成员变量,var是一种修饰符var$sex;functionjineng($var1){//声明成员函数(方法)echo$this->name;//使用预定义$this调用成员变量echo$var1;}......
  • Jupyter Latex+VScode json配置
    {//何时构建LaTeX项目onFileChange:文件更改时编译文件onSave:保存代码时自动编译文件never:从不自动编译)"latex-workshop.latex.autoBuild.run":"never",//显示上下文菜单"latex-workshop.showContextMenu":true,//启用智能感知功能包功能"l......
  • Spark 分布式计算中网络传输和序列化的关系(二)
    在Spark分布式计算中,网络传输和序列化是数据处理的重要组成部分。Spark通过将任务划分为多个分布式计算节点来处理数据,而序列化和网络传输直接影响计算性能和数据交互效率。1.序列化在Spark中的作用序列化是Spark将数据对象转换为字节流以进行网络传输或存储的......
  • C#支持将json中的多种类型反序列化为object类型
    我们知道json中的字段是弱类型的,也就是说json中的一个字段不用事先声明具体的类型,这就导致json中某个字段的值有可能是字符串,也有可能是数字,也有可能是布尔值,其它等。。。但是C#是强类型的,定义一个C#类中字段的时候,必须声明它是什么类型,所以我们可以将json中有不同类型的字段在C#......
  • PI发布rest,json接口
    PI接口的开发分成两个部分,第一个部分是ESB(EnterpriseServicesBuilder)部分,这里注意做数据结构定义,接口导入,字段关系映射,定义接口等。                        第二部分是IB(IntegrationBuilder)部分,这里主要做管道定义,服务......
  • 【Java】对象和JSON字符串之间的转换 全网最清晰!
    在Java中,将对象转换为JSON字符串通常使用一些流行的JSON库,如Jackson或Gson。这两个库都非常强大,支持将Java对象转换为JSON字符串,也支持反向操作。接下来我会介绍一个基于Jackson的工具类,它可以非常方便地实现Java对象和 JSON字符串之间的相互转换。1.引......
  • RedisTemplate RedisConfig 序列化方式 fastjson2
    SpringDataRedis为我们提供了下面的Serializer:GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。序列化方式对比:JdkSerializationRedisSerializer:使用JDK......
  • Java序列化
    在Java的世界中,序列化是一个不可或缺的概念,它允许我们将对象的状态保存到文件中,或者通过网络传输到其他JVM实例。作为一名Java技术专家和架构师,深入理解序列化机制对于构建高效、可靠的系统至关重要。本文将带你从基础到高级,全面掌握Java序列化。Java序列化基础什么是序列化......