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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【探索Linux】P.32(自定义协议)

引言

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

一、自定义协议概念

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

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

  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. 计算器协议简单介绍

我们的代码实现了一个简单的自定义网络通信协议,它通过定义Request和Response类来封装客户端请求和服务器响应的数据结构。协议使用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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!


目录
相关文章
|
4月前
|
存储 网络协议 Ubuntu
【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能
UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。 链表 链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从
266 2
|
6月前
|
Unix Linux
【Linux】详解信号的分类&&如何自定义信号的作用
【Linux】详解信号的分类&&如何自定义信号的作用
|
3月前
|
Linux 网络安全 开发工具
内核实验(二):自定义一个迷你Linux ARM系统,基于Kernel v5.15.102, Busybox,Qemu
本文介绍了如何基于Linux Kernel 5.15.102版本和BusyBox创建一个自定义的迷你Linux ARM系统,并使用QEMU进行启动和调试,包括内核和BusyBox的编译配置、根文件系统的制作以及运行QEMU时的命令和参数设置。
287 0
内核实验(二):自定义一个迷你Linux ARM系统,基于Kernel v5.15.102, Busybox,Qemu
|
3月前
|
安全 算法 网络协议
【在Linux世界中追寻伟大的One Piece】HTTPS协议原理
【在Linux世界中追寻伟大的One Piece】HTTPS协议原理
47 2
|
3月前
|
存储 Linux 网络安全
【Azure 存储服务】如何把开启NFS 3.0协议的Azure Blob挂载在Linux VM中呢?(NFS: Network File System 网络文件系统)
【Azure 存储服务】如何把开启NFS 3.0协议的Azure Blob挂载在Linux VM中呢?(NFS: Network File System 网络文件系统)
|
3月前
|
负载均衡 网络协议 Linux
在Linux中,如何理解VRRP协议?
在Linux中,如何理解VRRP协议?
|
3月前
|
网络协议 Linux 网络安全
在Linux中,我们都知道FTP协议有两种工作模式,它们的大概的⼀个工作流程是怎样的?
在Linux中,我们都知道FTP协议有两种工作模式,它们的大概的⼀个工作流程是怎样的?
|
3月前
|
域名解析 网络协议 Linux
在Linux中,我们都知道,dns采用了tcp协议,又采用了udp协议,什么时候采用tcp协议?什么 时候采用udp协议?为什么要这么设计?
在Linux中,我们都知道,dns采用了tcp协议,又采用了udp协议,什么时候采用tcp协议?什么 时候采用udp协议?为什么要这么设计?
|
3月前
|
域名解析 缓存 负载均衡
在Linux中,自定义解析域名的时候,可以编辑哪个⽂件?是否可以⼀个ip对应多个域名?是否⼀个域名对应多个ip?
在Linux中,自定义解析域名的时候,可以编辑哪个⽂件?是否可以⼀个ip对应多个域名?是否⼀个域名对应多个ip?
|
3月前
|
Ubuntu 搜索推荐 Shell
如何在 Linux VPS 上自定义你的 Bash 提示符
如何在 Linux VPS 上自定义你的 Bash 提示符
18 0