阅读导航
- 引言
- 一、自定义协议概念
- 二、自定义协议需要注意的事项
- 三、自定义协议示例(跨网络计算器协议)
- ✅协议代码(Protocol.hpp)
- 1. 计算器协议简单介绍
- 2. 序列化部分
- 3. 反序列化部分
- 4. 请求和响应数据结构
- 5. 使用自定义协议
- 四、总结
- 温馨提示
引言
在上一篇文章中,我们深入探讨了守护进程的稳定性、序列化与反序列化在数据传输中的关键作用。这篇文章和下篇文章将继续扩展我们的技术视野,聚焦于自定义协议的设计与实现。自定义协议为网络通信提供了高度的灵活性和针对性,允许我们根据特定应用的需求来定制数据交换的规则。我们将通过一个实际的例子:跨网络计算器,用它作为例子来展示自定义协议的强大功能和应用潜力。通过这种方式,我们不仅能够加深对网络通信原理的理解,还能探索如何将这些原理应用于解决现实世界中的复杂问题。
一、自定义协议概念
所谓自定义就是指在软件开发和网络通信领域中,由开发者或组织根据特定的需求和规则自行定义的一套通信规则或数据交换格式。这种协议通常是为了满足特定的业务场景或技术要求而设计的,它可能包括数据的传输方式、数据的编码和解码规则、消息的结构和内容等。
二、自定义协议需要注意的事项
-
数据格式:自定义协议需要定义数据的存储和传输格式,常见的格式包括文本格式如JSON、XML,以及二进制格式如Protocol Buffers、Thrift等。
-
通信规则:协议需要规定数据在网络中的传输方式,比如TCP、UDP等,以及如何建立连接、传输数据、关闭连接等。
-
消息结构:自定义协议需要定义消息的组成结构,包括消息头和消息体,以及各种控制信息和数据内容的排列方式。
-
错误处理:协议还需要包含错误处理机制,定义在出现错误或异常情况时的应对策略,比如重传机制、错误码等。
-
安全性:在设计自定义协议时,还需要考虑数据的安全性,包括数据的加密、认证和完整性保护等。
-
扩展性:一个好的自定义协议应该具有良好的扩展性,能够适应未来业务的发展和技术的更新。
三、自定义协议示例(跨网络计算器协议)
✅协议代码(Protocol.hpp)
#pragma once // 确保头文件在整个程序中只被包含一次
#include <iostream> // 包含标准输入输出流
#include <string> // 包含字符串类
#include <jsoncpp/json/json.h> // 包含JSONCPP库,用于JSON数据的处理
// 宏定义,用于序列化和反序列化过程中的数据分隔
const std::string blank_space_sep = " "; // 空格分隔符
const std::string protocol_sep = "\n"; // 换行符作为协议的分隔符
// 序列化函数,将内容字符串包装成网络传输的格式
std::string Encode(std::string &content)
{
std::string package; // 创建一个字符串用于存储包装后的数据
package = std::to_string(content.size()); // 将内容的长度转换为字符串
package += protocol_sep; // 添加协议分隔符
package += content; // 添加内容本身
package += protocol_sep; // 再次添加协议分隔符,表示数据结束
return package; // 返回包装后的字符串
}
// 反序列化函数,将接收到的网络数据解析为内容字符串
bool Decode(std::string &package, std::string *content)
{
std::size_t pos = package.find(protocol_sep); // 查找协议分隔符的位置
if(pos == std::string::npos) return false; // 如果找不到分隔符,解析失败
std::string len_str = package.substr(0, pos); // 提取长度字符串
std::size_t len = std::stoi(len_str); // 将长度字符串转换为数字
std::size_t total_len = len_str.size() + len + 2; // 计算总长度(长度字符串 + 内容 + 分隔符)
if(package.size() < total_len) return false; // 如果实际数据长度小于预期,解析失败
*content = package.substr(pos+1, len); // 提取内容字符串
package.erase(0, total_len); // 从原始数据中移除已解析的部分
return true; // 解析成功
}
// 请求数据结构
class Request
{
public:
// 构造函数,初始化请求的参数
Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper) {}
// 默认构造函数
Request() {}
public:
// 序列化方法,将请求对象转换为字符串
bool Serialize(std::string *out)
{
#ifdef MySelf
// 使用简单的字符串拼接方式
std::string s = std::to_string(x); // 将操作数x转换为字符串
s += blank_space_sep; // 添加空格分隔符
s += op; // 添加操作符
s += blank_space_sep; // 再次添加空格分隔符
s += std::to_string(y); // 将操作数y转换为字符串
*out = s; // 将拼接后的字符串赋值给输出参数
return true; // 返回成功
#else
// 使用JSON格式
Json::Value root; // 创建JSON值对象
root["x"] = x; // 添加操作数x
root["y"] = y; // 添加操作数y
root["op"] = op; // 添加操作符
Json::StyledWriter w; // 创建JSON格式化写入器
*out = w.write(root); // 将JSON对象转换为格式化的字符串
return true; // 返回成功
#endif
}
// 反序列化方法,将字符串转换为请求对象
bool Deserialize(const std::string &in)
{
#ifdef MySelf
// 使用简单的字符串拼接方式
std::size_t left = in.find(blank_space_sep); // 查找第一个空格分隔符
if (left == std::string::npos) return false; // 如果找不到分隔符,解析失败
std::string part_x = in.substr(0, left); // 提取操作数x的字符串
std::size_t right = in.rfind(blank_space_sep); // 查找最后一个空格分隔符
if (right == std::string::npos) return false; // 如果找不到分隔符,解析失败
std::string part_y = in.substr(right + 1); // 提取操作数y的字符串
if (left + 2 != right) return false; // 如果分隔符之间的长度不符合预期,解析失败
op = in[left + 1]; // 提取操作符
x = std::stoi(part_x); // 将操作数x的字符串转换为数字
y = std::stoi(part_y); // 将操作数y的字符串转换为数字
return true; // 返回成功
#else
// 使用JSON格式
Json::Value root;
Json::Reader r;
if (!r.parse(in, root)) return false; // 解析JSON字符串,如果失败则返回
x = root["x"].asInt(); // 提取操作数x
y = root["y"].asInt(); // 提取操作数y
op = root["op"].asInt(); // 提取操作符
return true; // 返回成功
#endif
}
// 调试方法,打印请求对象的内容
void DebugPrint()
{
std::cout << "新请求构建完成: " << x << op << y << "=?" << std::endl; // 打印操作数和操作符
}
public:
// 请求的数据成员
int x; // 操作数x
int y; // 操作数y
char op; // 操作符,可以是 + - * / %
};
// 响应数据结构
class Response
{
public:
// 构造函数,初始化响应的参数
Response(int res, int c) : result(res), code(c) {}
// 默认构造函数
Response() {}
public:
// 序列化方法,将响应对象转换为字符串
bool Serialize(std::string *out)
{
#ifdef MySelf
// 使用简单的字符串拼接方式
std::string s = std::to_string(result); // 将结果转换为字符串
s += blank_space_sep; // 添加空格分隔符
s += std::to_string(code); // 将错误代码转换为字符串
*out = s; // 将拼接后的字符串赋值给输出参数
return true; // 返回成功
#else
// 使用JSON格式
Json::Value root; // 创建JSON值对象
root["result"] = result; // 添加结果
root["code"] = code; // 添加错误代码
Json::StyledWriter w; // 创建JSON格式化写入器
*out = w.write(root); // 将JSON对象转换为格式化的字符串
return true; // 返回成功
#endif
}
// 反序列化方法,将字符串转换为响应对象
bool Deserialize(const std::string &in)
{
#ifdef MySelf
// 使用简单的字符串拼接方式
std::size_t pos = in.find(blank_space_sep); // 查找空格分隔符
if (pos == std::string::npos) return false; // 如果找不到分隔符,解析失败
std::string part_left = in.substr(0, pos); // 提取结果字符串
std::string part_right = in.substr(pos + 1); // 提取错误代码字符串
result = std::stoi(part_left); // 将结果字符串转换为数字
code = std::stoi(part_right); // 将错误代码字符串转换为数字
return true; // 返回成功
#else
// 使用JSON格式
Json::Value root;
Json::Reader r;
if (!r.parse(in, root)) return false; // 解析JSON字符串,如果失败则返回
result = root["result"].asInt(); // 提取结果
code = root["code"].asInt(); // 提取错误代码
return true; // 返回成功
#endif
}
// 调试方法,打印响应对象的内容
void DebugPrint()
{
std::cout << "结果响应完成, result: " << result << ", code: " << code << std::endl; // 打印结果和错误代码
}
public:
int result; // 计算结果
int code; // 错误代码,0表示成功,非0表示错误
};
1. 计算器协议简单介绍
我们的代码实现了一个简单的自定义网络通信协议,它通过定义Request
和Response
类来封装客户端请求和服务器响应的数据结构。协议使用Encode
函数将数据对象序列化为字符串格式,以便在网络上传输,并通过Decode
函数将接收到的字符串反序列化回数据对象。这种设计支持灵活的序列化选项,允许根据需要选择简单的文本格式或JSON格式。
2. 序列化部分
在自定义协议中,Encode
函数负责将content
(内容字符串)序列化为网络传输的格式。这个过程包括以下几个步骤:
- 计算
content
的长度,并将其转换为字符串。 - 将长度字符串、协议分隔符
protocol_sep
和content
本身拼接起来形成完整的数据包。 - 在
content
后再次添加协议分隔符,以标识数据包的结束。
std::string Encode(std::string &content)
{
std::string package = std::to_string(content.size()); // 步骤1: 计算内容长度
package += protocol_sep; // 步骤2: 添加分隔符
package += content; // 步骤2: 添加内容
package += protocol_sep; // 步骤3: 添加结束分隔符
return package; // 返回序列化后的数据包
}
3. 反序列化部分
反序列化是将已序列化的数据转换回原始数据结构的过程。Decode
函数负责将接收到的网络数据(package
)反序列化为content
字符串。这个过程包括以下几个步骤:
- 在
package
中查找第一个协议分隔符protocol_sep
的位置。 - 根据找到的位置,提取长度字符串(
len_str
)。 - 将长度字符串转换为数字,得到
content
的长度。 - 根据长度提取实际的
content
,并从原始package
中移除已处理的部分。
bool Decode(std::string &package, std::string *content)
{
std::size_t pos = package.find(protocol_sep); // 查找分隔符位置
if(pos == std::string::npos) return false; // 如果找不到分隔符,反序列化失败
std::string len_str = package.substr(0, pos); // 提取长度字符串
std::size_t len = std::stoi(len_str); // 将长度字符串转换为数字
if(package.size() < (len_str.size() + len + 2)) return false; // 验证长度
*content = package.substr(pos+1, len); // 提取内容
package.erase(0, len + len_str.size() + 2); // 移除已处理的部分
return true; // 反序列化成功
}
4. 请求和响应数据结构
自定义协议还定义了请求和响应的数据结构。Request
类封装了客户端发送的请求数据,而Response
类封装了服务器返回的响应数据。这些类提供了序列化和反序列化的方法,允许将对象状态转换为字符串形式,或从字符串形式恢复对象状态。
class Request {
// ... 省略构造函数、DebugPrint、Serialize、Deserialize 方法 ...
public:
int x; // 请求的操作数1
int y; // 请求的操作数2
char op; // 请求的操作符(如 +、-、*、/)
};
class Response {
// ... 省略构造函数、DebugPrint、Serialize、Deserialize 方法 ...
public:
int result; // 响应的结果
int code; // 响应的状态码(0表示成功,非0表示错误)
};
5. 使用自定义协议
自定义协议的使用涉及到将Request
对象通过Serialize
方法转换为字符串,然后通过网络传输发送给服务器。服务器接收到字符串后,使用Decode
方法将其反序列化为Response
对象,然后通过Serialize
方法将Response
对象转换为字符串回传给客户端。
四、总结
示例这种自定义协议的设计允许开发者控制数据的格式和结构,同时提供了灵活性,可以根据需要选择使用简单的文本格式或更复杂的格式(如JSON)。此外,通过在序列化和反序列化过程中进行错误检查,协议确保了数据的完整性和正确性。
温馨提示
感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!