当前位置: 首页 > news >正文

应用层协议和JSON的使用

应用层协议需要解决两个问题:

1.结构化数据的序列化和反序列化

2.tcp数据传输的粘包问题

注意:

1.结构化数据序列化后作为应用层报文的有效载荷,自定义协议添加的报头可以来解决粘包问题

2.tcp粘包问题导致的原因是由于其发送算法和面向字节流的缓冲区导致的,tcp缓冲区是面向字节流的,要发送的数据write进发送缓冲区,要接收的数据被放进接收缓冲区等待read,发送时由nagle算法和定时发送来进行,当缓冲区中数据达到MSS或没有发送请求却没收到确认应答的数据,那就发送,实在不行靠时钟中断来发送,这就导致了一个应用层报文被发送到服务器tcp缓冲区后,可能够过了,也可能不够,所以需要应用层自定义协议

namespace Protocol {const std::string ProtSep = " ";const std::string LineBreakSep = "\r\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;}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;}class Request {public:Request() : _data_x(0), _data_y(0), _oper(0) {}Request(int x, int y, char op) : _data_x(x), _data_y(y),_oper(op) {}void Debug() {std::cout << "_data_x: " << _data_x << std::endl;std::cout << "_data_y: " << _data_y << std::endl;std::cout << "_oper: " << _oper << std::endl;}void Inc() {_data_x++;_data_y++;}// 结构化数据->字符串bool Serialize(std::string *out){Json::Value root;root["datax"] = _data_x;root["datay"] = _data_y;root["oper"] = _oper;Json::FastWriter writer;*out = writer.write(root);return true;}bool Deserialize(std::string &in) // "x op y" {Json::Value root;Json::Reader reader;bool res = reader.parse(in, root);if(res) {_data_x = root["datax"].asInt();_data_y = root["datay"].asInt();_oper = root["oper"].asInt();}return res;}int GetX() {return _data_x;}int GetY() {return _data_y;}char GetOper() {return _oper;}private:// _data_x _oper _data_y// 报文的自描述字段// "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定// 很多工作都是在做字符串处理!int _data_x;// 第一个参数int _data_y;// 第二个参数char _oper;// + - * / %};class Response {public:Response() : _result(0), _code(0) {}Response(int result, int code) : _result(result),_code(code)比特就业课比特就业课11 / 21 {}bool Serialize(std::string *out) {Json::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);return true;}bool Deserialize(std::string &in) // "_result _code" [) {Json::Value root;Json::Reader reader;bool res = reader.parse(in, root);if(res) {_result = root["result"].asInt();_code = root["code"].asInt();}return res;}void SetResult(int res) {_result = res;}void SetCode(int code) {_code = code;}int GetResult() {return _result;}int GetCode() {return _code;}private:// "len\r\n_result _code\r\n"int _result;// 运算结果int _code;// 运算状态};// 简单的工厂模式,建造类设计模式class Factory {public:std::shared_ptr<Request> BuildRequest() {std::shared_ptr<Request> req =std::make_shared<Request>();return req;}比特就业课比特就业课12 / 21std::shared_ptr<Request> BuildRequest(int x, int y, charop) {std::shared_ptr<Request> req =std::make_shared<Request>(x, y, op);return req;}std::shared_ptr<Response> BuildResponse() {std::shared_ptr<Response> resp =std::make_shared<Response>();return resp;}std::shared_ptr<Response> BuildResponse(int result, intcode) {std::shared_ptr<Response> req =std::make_shared<Response>(result, code);return req;}};
}

1.整个应用层协议命名空间内主要分两部分内容,一个是自定义协议,就是encode和decode两个加报头和去报头的函数,它来解决tcp传输数据的数据粘包问题。而另一部分是现成的JSON方案,用来将结构化数据进行序列化和反序列化

2.encode接口

参数message是结构体序列化后的字符串,需要给这个字符串前面加上长度报头,并且在报头和有效载荷后加上特殊的标识符,这样我们就可以根据标识符来正确读出有效载荷长度,进而读出一个完整应用层报文,这样想的话其实第二个标识符好像没什么用(o.0)

3.decode接口

package里是从tcp缓冲区中用read读出来的新鲜数据,先确定第一个标识符的位置,如果连第一个标识符的位置都确定不了,那就说明肯定没有一个完整的应用层报文,如果确定了,那就读出有效载荷长度,然后算出该应用层报文的总长度,看package能不能满足,能满足则从package这个用户级的消息缓冲区中删去第一个报文,并将有效载荷给到返回型参数message

4.request和response类

这两个类的成员就是结构化的数据,里面的成员函数serialize和deserialize就是序列化和反序列化方法,序列化和反序列化采用JSON库来实现

Jsoncpp库

1. Json::Value 

Json::Value 是 ​​JsonCpp​​ 库中的核心类,Json::Value 可以表示 JSON 的所有数据类型

  • ​对象({})​​:键值对集合
  • ​数组([])​​:有序列表
  • ​字符串("...")​
  • ​数字(intdouble)​
  • ​布尔值(truefalse)​
  • null

创建 JSON 数据​

#include <json/json.h>
#include <iostream>int main() {Json::Value root;// 添加键值对(对象)root["name"] = "Alice";root["age"] = 25;root["is_student"] = true;// 添加数组root["hobbies"].append("reading");root["hobbies"].append("coding");// 嵌套对象root["address"]["city"] = "New York";root["address"]["zip"] = 10001;// 输出 JSONJson::StyledWriter writer;std::string jsonString = writer.write(root); std::cout << jsonString << std::endl;return 0;
}

结果: 

{"name" : "Alice","age" : 25,"is_student" : true,"hobbies" : ["reading", "coding"],"address" : {"city" : "New York","zip" : 10001}
}

访问元素

1.Json::Value& operator[](const char* key):通过键(字符串)访问对象中的键值对。如果键不存在,则创建一个新的键值对。
2.Json::Value& operator[](const std::string& key):同上,但使用
std::string 类型的键。
3.Json::Value& operator[](ArrayIndex index):通过索引访问数组中的元素。如果索引超出范围,则创建一个新的元素。
4.Json::Value& at(const char* key):通过键访问对象中的键值对,如果键不存在则抛出异常。
5.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):在数组末尾添加一个新元素

2.序列化

1.使用 Json::Value toStyledString 方法:
int main() {Json::Value root;root["name"] = "joe";root["sex"] = "男";std::string s = root.toStyledString();std::cout << s << std::endl;return 0;
}$ ./test.exe 
{"name" : "joe","sex" : "男"
}
2. 使用 Json::StreamWriter
Json::StreamWriter的write方法
virtual int write(Json::Value const& root, std::ostream* sout);//第二个参数是个输出流
int main() {Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder;// StreamWriter 的工厂std::unique_ptr<Json::StreamWriter>  writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}
$ ./test.exe
{"name" : "joe","sex" : "男"
}
3. 使用 Json::FastWriter:
StyledWriter 更快,因为它不添加额外的空格和换行符
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;
}
$ ./test.exe 
{"name":"joe","sex":"男"}#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;
}
$ ./test.exe 
{"name" : "joe","sex" : "男"
}

3.反序列化

使用 Json::Reader
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;
}
$ ./test.exe
Name: 张三
Age: 30
City: 北京

http://www.xdnf.cn/news/1117153.html

相关文章:

  • 飞算AI使用体验-一种基于项目工程思维的AI-Code思路
  • DVWA | Weak Session IDs 弱会话标识符
  • 【VLLM】大模型本地化部署
  • 当外卖骑手遇上“爽提学院”:一场关于专业的蜕变
  • Vue中的render()函数
  • 封装---优化try..catch错误处理方式
  • 小程序部分pai
  • 【OpenGL ES】手撕一个mini版的Android native渲染框架
  • 深入理解数据库连接池:原理、实现与Druid实战
  • 使用binutils工具分析目标文件(壹)
  • 【网络工程师软考版】计算机组成原理
  • SylixOS 下的中断嵌套
  • Android自定义View的事件分发流程
  • python的平安驾校管理系统
  • html案例:编写一个用于发布CSDN文章时,生成有关缩略图
  • 嵌入式固件 .pkg 打包流程
  • 深度学习图像分类数据集—宠物四种表情识别分类
  • 学习python调用WebApi的基本用法(2)
  • k8s存储入门
  • 基于Leaflet调用天地图在线API的多层级地名检索实战
  • 深度学习16(对抗生成网络:GAN+自动编码器)
  • 跨网络连接不同机器上的虚拟机
  • UNet改进(22):融合CNN与Transformer的医学图像分割新架构
  • 15. JVM调优的参数设置
  • [Linux 入门] Linux 引导过程、系统管理与故障处理全解析
  • word设置多级标题
  • Cursor的使用
  • docker容器高级管理-dockerfile创建镜像
  • 树莓派5-ollama-linux-arm64.tgz 下载
  • OkHttp SSE 完整总结(最终版)