[Linux#55][网络协议] 序列化与反序列化 | TcpCalculate为例

news2024/9/20 17:43:03

目录

1. 理解协议

1.1 结构化数据的传输

序列化与反序列化

代码感知:

Request 类

1. 构造函数

2. 序列化函数:Serialize()

3. 反序列化函数:DeSerialize()

补充

4. 成员变量

Response 类

1. 构造函数

2. 序列化函数:Serialize()

3. 反序列化函数:DeSerialize()

4. 成员变量

总结

2. 实验:网络版计算器

2.1 定义请求和响应协议

2.2 TCP 服务端设计

2.3 业务处理逻辑

3. TCP 客户端实现

4. 序列化与反序列化的重要性


在网络编程中,协议是一个关键概念。协议本质上是一种“约定”,规定了两方在通信时如何格式化和处理数据。本文将深入探讨如何通过协议进行结构化数据的传输,并且通过一个具体的网络版计算器( TCP服务器-客户端)示例,展示序列化与反序列化的实现。

学习导图:

1. 理解协议

协议,简单来说,就是通信双方都遵守的规则。在前面的例子中,我们使用了父亲和儿子通过电话沟通的场景。父亲告诉儿子会在特定时间打电话,这就是一种约定——协议。

1.1 结构化数据的传输

在网络通信中,数据通常以字节流的形式发送和接收。当我们需要传输的是结构化数据时,例如在QQ群聊中,除了文字消息外,还包含头像、时间和昵称。这些信息都需要以某种方式发送给对方。如果我们逐个发送这些数据,不仅麻烦,接收方也难以处理,因此需要对这些数据进行打包。

为什么要把字符串转成结构化数据呢?未来这个结构化的数据一定是一个对象,然后使用它的时候,直接对象.url 、对象.time 拿到。

而这里的结构体如message就是传说中的业务协议
因为它规定了我们聊天时网络通信的数据。

序列化与反序列化

为了简化结构化数据的传输,我们通常将多个独立的信息合并为一个报文。这就是序列化的过程:将数据打包成一个字符串或字节流,再通过网络发送。接收方收到数据后,需要通过反序列化,将收到的数据解析回原来的结构化数据。

代码感知:

这段代码的核心功能是实现请求(Request)和响应(Response)的序列化与反序列化。序列化的作用是将类中的成员变量转换成字符串格式,方便在网络中传输;反序列化的作用是将字符串解析回类的成员变量,恢复为结构化数据。以下是对该代码的详细解释:

Request

class Request
{
public:
    // 定义常量字符串分隔符和其长度
    static const char SPACE = ' ';
    static const int SPACE_LEN = 1;

这个类表示客户端发送给服务器的计算请求,包含两个操作数(_x_y)以及一个操作符(_op)。它提供了序列化和反序列化的能力。

1. 构造函数
Request()
{}

这个是默认构造函数,不进行任何初始化操作,只是声明了 Request 对象。

Request(int x, int y, int op)
    : _x(x), _y(y), _op(op)
{}

这是一个带参数的构造函数,它接受两个整数操作数 xy 和一个字符操作符 op,并将它们赋值给类中的成员变量 _x_y_op

2. 序列化函数:Serialize()
std::string Serialize()
{
    std::string str;
    str = std::to_string(_x);
    str += SPACE;
    str += _op;
    str += SPACE;
    str += std::to_string(_y);
    return str;
}
  • 功能:将 Request 对象中的数据成员 _x_op_y 组合成一个字符串。返回组合好的字符串。最终结果类似 "1 + 2" 的格式。
3. 反序列化函数:DeSerialize()

 

bool DeSerialize(const std::string &str)
{
    size_t left = str.find(SPACE);
    if(left == std::string::npos)
    {
        return false;
    }

    size_t right = str.rfind(SPACE);
    if (right == std::string::npos)
    {
        return false;
    }

    _x = atoi(str.substr(0, left).c_str());
    _y = atoi(str.substr(right + SPACE_LEN).c_str());
    
    if(left + SPACE_LEN < str.size())
    {
        _op = str[left + SPACE_LEN];
        return true;
    }
    else
    {
        return false;
    }
}
  • 功能:从输入的字符串中提取出操作数 _x_y 以及操作符 _op,并将它们存储到 Request 对象的成员变量中。
  • 步骤
    1. str.find(SPACE):在字符串 str 中查找第一个空格的位置,用作分隔符。如果找不到,返回 false
    2. str.rfind(SPACE):查找最后一个空格的位置,表示第二个操作数的开头。如果找不到,返回 false
    3. 使用 atoi 函数从字符串中提取整数操作数 _x_ysubstr(0, left) 获取左侧字符串,即第一个操作数,substr(right + SPACE_LEN) 获取右侧字符串,即第二个操作数。
    4. 从字符串 str 中获取操作符 _op,位于第一个空格后的位置。
    5. 如果解析成功,返回 true;否则返回 false

设计思路:

补充

  1. 上面代码中的atoi是怎么使用的,介绍一下atoi接口
  2. 是如何从字符串 str 中获取操作符 _op
  1. atoi 函数的使用:
    atoi 是 C++ 标准库函数之一,它位于 <cstdlib> 头文件中。该函数的作用是将一个字符串(以空字符结尾的字符数组)转换为 int 类型的整数。其原型如下:
int atoi(const char *str);

参数 str 是指向要转换的以空字符结尾的字符串的指针。atoi 会从字符串的开头开始转换,直到遇到第一个非数字字符或到达字符串的结尾。如果字符串以数字开头,atoi 会返回这些数字对应的整数值。如果字符串不是以数字开头,或者字符串为空,atoi 会返回 0。
以下是一些使用 atoi 的例子:

#include <cstdlib>
#include <iostream>
int main() {
    const char *str1 = "123";
    const char *str2 = "12abc34";
    const char *str3 = "abc123";
    int num1 = atoi(str1); // num1 will be 123
    int num2 = atoi(str2); // num2 will be 12
    int num3 = atoi(str3); // num3 will be 0 (no digits at the start)
    std::cout << "num1: " << num1 << std::endl;
    std::cout << "num2: " << num2 << std::endl;
    std::cout << "num3: " << num3 << std::endl;
    return 0;
}

需要注意的是,atoi 不进行错误检查,如果字符串不能完全转换为数字,那么未转换的部分将被忽略。此外,atoi 无法处理整数溢出,如果转换的数字超出了 int 的表示范围,结果是不确定的。
🎢2. 从字符串 str 中获取操作符 _op
Request 类的 DeSerialize 方法中,以下是获取操作符 _op 的代码片段:

if(left + SPACE_LEN < str.size())
{
    _op = str[left + SPACE_LEN];
    return true;
}
else
{
    return false;
}

这里的 leftstr 中第一个空格字符的位置,SPACE_LEN 是空格字符的长度,通常为 1。所以 left + SPACE_LEN 是第一个空格字符之后的位置,即操作符 _op 应该出现的位置。str[left + SPACE_LEN] 获取该位置的字符并将其赋值给 _op


4. 成员变量
public:
    int _x;
    int _y;
    char _op;
};

Response

Response 类表示服务器的响应,包含两个数据成员:计算结果 _ret 和状态码 _code

1. 构造函数
Response()
{}

这是默认构造函数,不初始化任何成员。

Response(int ret, int code)
    : _code(code), _ret(ret)
{}

这是一个带参数的构造函数,用来初始化响应结果 ret 和状态码 code

2. 序列化函数:Serialize()
std::string Serialize()
{
    std::string str = std::to_string(_code);
    str += SPACE;
    str += std::to_string(_ret);
    return str;
}
  • 功能:将 Response 对象的两个成员变量 _code_ret 组合成字符串。
3. 反序列化函数:DeSerialize()
bool DeSerialize(std::string &str)
{
    size_t pos = str.find(SPACE);
    if(pos == std::string::npos)
    {
        return false;
    }

    _code = atoi(str.substr(0, pos).c_str());
    _ret = atoi(str.substr(pos + SPACE_LEN).c_str());
    return true;
}
  • 功能:从输入的字符串中解析出状态码 _code 和计算结果 _ret,并存入 Response 对象。
4. 成员变量
int _ret;  // 计算结果
int _code; // 状态码

Response 类包含两个成员变量:

  • _ret:存储服务器的计算结果(例如:加法、减法的结果)。
  • _code:存储状态码,0 表示成功,非 0 表示错误(例如:除以 0 错误时 _code 可能为 1)。

总结

class Request
{
public:
    Request()
    {}

    Request(int x, int y, int op)
    : _x(x), _y(y), _op(op)
    {}

    ~Request()
    {}
    // _x _op _y
    std::string Serialize()
    {
        std::string str;
        str = std::to_string(_x);
        str += SPACE;
        str += _op;
        str += SPACE;
        str += std::to_string(_y);
        return str;
    }

    bool DeSerialize(const std::string &str)
    {
        size_t left = str.find(SPACE);
        if(left == std::string::npos)
        {
            return false;
        }

        size_t right = str.rfind(SPACE);
        if (right == std::string::npos)
        {
            return false;
        }

        _x = atoi(str.substr(0, left).c_str());
        _y = atoi(str.substr(right + SPACE_LEN).c_str());
        
        if(left + SPACE_LEN < str.size())
        {
            _op = str[left + SPACE_LEN];
            return true;
        }
        else
        {
            return false;
        }
    }

public:
    int _x;
    int _y;
    char _op;
};

class Response
{
public:
    Response()
    {}

    Response(int ret, int code)
    : _code(code)
    , _ret(ret)
    {}

    ~Response()
    {}

    // _code _ret
    std::string Serialize()
    {
        std::string str = std::to_string(_code);
        str += SPACE;
        str += std::to_string(_ret);
        return str;
    }

    bool DeSerialize(std::string &str)
    {
        size_t pos = str.find(SPACE);
        if(pos == std::string::npos)
        {
            return false;
        }

        _code = atoi(str.substr(0, pos).c_str());
        _ret = atoi(str.substr(pos + SPACE_LEN).c_str());
        return true;
    }

public:
    int _ret;  // 计算结果
    int _code; // 计算结果的状态码
};

代码展示了如何将 RequestResponse 对象进行序列化和反序列化,以便在网络中传输结构化数据。序列化将对象转换为字符串进行网络传输,反序列化将接收到的字符串重新解析为对象,方便在应用程序中处理。

  • Request 主要表示操作请求,包括两个操作数和一个操作符。
  • Response 表示服务器的响应,包括计算结果和状态码。
  • 序列化与反序列化是网络编程中常用的技术,能有效将复杂的结构化数据转化为适合网络传输的简单格式。

2. 实验:网络版计算器

为了加深理解,我们通过实现一个简单的TCP服务端来展示协议、序列化和反序列化的运作过程。这个服务端会处理简单的数学运算请求,客户端发送请求,服务端进行计算并返回结果。

2.1 定义请求和响应协议

我们定义了一个 Request 类来表示计算请求,包含两个操作数和一个操作符。同时,我们定义了 Response 类来表示响应结果,包含计算结果和状态码。

class Request {
public:
    Request(int x, int y, char op) : _x(x), _y(y), _op(op) {}

    // 序列化:将请求转换为字符串格式
    bool serialize(string *out) {
        *out = to_string(_x) + " " + _op + " " + to_string(_y);
        return true;
    }

    // 反序列化:从字符串解析出请求内容
    bool deserialize(const string &in) {
        auto left = in.find(' ');
        auto right = in.rfind(' ');
        if (left == string::npos || right == string::npos || left == right)
            return false;
        _x = stoi(in.substr(0, left));
        _op = in[left + 1];
        _y = stoi(in.substr(right + 1));
        return true;
    }

public:
    int _x; // 操作数1
    int _y; // 操作数2
    char _op; // 操作符
};

class Response {
public:
    Response(int exitcode, int result) : _exitcode(exitcode), _result(result) {}

    // 序列化:将响应转换为字符串格式
    bool serialize(string *out) {
        *out = to_string(_exitcode) + " " + to_string(_result);
        return true;
    }

    // 反序列化:从字符串解析出响应内容
    bool deserialize(const string &in) {
        auto pos = in.find(' ');
        if (pos == string::npos) return false;
        _exitcode = stoi(in.substr(0, pos));
        _result = stoi(in.substr(pos + 1));
        return true;
    }

public:
    int _exitcode; // 状态码
    int _result;   // 计算结果
};

2.2 TCP 服务端设计

我们设计了一个简单的TCP服务器 CalServer,用于处理客户端的请求并返回响应。

  • handlerEntry 函数:负责处理单个客户端连接。接收请求、反序列化、执行计算、序列化响应并发送回客户端。
  • CalServer 类:负责监听端口并处理多个客户端连接。
class CalServer {
public:
    CalServer(const uint16_t port) : _port(port), _listensock(-1) {}

    void initServer() {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        bind(_listensock, (struct sockaddr*)&local, sizeof(local));
        listen(_listensock, 5);
    }

    void start(func_t func) {
        signal(SIGCHLD, SIG_IGN);
        while (true) {
            int sock = accept(_listensock, nullptr, nullptr);
            if (fork() == 0) {
                handlerEntry(sock, func);
                close(sock);
                exit(0);
            }
            close(sock);
        }
    }
};

2.3 业务处理逻辑

处理请求的逻辑被封装在 Cal 函数中。它根据请求中的操作符计算结果并填充响应:

void Cal(const Request &req, Response &resp) {
    switch (req._op) {
        case '+': resp._result = req._x + req._y; break;
        case '-': resp._result = req._x - req._y; break;
        case '*': resp._result = req._x * req._y; break;
        case '/': if (req._y == 0) resp._exitcode = 1; else resp._result = req._x / req._y; break;
        default: resp._exitcode = 2; break;
    }
}

3. TCP 客户端实现

客户端通过发送序列化后的请求并接收响应。

class CalClient {
public:
    CalClient(const string &ip, const uint16_t &port) : _serverip(ip), _serverport(port), _sockfd(-1) {}

    void initClient() {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());
        connect(_sockfd, (struct sockaddr*)&server, sizeof(server));
    }

    void run() {
        while (true) {
            string msg;
            cout << "Enter calculation: ";
            getline(cin, msg);
            Request req = parseInput(msg);
            string req_str;
            req.serialize(&req_str);
            send(_sockfd, req_str.c_str(), req_str.size(), 0);

            char buffer[1024];
            recv(_sockfd, buffer, sizeof(buffer), 0);
            Response resp;
            resp.deserialize(buffer);
            cout << "Result: " << resp._result << endl;
        }
    }

private:
    string _serverip;
    uint16_t _serverport;
    int _sockfd;
};

4. 序列化与反序列化的重要性

  • 序列化:将结构化的数据(如对象)转换为字节流或字符串,以便传输。
  • 反序列化:将字节流或字符串重新解析为结构化数据,供应用程序使用。

通过序列化与反序列化,应用程序与网络通信得到了有效解耦。这种设计使得复杂的数据可以轻松地在网络中传输,并且为上层应用提供了灵活的操作方式。

对于网络版计算器所有部分的完整代码,之后将上传 gitee,下篇文章将对于设计思路和一些坑点继续讲解~

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

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

相关文章

力扣 中等 2300.咒语和药水的成功对数

文章目录 题目介绍解法 题目介绍 解法 class Solution {public int[] successfulPairs(int[] spells, int[] potions, long success){Arrays.sort(potions);int n spells.length, m potions.length;int[] pairs new int[n];for (int i 0; i < n; i) {int left 0, righ…

无消息传递的图变换器中的图归纳偏差

人工智能咨询培训老师叶梓 转载标明出处 在处理小规模数据集时&#xff0c;图变换器的性能通常不尽如人意&#xff0c;特别是在需要明显的归纳偏好时。为了引入这些偏好&#xff0c;早期的图变换器一般会利用消息传递组件或位置编码。然而&#xff0c;依赖消息传递的图变换器在…

C# AutoResetEvent ManualResetEvent Mutex 对比

三个函数功能类似&#xff0c;都是线程同步的主要函数。但在使用上有一些差别。 关于代码的使用&#xff0c;帖子很多。形象的用图来描述一下。

【Meta分析】IF=12.1!人工智能预测模型Meta分析怎么做?

预测模型的Meta分析 人工智能&#xff08;AI&#xff09;是计算机科学的一个重要分支&#xff0c;其主要目标是让算法执行通常由人类完成的任务。机器学习是指一组允许算法从数据中学习并自我优化的技术&#xff0c;而无需明确编程。深度学习这一术语常与机器学习互换使用&…

怿星设计分享丨设计师与AI的情感化HMI

在当今科技迅速发展的时代背景下&#xff0c;人机交互&#xff08;HMI&#xff09;的设计正从传统的功能性层面转向更加注重用户体验与情感交流的方向。设计师们不再仅仅关注界面的功能性&#xff0c;而是更加重视如何通过设计传递情感&#xff0c;使用户在使用产品时能够感受到…

EsDA,一站式嵌入式软件

EsDA是一套面向工业智能物联领域的嵌入式系统设计自动化工具集&#xff0c;包含实时操作系统AWorksLP、低代码开发平台AWStudio、资源管理平台AXPI、跨平台GUI引擎AWTK和云服务平台ZWS&#xff0c;旨在提高嵌入式软件开发的效率、性能和可扩展性。 EsDA全称是嵌入式系统设计自动…

回归预测|基于饥饿游戏搜索优化随机森林的数据回归预测Matlab程序HGS-RF 多特征输入单输出 高引用先用先创新

回归预测|基于饥饿游戏搜索优化随机森林的数据回归预测Matlab程序HGS-RF 多特征输入单输出 高引用先用先创新 文章目录 一、基本原理1. 饥饿游戏搜索优化算法&#xff08;HGS&#xff09;简介2. 随机森林&#xff08;RF&#xff09;简介3. HGS-RF回归预测流程1. 初始化2. 随机森…

三维手势 handpose 3D RGB 手势3D建模 三维建模-手势舞 >> DataBall

请关注即将发布 handpose x plus 项目 三维手势 handpose 3D RGB 单目相机手势识别 手语 歌曲 Friends 手势检测 手势3D建模 三维建模 咨询合作 DataBall 项目&#xff0c;欢迎加以下微信。 助力快速掌握数据集的信息和使用方式。

sourceTree保姆级教程7:(合并某次提交)

在日常开发过程中&#xff0c;大家有时候并非都是在同一个分支进行开发&#xff0c;可能存在多人的情况下开发。根据上线的需求依次提交代码。当然也可能存在交叉提交的情况。此时应如何在master分支去合并具体某一次的提交呢&#xff1f;下面就开始了&#xff1a; 1.打开本地…

巧用联合与枚举:解锁自定义类型的无限潜力

嘿嘿,家人们,今天咱们来详细剖析C语言中的联合与枚举,好啦,废话不多讲,开干! 目录 1.:联合体 1.1:联合体类型的声明 1.1.1:代码1 1.1.2:代码2(计算机联合体的大小) 1.1.3:代码3 1.2:联合体的特点 1.2.1:代码1 1.2.2:代码2 1.3:相同成员的结构体与联合体进行对比 1.3…

前端界面搜索部分,第一个选择框的值,影响第二个选择框的值

1.字段声明 {title: 单位名称,dataIndex: departmentId,align: center,width: 100,hideInTable: true,renderFormItem: (item, { defaultRender, ...rest }) > (<ProFormSelectname"departmentId"// label"单位名称"options{hospitaltData}onChange…

旋转矩阵乘法,自动驾驶中的点及坐标系变换推导

目录 1. 矩阵乘法的内项相消 2. 左右乘&#xff0c;内外旋与动静坐标系 3. 点变换 3.1 点旋转后的点坐标表示 3.2 坐标系旋转后的点坐标表示 4. 坐标变换的实质 1. 矩阵乘法的内项相消 关于旋转变换&#xff0c;离不开矩阵的乘法&#xff0c;而矩阵乘法的物理意义和本身数…

电脑usb控制软件有哪些?6款软件帮你轻松解决USB端口泄密烦恼!

在数字化时代&#xff0c;企业的信息安全成为重中之重。 然而&#xff0c;USB端口泄密事件频发&#xff0c;给企业的数据安全和业务连续性带来了巨大威胁。 此前&#xff0c;某大型制造企业&#xff0c;由于员工在日常工作中频繁使用U盘等USB存储设备&#xff0c;导致公司核心…

[Linux]自定义shell详解

自定义shell 前言1.命令行提示符&#xff0c;字符串的打印1.1命令行提示符2.命令行字符串 2.0对命令行字符串进行切割2.执行命令3.有趣的小问题完整代码 前言 写之前我们先看看一个完整的shell都包括了什么 $符号前面&#xff08;包括这个符号&#xff09;就是命令行提示符&a…

电线电缆制造5G智能工厂物联数字孪生平台,推进制造业数字化转型

电线电缆制造行业作为关键的基础设施建设领域&#xff0c;正积极拥抱新技术&#xff0c;推动生产方式的深刻变革。电线电缆制造5G智能工厂物联数字孪生平台的兴起&#xff0c;不仅为行业注入了新的活力&#xff0c;更为制造业的数字化转型树立了新的标杆。 电线电缆制造5G智能…

【项目案例】物联网比较好的10+练手项目推荐,附项目文档/源码/视频

练手项目推荐 1 智能小车 项目功能介绍&#xff1a; 本项目由三部分组成&#xff1a;应用端&#xff08;微信小程序&#xff09;、设备端&#xff08;Hi3861&#xff09;、驱动端&#xff08;UPS&#xff09;。 1. 应用端&#xff0c;采用微信小程序作为应用端控制界面。在开…

8个4K图片壁纸网站分享

整理了8个精选的图片壁纸网站&#xff0c;它们提供了丰富多样的壁纸选择&#xff0c;从自然风光到艺术创作&#xff0c;应有尽有。准备好让你的设备焕然一新了吗&#xff1f;让我们一起来看看这些壁纸宝藏吧&#xff01; 1、菜鸟图库 美女图片|手机壁纸|风景图片大全|高清图片…

工业交换机如何保证数据的访问安全

在现代工业自动化环境中&#xff0c;工业交换机作为关键的网络设备&#xff0c;扮演着数据传输和信息交互的重要角色。为了确保数据的访问安全&#xff0c;工业交换机不仅具备高效的转发性能&#xff0c;还集成了多层次的安全防护机制&#xff0c;以抵御各种潜在的网络威胁。 首…

Unity之FPS

目录 &#x1f3ae;MouseLook摄像机旋转脚本 &#x1f3ae;PickUpItem武器拾取脚本 &#x1f3ae;PlayerController玩家控制器 &#x1f3ae;Inventory武器库 &#x1f3ae;Weapon武器抽象类 &#x1f3ae;Weapon_AutomaticGun武器脚本 其实这个教程很早就收藏了就是被20…

MySQL之表内容的增删改查(含oracel 9i经典测试雇佣表下载)

目录 一:Create 二:Retrieve 1.select列 2.where条件 3.结果排序 4. 筛选分页结果 三:Update 四:Delete 1.删除数据 2. 截断表 五&#xff1a;插入查询结果 六&#xff1a;聚合函数 七:group by子句的使用 表内容的CRUD操作 : Create(创建), Retrieve(读取)…