个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创计算机网络基础(3)_应用层自定义协议与序列化
收录于专栏【计算机网络】
本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
1. 应用层
再谈协议 :
网络版计算器 :
2. 序列化和反序列化
3. 重新理解 read, write, recv, send 和 tcp 为什么支持全双工
4. 流式数据处理
5. Jsoncpp
特性 :
安装
6. Json实现序列化和反序列化
序列化 :
反序列化 :
总结 :
7. Json::Value
构造函数 :
访问元素
类型检查
赋值和类型转换
数组和对象操作
1. 应用层
我们程序员写的一个个解决我们实际问题, ,满足我们日常需求的网络程序, 都是在应用层.
再谈协议 :
协议是一种 "约定", socket api 接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的, 如果我们要传输一些 "结构化的数据" 怎么办呢?
其实, 协议就是双方约定好的结构化的数据~
网络版计算器 :
例如, 我们需要实现一个服务器版的加法器, 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
约定方案一 :
客户端发送一个形如 "1 + 1" 的字符串
这个字符串中有两个操作数, 都是整形
两个数字之间会有一个字符是运算符, 运算符只能是 "+"
数字和运算符之间没有空格
.......
定义方案二 :
定义结构体来表示我们需要交互的信息
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转换回结构体
这个过程叫做 "序列化" 和 "反序列化"
2. 序列化和反序列化
无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是 OK 的, 这种约定, 就是 应用层协议
如果我们采用方案二, 我们也要体现协议定制的细节
我们要引用序列化和反序列化, 只不过我们直接采用了现成的方案 --- jsoncpp 库
我们要对 socket 进行字节流的读取处理
3. 重新理解 read, write, recv, send 和 tcp 为什么支持全双工
所以 :
1. 在任何一台主机上, TCP 连接既有发送缓冲区, 又有接收缓冲区, 所以, 在内核中, 可以在发消息的同时, 也可以收消息, 即全双工
2. 这就是为什么一个 tcp sockfd 读写都是它的原因
3. 实际数据什么时候发, 发多少, 出错了怎么办, 由 TCP 控制, 所以 TCP 叫做传输层控制协议
4. 流式数据处理
你如何保证你每次读取就能读完请求缓冲区的所有内容?
你怎么保证读取完毕或者读取没有完毕的时候, 读到的就是一个完整的请求呢?
处理 TCP 缓冲区中的数据, 一定要保证正确处理请求
const std::string ProtSep = " ";
const std::string LineBreakSep = "\n";
// "len\nx op y\n" : \n 不属于报文的一部分,约定std::string Encode(const std::string &message)
{std::string len = std::to_string(message.size());std::string package = len + LineBreakSep + message +LineBreakSep;return package;
}
// "len\nx op y\n" : \n 不属于报文的一部分,约定// 我无法保证 package 就是一个独立的完整的报文
// "l
// "len
// "len\n
// "len\nx
// "len\nx op
// "len\nx op y
// "len\nx op y\n"
// "len\nx op y\n""len
// "len\nx op y\n""len\n
// "len\nx op
// "len\nx op y\n""len\nx op y\n"
// "len\nresult code\n""len\nresult code\n"
bool Decode(std::string &package, std::string *message)
{// 除了解包,我还想判断报文的完整性, 能否正确处理具有"边界"的报文auto pos = package.find(LineBreakSep);if (pos == std::string::npos)return false;std::string lens = package.substr(0, pos);int messagelen = std::stoi(lens);int total = lens.size() + messagelen + 2 * LineBreakSep.size();if (package.size() < total)return false;// 至少 package 内部一定有一个完整的报文了!*message = package.substr(pos + LineBreakSep.size(),messagelen);package.erase(0, total);return true;
}
所以完整的处理过程为 :
5. Jsoncpp
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库, 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能, Jsoncpp 是开源的, 广泛用于各种需要处理 JSON 数据的 C++ 项目中.
特性 :
1. 简单易用 : Jsoncpp 提供了直观的 API, 使得处理 JSON 数据变得简单.
2. 高性能 : Jsoncpp 的性能经过优化, 能够高效地处理大量 JSON 数据.
3. 全面支持 : 支持 JSON 标准中的所有数据类型, 包括对象, 数组, 字符串, 数字, 布尔值和 null
4. 错误处理 : 在解析 JSON 数据时, Jsoncpp 提供了详细的错误信息和位置, 方便开发者调试
安装
C++
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
6. Json实现序列化和反序列化
序列化 :
序列化指的是将数据结构或对象转换为一种格式, 以便在网络上传输或存储到文件中. Jsoncpp 提供了多种方式进行序列化 :
1. 使用 Json:Value 的 toStyledString 方法 :
优点 : 将 Json::Value 对象直接转换为格式化的 JSON 字符串
实例 :
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";std::cout << root << std::endl;return 0;
}
2. 使用 Json::StreamWriter : (推荐)
优点 : 提供了更多的定制选项, 如缩进, 换行符等
示例 :
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder swb;// StreamWriter 的工厂std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::stringstream ss;sw->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}
3. 使用 Json::FastWrite :
优点 : 比 StyleWriter 更快, 因为它不添加额外的空格和换行符
示例 :
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::FastWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
}
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";// Json::FastWriter writer;Json::StyledWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
}
反序列化 :
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象, Jsoncpp 提供了以下方法进行反序列化 :
1. 使用Json::Reader :
优点 : 提供详细的错误信息和位置, 方便调试.
示例 :
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{// JSON 字符串std::string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";// 解析 JSON 字符串Json::Reader reader;Json::Value root;// 从字符串中读取 JSON 数据bool parsingSuccessful = reader.parse(json_string, root);if (!parsingSuccessful){// 解析失败,输出错误信息std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;return 1;}// 访问 JSON 数据std::string name = root["name"].asString();int age = root["age"].asInt();std::string city = root["city"].asString();// 输出结果std::cout << "Name: " << name << std::endl;std::cout << "Age: " << age << std::endl;std::cout << "City: " << city << std::endl;return 0;
}
2. 使用 Json::CharReader 的派生类 (不推荐, 上面的足够了)
在某些情况下, 你可能需要更精细地控制解析过程, 可以直接使用 Json::CharReader 的派生类.
但通常情况下, 使用 Json::parseFromStream 或 Json::Reader 的 parse 方法足够了.
总结 :
1. toStyledString, StreamWrite 和 FastWrite 提供了不同的序列化选项, 你可以根据具体需求选择使用.
2. Json::Reader 和parseFromStream 函数是 Jsoncpp 中主要的反序列化工具, 它们提供了强大的错误处理机制
3. 在进行序列化和反序列化时, 请确保处理所有可能的错误情况, 并验证输入和输出的有效性
7. Json::Value
Json::Value 是 Jsoncpp 库中一个重要类, 用于表示和操作 JSON 数据结构, 以下是一些常用的 Json::Value 操作列表 :
构造函数 :
• Json::Value():默认构造函数,创建一个空的 Json::Value 对象。
• Json::Value(ValueType type, bool allocated = false):根据给定的ValueType(如 nullValue, intValue, stringValue 等)创建一个Json::Value对象
访问元素
• Json::Value& operator[](const char* key):通过键(字符串)访问对象中的元素。如果键不存在,则创建一个新的元素。
• Json::Value& operator[](const std::string& key):同上,但使用std::string 类型的键。
• Json::Value& operator[](ArrayIndex index):通过索引访问数组中的元素。如果索引超出范围,则创建一个新的元素。
• Json::Value& at(const char* key):通过键访问对象中的元素,如果键不存在则抛出异常。
• Json::Value& at(const std::string& key):同上,但使用std::string类型的键。
类型检查
• bool isNull():检查值是否为 null。
• bool isBool():检查值是否为布尔类型。
• bool isInt():检查值是否为整数类型。
• bool isInt64():检查值是否为 64 位整数类型。
• bool isUInt():检查值是否为无符号整数类型。
• bool isUInt64():检查值是否为 64 位无符号整数类型。
• bool isIntegral():检查值是否为整数或可转换为整数的浮点数。
• bool isDouble():检查值是否为双精度浮点数。
• bool isNumeric():检查值是否为数字(整数或浮点数)。
• bool isString():检查值是否为字符串。
• bool isArray():检查值是否为数组。
• bool isObject():检查值是否为对象(即键值对的集合)。
赋值和类型转换
• Json::Value& operator=(bool value):将布尔值赋给Json::Value 对象。
• Json::Value& operator=(int value):将整数赋给 Json::Value 对象。
• Json::Value& operator=(unsigned int value):将无符号整数赋给Json::Value 对象。
• Json::Value& operator=(Int64 value):将 64 位整数赋给Json::Value对象。
• Json::Value& operator=(UInt64 value):将 64 位无符号整数赋给Json::Value 对象。
• Json::Value& operator=(double value):将双精度浮点数赋给Json::Value 对象。
• Json::Value& operator=(const char* value):将C 字符串赋给Json::Value 对象。
• Json::Value& operator=(const std::string& value):将std::string赋给 Json::Value 对象。
• bool asBool():将值转换为布尔类型(如果可能)。
• int asInt():将值转换为整数类型(如果可能)。
• Int64 asInt64():将值转换为 64 位整数类型(如果可能)。
• unsigned int asUInt():将值转换为无符号整数类型(如果可能)。
• UInt64 asUInt64():将值转换为 64 位无符号整数类型(如果可能)。
• double asDouble():将值转换为双精度浮点数类型(如果可能)。
• std::string asString():将值转换为字符串类型(如果可能)
数组和对象操作
• size_t size():返回数组或对象中的元素数量。
• bool empty():检查数组或对象是否为空。
• void resize(ArrayIndex newSize):调整数组的大小。
• void clear():删除数组或对象中的所有元素。
• void append(const Json::Value& value):在数组末尾添加一个新元素。
• Json::Value& operator[](const char* key, const Json::Value& defaultValue = Json::nullValue):在对象中插入或访问一个元素,如果键不存在则使用默认值。
• Json::Value& operator[](const std::string& key, const Json::Value& defaultValue = Json::nullValue):同上,但使用std::string类型的