【探索Linux】P.32(自定义协议)

在这里插入图片描述

阅读导航

  • 引言
  • 一、自定义协议概念
  • 二、自定义协议需要注意的事项
  • 三、自定义协议示例(跨网络计算器协议)
    • ✅协议代码(Protocol.hpp)
    • 1. 计算器协议简单介绍
    • 2. 序列化部分
    • 3. 反序列化部分
    • 4. 请求和响应数据结构
    • 5. 使用自定义协议
  • 四、总结
  • 温馨提示

引言

在上一篇文章中,我们深入探讨了守护进程的稳定性、序列化与反序列化在数据传输中的关键作用。这篇文章和下篇文章将继续扩展我们的技术视野,聚焦于自定义协议的设计与实现。自定义协议为网络通信提供了高度的灵活性和针对性,允许我们根据特定应用的需求来定制数据交换的规则。我们将通过一个实际的例子:跨网络计算器,用它作为例子来展示自定义协议的强大功能和应用潜力。通过这种方式,我们不仅能够加深对网络通信原理的理解,还能探索如何将这些原理应用于解决现实世界中的复杂问题。

一、自定义协议概念

所谓自定义就是指在软件开发和网络通信领域中,由开发者或组织根据特定的需求和规则自行定义的一套通信规则或数据交换格式。这种协议通常是为了满足特定的业务场景或技术要求而设计的,它可能包括数据的传输方式、数据的编码和解码规则、消息的结构和内容等。

二、自定义协议需要注意的事项

  1. 数据格式:自定义协议需要定义数据的存储和传输格式,常见的格式包括文本格式如JSON、XML,以及二进制格式如Protocol Buffers、Thrift等。

  2. 通信规则:协议需要规定数据在网络中的传输方式,比如TCP、UDP等,以及如何建立连接、传输数据、关闭连接等。

  3. 消息结构:自定义协议需要定义消息的组成结构,包括消息头和消息体,以及各种控制信息和数据内容的排列方式。

  4. 错误处理:协议还需要包含错误处理机制,定义在出现错误或异常情况时的应对策略,比如重传机制、错误码等。

  5. 安全性:在设计自定义协议时,还需要考虑数据的安全性,包括数据的加密、认证和完整性保护等。

  6. 扩展性:一个好的自定义协议应该具有良好的扩展性,能够适应未来业务的发展和技术的更新。

三、自定义协议示例(跨网络计算器协议)

✅协议代码(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; // 添加操作数xroot["y"] = y; // 添加操作数yroot["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(); // 提取操作数xy = root["y"].asInt(); // 提取操作数yop = root["op"].asInt(); // 提取操作符return true; // 返回成功
#endif}// 调试方法,打印请求对象的内容void DebugPrint(){std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl; // 打印操作数和操作符}public:// 请求的数据成员int x; // 操作数xint y; // 操作数ychar 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. 计算器协议简单介绍

我们的代码实现了一个简单的自定义网络通信协议,它通过定义RequestResponse类来封装客户端请求和服务器响应的数据结构。协议使用Encode函数将数据对象序列化为字符串格式,以便在网络上传输,并通过Decode函数将接收到的字符串反序列化回数据对象。这种设计支持灵活的序列化选项,允许根据需要选择简单的文本格式或JSON格式。

2. 序列化部分

在自定义协议中,Encode函数负责将content(内容字符串)序列化为网络传输的格式。这个过程包括以下几个步骤:

  1. 计算content的长度,并将其转换为字符串。
  2. 将长度字符串、协议分隔符protocol_sepcontent本身拼接起来形成完整的数据包。
  3. 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字符串。这个过程包括以下几个步骤:

  1. package中查找第一个协议分隔符protocol_sep的位置。
  2. 根据找到的位置,提取长度字符串(len_str)。
  3. 将长度字符串转换为数字,得到content的长度。
  4. 根据长度提取实际的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; // 请求的操作数1int y; // 请求的操作数2char 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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/826929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rust Tracing 入门

Tracing 是一个强大的工具&#xff0c;开发人员可以使用它来了解代码的行为、识别性能瓶颈和调试问题。 Rust 是一种以其性能和安全保证而闻名的语言&#xff0c;在它的世界中&#xff0c;跟踪在确保应用程序平稳高效运行方面发挥着至关重要的作用。 在本文中探讨Tracing 的概…

C++ 初识模板

目录 0.前言 1.泛型编程 2.函数模板 2.1概念 2.2格式 2.3原理 2.4函数模板的实例化 2.4.1隐式实例化 2.4.2显式实例化 2.5模板参数的匹配原则 3.类模板 3.1类模板的定义格式 3.2类模板的实例化 4.结语 &#xff08;图像由AI生成&#xff09; 0.前言 在 C 中&a…

Unity3D 爆火的休闲益智游戏工程源码/3D资源 大合集

Unity3D休闲益智游戏工程源码大合集 一、关卡类游戏工程源码二、跑酷类游戏工程源码三、消除合成类游戏工程源码四、棋牌类游戏工程源码五、RPG(角色扮演)类游戏工程源码六、FPS&#xff08;射击&#xff09;类游戏工程源码十、Unity3D工艺仿真六、Unity游戏资源1、Unity3D 吃鸡…

Redis数据类型——String

Redis官网指令文档&#xff1a;Commands | Docs 前言 此处的String类型是针对Redis的Value的&#xff0c;因为Key的形式都是String&#xff0c;而Value则有哈性、列表、集合等形式。 众所周知&#xff0c;由于不同编码&#xff0c;经常会出现乱码的问题&#xff0c;但在Redi…

打造稳定安全的亚马逊测评环境:关键步骤与要点一览

亚马逊测评环境的搭建是一项既复杂又需要深入细致考虑的工作&#xff0c;它涉及多方面的技术配置和资源准备。以下是一些关键步骤和要点&#xff0c;帮助您更高效地构建测评环境。 一、资源筹备 1. 养号系统&#xff1a;选择稳定、高效的养号系统&#xff0c;确保能够模拟真实…

Linux系统-进程和计划任务管理

一.程序和进程 1.程序 保持在硬盘、光盘等介质中的可执行代码和数据文件中静态保存的代码 2.进程 在CPU及内存中运行的程序代码动态执行的代码父、子进程每个程序可以创建一个或多个进程 3.进程特征 动态性&#xff1a;进程是程序的一次执行过程&#xff0c;是临时的&…

决策树分类任务实战(python 代码详解)

目录 一、导入库、数据集、并划分训练集和测试集 二、参数调优 (一)第一种调参方法&#xff1a;for循环 (1)单参数优化 ①单参数优化(无K折交叉验证) ②单参数K折交叉验证 优化 (2)多参数优化 ①多参数优化(无K折交叉验证) 参数介绍&#xff1a; ②多参数K折交叉验证…

vulfocus靶场名称: apache-cve_2021_41773/apache-cve_2021_42013

Apache HTTP Server 2.4.49、2.4.50版本对路径规范化所做的更改中存在一个路径穿越漏洞&#xff0c;攻击者可利用该漏洞读取到Web目录外的其他文件&#xff0c;如系统配置文件、网站源码等&#xff0c;甚至在特定情况下&#xff0c;攻击者可构造恶意请求执行命令&#xff0c;控…

记录一下hive启动metestore服务时报错

【背景说明】 之前hadoop有问题&#xff0c;把hadoop和MySQL删了重装&#xff0c;hive没有动&#xff0c;然后启hive的metastore服务的时候&#xff0c;显示找不到metastore数据库 【报错】 Caused by: java.lang.reflect.InvocationTargetExceptionat sun.reflect.Generated…

【Java框架】SpringMVC(一)——基本的环境搭建及基本结构体系

目录 MVC模式视图(View)控制器(Controller)模型(Model)JSP Model1JSP Model2MVC的优点MVC的缺点 Spring MVC架构介绍特点 SpringMVC环境搭建(在前面Spring整合Mybatis的基础上)1.创建控制器Controller2.创建springmvc配置文件&#xff0c;并添加Controller的Bean3.web.xml中配置…

# 从浅入深 学习 SpringCloud 微服务架构(二)模拟微服务环境(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;二&#xff09;模拟微服务环境&#xff08;1&#xff09; 段子手168 1、打开 idea 创建父工程 创建 artifactId 名为 spring_cloud_demo 的 maven 工程。 --> idea --> File --> New --> Project --> Ma…

2024 CKA 最新 | 基础操作教程(十七)

题目内容 设置配置环境&#xff1a; [candidatenode-1] $ kubectl config use-context ek8s Task 将名为 node02 的 node 设置为不可用&#xff0c;并重新调度该 node 上所有运行的 pods。 考点相关内容分析 node 在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c…

VASA-1:一键生成高质量视频,颠覆你的想象!

VASA-1&#xff1a;语音生成AI视频 前言 最近&#xff0c;微软公司公布了一项图生视频的 VASA-1 框架&#xff0c;该 AI 框架只需使用一张真人肖像照片和一段个人语音音频&#xff0c;就能够生成精确逼真的相对应文本的视频&#xff0c;而且可以使表情和面部动作表现的十分自然…

【数据结构】栈和队列(链表模拟队列)

学习本章节必须具备 单链表的前置知识&#xff0c; 建议提前学习&#xff1a;点击链接学习&#xff1a;单链表各种功能函数 细节 详解 本章节是学习用 单链表模拟队列 1. 单链表实现队列 思路如下 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数…

线程互斥及基于线程锁的抢票程序

我们实现一个简单的多线程抢票程序。 #include<iostream> #include<thread> #include<unistd.h> #include<functional> #include<vector> using namespace std; template<class T> using func_tfunction<void(T)>;//返回值为void,…

XUbuntu18.04 源码编译Qt4.5.3的过程

由于新公司很多旧的软件都是基于这个版本做的嵌入式开发。 所以想要自己搭一套基于Linux的非嵌入式开发环境&#xff0c;方便用来调试和编译代码。 这样就可以完成在linux下开发&#xff0c;然后直接嵌入式打包&#xff0c;涉及到界面的部分就不需要上机调试看问题了。 所以…

Axure引用ECharts图表 解决火狐浏览器出错

Axure原型添加Echarts图表&#xff0c;没耐心看文章的可以直接下载示例 Axure中使用ECharts图表示例 1. 打开Axure新建页面 2. 页面添加元件 元件类型随意&#xff0c;矩形、动态面板、热区、图片 甚至段落都可以3. 命名元件 随意命名&#xff0c;单个页面用到多个图表时名…

机器学习-11-基于多模态特征融合的图像文本检索

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中图像文本检索技术。此技术把自然语言处理和图像处理进行了融合。 参考 2024年&#xff08;第12届&#xff09;“泰迪杯”数据挖掘挑战赛 图像特征提取&#xff08;VGG和Resnet特征提取卷积过程详解&…

Facebook账号运营要用什么IP?

众所周知&#xff0c;Facebook封号大多数情况都是因为IP的原因。Facebook对于用户账号有严格的IP要求和限制&#xff0c;以维护平台的稳定性和安全性。在这种背景下&#xff0c;海外IP代理成为了一种有效的解决方案&#xff0c;帮助用户避免检测&#xff0c;更加快捷安全地进行…

学习笔记:Vue2中级篇

Vue2 学习笔记&#xff1a;Vue2基础篇_ljtxy.love的博客-CSDN博客学习笔记&#xff1a;Vue2中级篇_ljtxy.love的博客-CSDN博客学习笔记&#xff1a;Vue2高级篇_ljtxy.love的博客-CSDN博客 Vue3 学习笔记&#xff1a;Vue3_ljtxy.love的博客&#xff09;-CSDN博客 文章目录 5.…