破解wordpress网站密码,衡水做企业网站的公司,爱网站长尾,国内比百度好的搜索引擎Linux网络#xff1a;序列化与反序列化 序列化与反序列化jsonjsoncppValue对象序列化反序列化WriterReader 序列化与反序列化
在网络通信中#xff0c;最重要的就是通过接口#xff0c;将数据通过网络发送给另一台主机。那么另一台主机收到数据后#xff0c;就可以对数据进… Linux网络序列化与反序列化 序列化与反序列化jsonjsoncppValue对象序列化反序列化WriterReader 序列化与反序列化
在网络通信中最重要的就是通过接口将数据通过网络发送给另一台主机。那么另一台主机收到数据后就可以对数据进行处理了。
但是网络通信中数据不是简单的将所有的数据堆砌在一起然后一起发送出去。相反的数据应该以特定的形式组织起来才能让接收方进行合理的解析。
比如以下数据
你好2024.10.21cool boy这是一个叫做cool boy的用户在2024.10.21发送了一条你好的消息。对于人来说这是很好理解的但是对于计算机来说这就很难处理了。比如说它也可以被解析为用户名是你好的用户发送了一条叫做cool boy的消息。用户名、时间、消息内容这三个字段的分界符在哪里如何区分这三个区域
对于UDP来说一个消息放在一个报文中一起发送。但是对于TCP来说它是面向字节流的那么就可以能出现多个消息的粘包问题
你好2024.10.21cool boy我是一个程序员2024.10.21cool boy比如收到这样一条TCP字节流消息计算机又要如何区分两个报文的边界
可以发现简单的把数据堆在一起这个数据就会难以解析。因此要把数据按照一定的规律合理组织在一起并且约定好各个字段的含义如果是TCP通信还需要约定好一条消息的定界符避免粘包问题。
按照一定的规则来组织数据这种行为称为序列化。而从组织好的数据中还原出目标信息称为反序列化。
当前流行的序列化协议
JSONXML二进制格式
其中json全称JavaScript Object Notation即JavaScript 对象 表示法顾名思义它是由JavaScript提出的一种序列化规范这是一种文本形式的协议以字符串的形式存储与解析数据。
比如刚才的消息可以表示为
{message: 你好,time: 2024.10.21,name: cool boy
}这样整个消息就被分为了三个部分并且每个字段都以键值对的形式表示。这样就可以很明确的表示你好是一条消息而cool boy是用户名。而整条报文的最外层有一个{}这就可以起到TCP的定界符的作用。
除去json还有很多数据序列化格式比如二进制格式可以使用protobuf等。接下来博客讲解json的序列化格式以及它的C接口。 json
json可以保存标量类型数组对象三种形式的数据规则如下
数据以键值对key: value的形式存储多条数据使用,隔开key的类型必须是字符串value的类型可以是标量类型数组对象数组用[]表示对象用{}表示
对于整个json消息它可以是数组或对象。
数组形式
[hello,world,true,3.14
]对象形式
{name: cool boy,age: 12,adult: false
}首先数组内部的数据不需要以键值对的形式存储而数组最外侧使用[]表示。在数组中可以存储任何形式的数据比如true是布尔类型3.14是浮点型。
而在对象形式中每个字段都要以key:value的形式存储最外侧使用{}对象中也可以存储任意标量类型。
另外的数组与对象之间还可以进行嵌套
[{name: 张三,age: 12,adult: false},{name: 李四,age: 20,adult: true}
]比如这是一个数组内部有两个对象这就是数组内嵌套对象。
{name: 张三,age: 12,friend: [李四,王五,赵六]
}这是一个对象在对象中friend存储的是一个数组数组内又存储了多个字符串。
想要处理json数据还是比较麻烦的比如说要考虑括号匹配问题数据类型的转化问题。但是不用担心目前的主流语言都要专门支持json格式的库其中C可以使用jsoncpp库完成数据的序列化与反序列化。 jsoncpp
在Linux中下载jsoncpp非常简单以Ubuntu为例
sudo apt install libjsoncpp-dev执行完指令就可以使用该库了只需包含头文件
#include jsoncpp/json/json.hValue对象
jsoncpp的所有操作都基于Json::Value类该类用于保存一个json格式的对象。
基于C的操作符重载特性Json::Value类实现了operator[]完成对元素的增删改函数声明如下
Value operator[](const char* key);
Value operator[](const String key);这两个接口是对json对象的操作[]内放key值而key必须是一个字符串此处支持char*和std::string两者格式的字符串。
与std::map用法一致如果[]内的值存在那么就返回该值如果不存在就添加该值
Json::Value root;
root[name] 张三;
root[age] 12;std::cout root[age] std::endl;以上代码中创建了一个json对象root并设置了“name”和age属性最后又获取并输出gae。
如果Json::Value存储的是一个json数组那么使用以下两个接口
Value operator[](ArrayIndex index);
Value operator[](int index);这两个接口根据下标index返回元素的引用下标从0开始。
如果存储了json数组那么就不能通过operator[key] value的形式插入值了因为数组不需要key值此时使用以下接口
void append(const Json::Value value);只需要append(value)即可在数组尾插一个元素。
Json::Value root;
root.append(张三);
root.append(李四);
root.append(王五);
root.append(赵六);std::cout root[2] std::endl;以上代码往数组中插入了四个值并输出下标为2的元素。
类型转化
如果仔细观察你会发现上面所有接口操作的都是Json::Value比如append(const Json::Value value)。
其实Json::Value可以转化为绝大部分的标量类型
Json::Value operator(bool value);
Json::Value operator(int value);
Json::Value operator(unsigned int value);
Json::Value operator(Int64 value);
Json::Value operator(UInt64 value);
Json::Value operator(double value);
Json::Value operator(const char* value);
Json::Value operator(const std::string value);也就是说之前的所有接口其实Json::Value这个参数可以表达所有的标量类型因此不论是数组的appent还是对象的operator[]都可以插入任意标量类型的数据这些标量数据最后会转化为Json::Value。
数组和对象操作
再补充一些数组和对象的通用接口
size_t size();
bool empty();
void clear();size返回数组或对象中元素数量empty检查数组或对象是否为空clear清空数组或对象的所有元素 序列化反序列化
了解如何往Json::Value中填写数据后接下来就要考虑如何进行序列化与反序列化了。
Writer
jsoncpp通过Json::Writer类进行序列化操作但是该类是一个抽象类无法实例化出对象。它有两个派生类StyledWriter和FastWriter这两个类的对象完成具体的序列化操作。
在Json::Writer类中有一个writer函数声明如下
virtual String write(const Value root) 0;这是最核心的序列化函数 0表示这个函数由子类重写也就是StyledWriter和FastWriter对这个函数进行了重写。
函数输入一个Json::Value对象随后会把这个对象进行序列化将序列化好的数据作为返回值。
构造并序列化以下json
[hello,world,12,false,[abc,def],{gender: boy,age: 12}
]这是一个数组内部包含了六个元素其中第五个元素是另一个数组第六个元素是一个对象。
构造对象
Json::Value root;
root.append(hello);
root.append(world);
root.append(12);
root.append(false);Json::Value arr;
arr.append(abc);
arr.append(def);
root.append(arr);Json::Value obj;
obj[gender] boy;
obj[age] 12;
root.append(obj);对于前四个标量数据直接通过append添加到root结尾即可。而对于嵌套的数组和对象要创建另一个Json::Value先进行数据填充在添加到root结尾。
序列化
Json::StyledWriter swriter;
std::string style swriter.write(root);
std::cout StyledWriter: std::endl;
std::cout style std::endl;Json::FastWriter fwriter;
std::string fast fwriter.write(root);
std::cout FastWriter: std::endl;
std::cout fast std::endl;此处的序列化分别实验了StyledWriter和FastWriter它们都可以完成序列化。通过write(root)接口完成序列化后将序列化的结果输出结果
StyledWriter:
[hello,world,12,false,[ abc, def ],{age : 12,gender : boy}
]FastWriter:
[hello,world,12,false,[abc,def],{age:12,gender:boy}]可以看出其实两种序列化形式的数据内容是一样的区别在于格式
StyledWriter会给json数据添加换行格式让数据更加可视化FastWriter去除所有的换行格式整个json就是一行数据
在实际中选用哪一种都可以两者区别其实不大。
现在将以上对象序列化到test.json文件中
Json::StyledWriter swriter;
std::string str swriter.write(root);
std::ofstream ofs(test.json);
ofs str;
ofs.close();使用std::string接收到write返回值后直接通过文件流对象输出到文件即可。 Reader
当需要反序列化数据时就要用到Json::Reader类核心反序列化函数如下
bool parse(const std::string document, Value root,bool collectComments true);
bool parse(IStream is, Value root, bool collectComments true);与Writer不同Reader可以直接使用无需使用它的派生类。不论是通过StyledWriter还是FastWriter序列化的数据Reader都可以直接反序列化。
第一个函数接收一个std::string和一个Json::Value root对象的引用parse直接反序列化字符串内的数据给root对象第三个参数不用管默认为true即可。
第二个函数则是通过文件流IStream is读取数据然后反序列化到root中。
当拿到一个未知的Json::Value又要如何解析这就要用到is和as系列的接口
bool isNull() const; // 检查值是否为为空
bool isBool() const; // 检查是否为布尔类型
bool isInt() const; // 检查是否为整型
bool isInt64() const; // 检查是否为64位整型
bool isUInt() const; // 检查是否为无符号整型
bool isUInt64() const; // 检查是否为64位无符号整型
bool isIntegral() const; // 检查是否为整数或者可转化为整数的浮点数
bool isDouble() const; // 检查是否为浮点数
bool isNumeric() const; // 检查是否为数字整数或浮点数
bool isString() const; // 检查是否为字符串
bool isArray() const; // 检查是否为数组
bool isObject() const; // 检查是否为对象由于拿到Json::Value后无法确定内部的值是对象数组还是一些标量。因此提供了以上is系列接口来检测数据类型防止出现接收数据时类型不匹配导致的错误。
const char* asCString() const;
String asString() const;
Int asInt() const;
UInt asUInt() const;
Int64 asInt64() const;
UInt64 asUInt64() const;
float asFloat() const;
double asDouble() const;
bool asBool() const;当确定了类型后就可以通过以上的as系列接口直接把Json::Value转化为对应的标量类型进行后续操作。
而对于数组可能不知道它有多少个元素可以通过size接口获取下标范围。而对于对象可能不知道他有哪些key此时可以通过以下接口
Members getMemberNames() const;如果Json::Value内部存储的是对象该函数用于获取一个Json::Value的所有key并存储在Json::Value::Members中Json::Value::Members是一个数组可以直接通过下标访问。
对象常用以下格式解析
Json::Value::Members keys root.getMemberNames();
for (int j 0; j keys.size(); j)
{Json::Value item root[keys[j]]// 解析item
}确定root是一个对象后先root.getMemberNames()拿到所有的key。随后通过一个循环遍历所有的keyroot[keys[j]]就是对象root的一个成员随后在对该对象做解析操作即可。
由于不论是标量类型还是数组或者对象都是使用Json::Value表示的所以常用递归的形式来解析
void print(Json::Value root)
{if (root.isInt())std::cout root.asInt() std::endl;else if (root.isBool())std::cout root.asBool() std::endl;else if (root.isString())std::cout root.asString() std::endl;else if (root.isArray()){std::cout [ std::endl;for (int i 0; i root.size(); i)print(root[i]);std::cout ] std::endl;}else if (root.isObject()){std::cout { std::endl;Json::Value::Members keys root.getMemberNames();for (int j 0; j keys.size(); j){std::cout keys[j] : ;print(root[keys[j]]);}std::cout } std::endl;}
}当该函数接收到一个Json::Value root后先判断他是不是基本的标量类型如果是标量类型那么直接输出即可。
如果是数组那么for (int i 0; i root.size(); i)遍历它的所有下标每个元素root[i]有可能是标量嵌套数组嵌套对象。但是没关系这些类型都被统一为了Json::Value直接递归print(root[i])。
对象同理先通过root.getMemberNames()拿到所有的key所有root[keys[j]]就是对象内的元素这个元素同样有可能是标量嵌套数组嵌套对象它们被统一为了Json::Value直接递归print(root[keys[j]])。
以递归形式读取刚才的test.json
void print(Json::Value root)
{if (root.isInt())std::cout root.asInt() std::endl;else if (root.isBool())std::cout root.asBool() std::endl;else if (root.isString())std::cout root.asString() std::endl;else if (root.isArray()){std::cout [ std::endl;for (int i 0; i root.size(); i)print(root[i]);std::cout ] std::endl;}else if (root.isObject()){std::cout { std::endl;Json::Value::Members keys root.getMemberNames();for (int j 0; j keys.size(); j){std::cout keys[j] : ;print(root[keys[j]]);}std::cout } std::endl;}
}int main()
{std::ifstream ifs(test.json);Json::Reader re;Json::Value root;re.parse(ifs, root);print(root);return 0;
}在main函数中打开文件流ifs(test.json)随后re.parse(ifs, root)直接从文件流中读取数据解析到root对象中最后通过print函数递归输出该数据。
输出结果
[
hello
world
12
0
[
abc
def
]
{
age: 12
gender: boy
}
]这个结果没有控制缩进所有格式有点丑。