应用层协议需要解决两个问题:
1.结构化数据的序列化和反序列化
2.tcp数据传输的粘包问题
注意:
1.结构化数据序列化后作为应用层报文的有效载荷,自定义协议添加的报头可以来解决粘包问题
2.tcp粘包问题导致的原因是由于其发送算法和面向字节流的缓冲区导致的,tcp缓冲区是面向字节流的,要发送的数据write进发送缓冲区,要接收的数据被放进接收缓冲区等待read,发送时由nagle算法和定时发送来进行,当缓冲区中数据达到MSS或没有发送请求却没收到确认应答的数据,那就发送,实在不行靠时钟中断来发送,这就导致了一个应用层报文被发送到服务器tcp缓冲区后,可能够过了,也可能不够,所以需要应用层自定义协议
cpp
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 / 21
std::shared_ptr<Request> BuildRequest(int x, int y, char
op) {
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, int
code) {
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 数据
cpp
#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;
// 输出 JSON
Json::StyledWriter writer;
std::string jsonString = writer.write(root);
std::cout << jsonString << std::endl;
return 0;
}
结果:
cpp
{
"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 方法:
cpp
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" : "男"
}
- 使用Json::StreamWriter:
Json::StreamWriter的write方法
virtual int write(Json::Value const& root, std::ostream* sout);//第二个参数是个输出流
cpp
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" : "男"
}
- 使用Json::FastWriter:
比 StyledWriter 更快,因为它不添加额外的空格和换行符
cpp
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:
cpp
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: 北京