应用层协议和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 的所有数据类型
- 对象(
{}
):键值对集合 - 数组(
[]
):有序列表 - 字符串(
"..."
) - 数字(
int
,double
) - 布尔值(
true
,false
) -
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}
}
访问元素
2.序列化
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" : "男"
}
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" : "男"
}
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.反序列化
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: 北京