【探索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; // 添加操作数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. 计算器协议简单介绍

我们的代码实现了一个简单的自定义网络通信协议,它通过定义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; // 请求的操作数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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iOS - 多线程-GCD

文章目录 iOS - 多线程-GCD1. 常见多线程方案2. GCD2.1 GCD的常见函数GCD中有2个用来执行任务的函数 2.2 GCD的队列2.2.1 GCD的队列可以分为2大类型 2.3 容易混淆的术语2.4.1 有4个术语比较容易混淆&#xff1a;同步、异步、并发、串行 2.4 各种队列的执行效果 3. 死锁3.1 死锁…

了解BACnet的对象模型 (三)

文章目录 前言18个对象BACnet 对象的属性设备对象&#xff08;Device&#xff09;的属性输入输出值对象类型及其属性 在代码中的表达Device对象的属性模拟输入对象的属性 小结 前言 在楼宇自控网络中&#xff0c;各种设备之间要进行数据交换&#xff0c;为了能够实现设备的互操…

【数字电路与系统】【北京航空航天大学】实验:时序逻辑设计——三色灯开关(四)、设计实现

本次实验&#xff08;一&#xff09;见博客&#xff1a;【数字电路与系统】【北京航空航天大学】实验&#xff1a;时序逻辑设计——三色灯开关&#xff08;一&#xff09;、实验指导书 本次实验&#xff08;二&#xff09;见博客&#xff1a;【数字电路与系统】【北京航空航天…

【Yolov系列】Yolov5学习(一)补充1.1:自适应锚框计算

1、Yolov5的网络结构 Yolov5中使用的Coco数据集输入图片的尺寸为640*640&#xff0c;但是训练过程的输入尺寸并不唯一&#xff0c;Yolov5可以采用Mosaic增强技术把4张图片的部分组成了一张尺寸一定的输入图片。如果需要使用预训练权重&#xff0c;最好将输入图片尺寸调整到与作…

表达式求值(后缀表达式)(数据结构)

一、概念 算术表达式是由操作数&#xff08;运算数&#xff09;、运算符&#xff08;操作符&#xff09;、和界线符&#xff08;括号&#xff09;三部分组成&#xff0c;在计算机中进行算术表达式的计算是通过堆栈来实现的。 二后缀表达式的逻辑和实现方式&#xff08;逆波兰…

CST电磁仿真软件波导端口和离散端口的设置流程【教程解读】

置波导端口 通过波导端口馈电! Simulation > Sources and Loads > Waveguide Port 假设Waveguide Port是无限长的波导上给定的Mode&#xff0c;用来激励电磁场。相比离散端口(Discrete Port)&#xff0c;波导端口可以提供很好的模式匹配&#xff0c;因此能提供更高的精…

Android Material Design学习笔记

Material Design控件学习记录 Toolbar 新建一个工程后&#xff0c;在res/values/themes.xml文件里 <resources xmlns:tools"http://schemas.android.com/tools"><!-- Base application theme. --><style name"Theme.MaterialTest" paren…

Python的round与Excel的round不一样?

Python四舍五入怎么做 round()奇进偶舍round函数既不是“四舍五入”的原则&#xff0c;也不是“四舍六入无成双”的原则。 decimal round() 偶然发现python的round函数和excel的round函数对某些数据的处理结果不一致。有看到博主提到是奇进偶舍的方法&#xff0c;但经过验证和…

深度学习与神经网络入门

前言 人工智能&#xff08;AI&#xff09;与机器学习&#xff08;ML&#xff09;与深度学习&#xff08;DL&#xff09;的关系&#xff1a; DL包含于ML&#xff0c;ML包含于AI。 即深度学习是机器学习一部分&#xff0c;机器学习又是人工智能的一个分支。 那么深度学习到底有…

关系型数据库的相关概念

表、记录、字段 表 一个实体集相当于一个表记录 一个实体相当于一个记录&#xff0c;在表中表表现为一行数据字段 一个字段相当于数据库表中的列 表的关联关系 一对一(一对一的表可以合并成一张表)一对多多对多 必须创建第三张表&#xff0c;该表通常称为联接表&#xff0c…

协议的定制之序列化与反序列化 | 守护进程

目录 一、再谈协议 二、序列化与反序列化 三、网络计算器的简单实现 四、网络计算器完整代码 五、代码改进 六、守护进程 七、Json序列化与反序列化 八、netstat 一、再谈协议 是对数据格式和计算机之间交换数据时必须遵守的规则的正式描述。简单的说了&#xff0c;网络…

java多线程-练习

需求 代码 使用Callable 方便返回执行的结果&#xff1a;每个线程的发送礼物的数量礼物清单&#xff1a;共享资源&#xff0c;方便上锁礼物数量&#xff1a;线程变量&#xff0c;每个线程发送礼物的数量 public class SendGiftThread implements Callable<Integer> {// 共…

通过命令行构建Django应用程序

假设读者安装好Django开发环境后&#xff08;这个环境搭建很容易&#xff0c;大家可以参看随意一个网文&#xff09;&#xff0c;就可以通过命令行构建Django应用程序了。通过命令行构建Django应用程序的关键&#xff0c;是使用一个Django框架自带的管理工具——django-admin.p…

springboot3 集成knife4j

knife4j介绍 Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案。 springdoc地址&#xff1a;OpenAPI 3 Library for spring-boot Knife4j官网地址&#xff1a;Knife4j 集Swagger2及OpenAPI3为一体的增强解决方案. | Knife4j 环境介绍 java&#xff1a;17 Spring…

你们项目日志是如何处理的???

ELK日志采集系统 1.什么是ELK ELK 是一套流行的数据搜索、分析和可视化解决方案&#xff0c;由三个开源项目组成&#xff0c;每个项目的首字母合起来形成了“ELK”这一术语&#xff1a; Elasticsearch (ES): Elasticsearch 是一个基于 Apache Lucene 构建的分布式、实时搜索与…

项目开发流程

项目开发流程 &#x1f469;‍&#x1f9b3;项目立项 估计项目的花费&#xff0c;确定大致的所需开发人员数&#xff0c;确定项目是否可行&#xff1b; &#x1f469;‍&#x1f9b0;需求分析 整体过程&#xff1a; 项目背景和目标&#xff0c;即项目的目的是什么 用户需求&…

动态Web项目讲解+Demo

web流程演示 请求路径 请求路径明确要请求的是哪个servlet 请求方式 servlet含有两种请求方式&#xff1a;doGet和doPost doGet&doPost 返回数据就是httpResponse&#xff0c;返回给success 参数 包含在request当中 成功 上述流程任何一步都没出问题&#xff0c;就会…

设计模式之创建型模式---工厂模式

文章目录 工厂模式概述简单工厂简单工厂的代码实现简单工厂的使用简单工厂应用场景 工厂方法工厂方法模式的代码实现工厂方法的使用工厂方法应用场景 抽象工厂抽象工厂模式代码实现抽象工厂的使用方法抽象工厂模式的应用场景 总结 工厂模式概述 工厂模式从名字就能看出&#x…

在【laravel框架】学习中遇到的常见的问题以及解决方法

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

.net core webapi 高颜值的接口管理系统界面取代swagger,更好调试和查看

.net core webapi 高颜值的接口管理系统界面取代swagger&#xff0c;更好调试和查看 安装 dotnet add package IGeekFan.AspNetCore.Knife4jUI --version 0.0.16配置文档&#xff1a; 配置起始页 builder.Services.AddSwaggerGen(c > {// 配置 Swagger 文档相关信息c.Swa…