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

目录

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 123int num2 = atoi(str2); // num2 will be 12int 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 _ystd::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 _retstd::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; // 操作数1int _y; // 操作数2char _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.rhkb.cn/news/428536.html

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

相关文章

JavaWeb - 5 - 前端工程化

一.前后端分离开发 前后端混合开发 缺点&#xff1a;沟通成本高&#xff0c;分工不明确&#xff0c;不便管理&#xff0c;不便维护拓展 前后端分离开发 当前最为主流的开发模式&#xff1a;前后端分离 前后端分离开发中很重要的是API接口文档&#xff08;如&#xff1a;YApi&…

胤娲科技:谷歌DeepMind祭出蛋白质设计新AI——癌症治疗迎来曙光

在科技的浩瀚星空中&#xff0c;DeepMind的“阿尔法”家族总是能带来令人瞩目的璀璨光芒。这一次&#xff0c;它们再次以惊人的姿态&#xff0c; 将AI的触角深入到了生命的微观世界——蛋白质设计领域&#xff0c;为我们描绘了一幅未来医疗的宏伟蓝图。 想象一下&#xff0c;一…

Scrapy爬虫实战——某瓣250

# 按照我个人的习惯&#xff0c;在一些需要较多的包作为基础支撑的项目里&#xff0c;习惯使用虚拟环境&#xff0c;因为这样能极大程度的减少出现依赖冲突的问题。依赖冲突就比如A、B、C三个库&#xff0c;A和B同时依赖于C&#xff0c;但是A需要的C库版本大于N&#xff0c;而B…

VUE3配置路由(超级详细)

第一步创建vue3的项目

多版本node管理工具nvm

什么是nvm&#xff1f; 在项目开发过程中&#xff0c;使用到vue框架技术&#xff0c;需要安装node下载项目依赖&#xff0c;但经常会遇到node版本不匹配而导致无法正常下载&#xff0c;重新安装node却又很麻烦。为解决以上问题&#xff0c;nvm&#xff1a;一款node的版本管理工…

微服务-- Sentinel的使用

目录 Sentinel&#xff1a;微服务的哨兵 生态系统景观 sentinel与spring cloud Hystrix 对比 Sentinel 主要分为两部分 Sentinel安装与使用 Sentinel的控制规则 流控规则 流控规则的属性说明 新增流控规则 关联流控模式 SentinelResource注解的使用 SentinelResou…

mysql-死锁

文章目录 1、概念1.1、创建表 account1.2、id 自动创建 主键索引 primary1.3、name 没有创建索引 2、产生死锁的必要条件2.1、此时 name 没有创建 索引 3、如何处理死锁3.1、方式1&#xff1a;等待&#xff0c;直到超时&#xff08;innodb_lock_wait_timeout50s&#xff09;3.2…

GRE隧道在实际部署中的优化、局限性与弊端

GRE的其他特性 上一篇光讲解配置就花了大量的篇幅&#xff0c;还一些特性没有讲解的&#xff0c;这里在来提及下。 1、动态路由协议 在上一篇中是使用的静态路由&#xff0c;那么在动态路由协议中应该怎么配置呢&#xff1f; undoip route-static 192.168.20.0 255.255.255.0 …

element-plus的菜单组件el-menu

菜单是几乎是每个管理系统的软件系统中不可或缺的&#xff0c;element-plus提供的菜单组件可以快速完成大部分的菜单的需求开发&#xff0c; 该组件内置和vue-router的集成&#xff0c;使用起来很方便。 主要组件如下 el-menu 顶级菜单组件 主要属性 mode:决定菜单的展示模式…

2024/9/20 使用QT实现扫雷游戏

有三种难度初级6x6 中级10x10 高级16x16 完成游戏 游戏失败后&#xff0c;无法再次完成游戏&#xff0c;只能重新开始一局 对Qpushbutton进行重写 mybutton.h #ifndef MYBUTTON_H #define MYBUTTON_H #include <QObject> #include <QWidget> #include <QPus…

2024年8月HarmonyOS鸿蒙应用开发者高级认证全新题库

有题库在手&#xff0c;一小时轻松拿下鸿蒙高级。你们需要也可以无偿分享哦&#xff01; 项目需要为不同的设备形态(如手机 、 智能手表)提供定制化构建 。请说明如何在 DevEcostudio 中 设置不同的构建配置&#xff0c; 以生成针对不同设备的 hap 包&#xff1a; 在模块级别 b…

JavaWeb的Filter详解

过滤器Filter 什么是Filter&#xff1f; 依据字面上的中文意思为过滤器。Filter的作用 当用户的请求到达指定的URL之前&#xff0c;可以借助Filter来改变这些请求的内容&#xff1b;同样地&#xff0c;当响应结果到达客户端之前&#xff0c;可以使用Filter修改输出的内容。什么…

【数据结构入门】排序算法之三路划分与非比较排序

文章目录 前言 一、三路划分优化 1.1. 基本思想 1.2. 实现步骤 1.3. 优点 1.4 代码实现 二、非比较排序 2.1 计数排序 2.1.1基本思想 2.1.2具体步骤 2.1.3算法特性 2.1.4算法实现 2.2 基数排序 2.2.1基本思想 2.2.2具体步骤 2.2.3 基数排序的方法 2.2.4算法特…

MongoDB在Linux系统中的安装与配置指南

在这篇文章中&#xff0c;我们将介绍如何在CentOS 7服务器上安装MongoDB&#xff0c;并通过DataX将数据从MongoDB迁移到MySQL数据库。这将包括MongoDB的安装、配置、数据准备以及使用DataX进行数据迁移的详细步骤。 MongoDB简介 MongoDB是一个高性能、开源、无模式的文档型数据…

Leetcode面试经典150题-97.交错字符串

给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串 &#xff1a; s s1 s2 ... snt t1 t2 ... tm|n - m| < 1交错 是…

Springboot3 + MyBatis-Plus + MySql + Uniapp 实现商品规格选择sku(附带自设计数据库,最新保姆级教程)

Springboot3 MyBatis-Plus MySql Uniapp 实现商品规格选择sku&#xff08;附带自设计数据库&#xff0c;最新保姆级教程&#xff09; 1、效果展示2、数据库设计2.1 商品表2.2 商品价格和规格中间表2.3 商品规格表 3、后端代码3.1 model3.2 vo3.3 mapper、server、serverImp3…

使用express或koa或nginx部署history路由模式的单页面应用

使用hash模式会有#&#xff0c;影响美观&#xff0c;所以使用history模式会是个更好的选择。 前端项目打包上线部署&#xff0c;可以使用下面的方式部署history模式的项目&#xff0c;下面以 jyH5 为例 expressjs部署 express脚手架搭建的app.js中添加如下代码&#xff1a; …

CDA Level 1 业务数据分析

目录 理解业务数据分析方法、掌握业务数据分析流程、能够使用及设计创建业务指标、能够结合业务模型及业务分析方法正确理解业务问题&#xff0c;找到问题原因&#xff0c;并能够提出解决问题建议&#xff0c;这个章节的应用会考的比较多&#xff08;终于正经起来了呢&#xf…

设计模式 组合模式(Composite Pattern)

组合模式简绍 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以用一致的方式处理单个对象和组合对象。这样&#xff0c;可以在不知道对象具体类型的条…

7--SpringBoot-后端开发、原理详解(面试高频提问点)

目录 SpringBoot原理 起步依赖 自动配置 配置优先级 Bean设置 获取Bean 第三方Bean SpringBoot原理 内容偏向于底层的原理分析 基于Spring框架进行项目的开发有两个不足的地方&#xff1a; 在pom.xml中依赖配置比较繁琐&#xff0c;在项目开发时&#xff0c;需要自己去找…