[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.xdnf.cn/news/1540231.html

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

相关文章

免费下载PDF | 自然语言处理新范式:基于预训练模型的方法

前言 本次给大家推荐阅读的书籍是——《自然语言处理&#xff1a;基于预训练模型的方法》。近些年来&#xff0c;以GPT、BERT为代表的预训练模型在自然语言处理领域掀起了一股浪潮&#xff0c;打开了“预训练精调”的自然语言处理新范式的大门。 由电子工业出版社出版的《自然…

动手学深度学习(pytorch土堆)-06损失函数与反向传播、模型训练、GPU训练

模型保存与读取 完整模型训练套路 import torch import torchvision.datasets from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriterfrom model impo…

AV1 Bitstream Decoding Process Specification--[7]: 语法结构语义-3

原文地址&#xff1a;https://aomediacodec.github.io/av1-spec/av1-spec.pdf 没有梯子的下载地址&#xff1a;AV1 Bitstream & Decoding Process Specification摘要&#xff1a;这份文档定义了开放媒体联盟&#xff08;Alliance for Open Media&#xff09;AV1视频编解码…

分发饼干00

题目链接 分发饼干 题目描述 注意点 1 < g[i], s[j] < 2^31 - 1目标是满足尽可能多的孩子&#xff0c;并输出这个最大数值 解答思路 可以先将饼干和孩子的胃口都按升序进行排序&#xff0c;随后根据双指针 贪心&#xff0c;将当前满足孩子胃口的最小饼干分配给该孩…

再次理解UDP协议

一、再谈端口号 在 TCP / IP 协议中&#xff0c;用 "源 IP", "源端口号", "目的 IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过 netstat -n 查看) 我们需要端口号到进程的唯一性&#xff0c;所以一个…

Obsidian如何粘贴的图片类似于Typora,图片相对当前路径

添加插件 下载插件&#xff1a; Custom Attachment Location 基础设置 同时需要在下面进行设置 示意效果

大数据多集群数据作业和集群状态监控

目前手里面有四套大数据集群的作业需要维护&#xff0c;分别属于不同的客户&#xff0c;我同岗位的兄弟离职后&#xff0c;所有的数据作业都落到我头上了&#xff0c;公司也不招人了。开发新的数据作业倒没有什么问题&#xff0c;就是客户叫我补数的时候&#xff0c;头比较大&a…

Linux基础权限

Linux基础权限 shell的概念Linux基础权限Linux的两种用户Linux的权限管理权限认知权限设置权限掩码粘滞位 shell的概念 &#xff08;shell&#xff09;命令行解释器 的存在意义&#xff1a; 将用户的命令翻译给操作系统&#xff0c;然后返回OS的结果给用户&#xff1b;保护OS…

YOLOv5图像识别教程包成功-以识别桥墩缺陷详细步骤分享

前置环境资源下载 提示&#xff1a;要开外网才能下载的环境我都放在了网盘里&#xff0c;教程中用到的环境可从这里一并下载&#xff1a; https://pan.quark.cn/s/f0c36aa1ef60 1. 下载YOLOv5源码 官方地址&#xff1a;GitHub - ultralytics/yolov5: YOLOv5 &#x1f680; …

9.4 溪降技术:带包下降

目录 9.4 携包下降概述观看视频课程电子书&#xff1a;携包下降在瀑布中管理背包扔背包滑索传送背包固定到安全带 V7 提示&#xff1a;将背包固定到安全带总结 9.4 携包下降 概述 在水流和悬崖边缘携包下降是最危险的情况&#xff01; 正如我们之前所学&#xff0c;在峡谷探险中…

流程型制造业MES系统的特点及主要功能介绍

流程型MES系统的应用程度较高。特别是石油石化行业原有自动化和信息化的程度较高&#xff0c;一般应用在于生产管控&#xff0c;mes系统的应用主要目的是使得最容易出现产品质量的配料、投料以及乳化加工过程得到管控和追溯。 随着生产工艺发展&#xff0c;石化行业MES系统应用…

Java基础(中)

面向对象基础 面向对象和面向过程的区别 面向过程编程&#xff08;Procedural-Oriented Programming&#xff0c;POP&#xff09;和面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是两种常见的编程范式&#xff0c;两者的主要区别在于解决…

Java设计模式——工厂方法模式(完整详解,附有代码+案例)

文章目录 5.3 工厂方法模式5.3.1概述5.3.2 结构5.3.3 实现 5.3 工厂方法模式 针对5.2.3中的缺点&#xff0c;使用工厂方法模式就可以完美的解决&#xff0c;完全遵循开闭原则。 5.3.1概述 工厂方法模式&#xff1a;定义一个创建对象的接口&#xff08;这里的接口指的是工厂&…

逆向中巧遇MISC图片隐藏

这道题比较有意思&#xff0c;而且因为我对misc并不是很熟悉&#xff0c;发现该题目将flag隐藏在图片的颜色属性&#xff0c;巧妙的跟踪到这些密文位置&#xff0c;拿下题目一血&#xff0c;还是很有参考学习意义的。&#xff08;题目附件&#xff0c;私信发。&#xff09; 1、…

openstack 2023.2 Bobcat 本地安装部署

一、系统环境 rootodoo16e-server:~# cat /etc/lsb-release DISTRIB_IDUbuntu DISTRIB_RELEASE22.04 DISTRIB_CODENAMEjammy DISTRIB_DESCRIPTION"Ubuntu 22.04.5 LTS"rootodoo16e-server:~# python3 --version Python 3.10.12rootodoo16e-server:~# pip --version …

基于YOLOv5s的无人机航拍输电线瓷瓶检测(附数据集与操作步骤)

本文主要内容:详细介绍了无人机航拍输电线瓷瓶检测的整个过程&#xff0c;从创建数据集到训练模型再到预测结果全部可视化操作与分析。 文末有数据集获取方式&#xff0c;请先看检测效果 现状 输电线路绝缘瓷瓶的检测主要依赖人工巡检。巡检人员需携带专业设备&#xff0c;攀…

亿级数据表多线程update锁表问题

目录 1、问题描述 2、原因分析 3、问题解决 1、问题描述 在pg数据库&#xff0c;某个业务&#xff0c;有一张数据表test&#xff0c;数据表结果如下&#xff1a; test(sjjbh,wlbid,gzmb,sfzg,zgsj,cjsj,xx...)&#xff0c;这个表没有主键&#xff0c;会有很多重复数据。 tes…

Vue报错 ‘vite‘ 不是内部或外部命令,也不是可运行的程序或批处理文件

报错 vue-project0.0.0 dev vite‘vite’ 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。解决 第1步. 控制台输入 npm install -g create-vite第2步. 控制台输入 npm install -g vite第3步. 运行就ok啦

【HTTP】方法(method)以及 GET 和 POST 的区别

文章目录 方法&#xff08;method&#xff09;登录上传GET 和 POST 有什么区别&#xff08;面试&#xff09;区别不准确的说法 方法&#xff08;method&#xff09; 首行中的第一部分。首行是由方法、URL 和版本号组成 方法描述了这次请求想干什么&#xff0c;最主要的是&…

13 vue3之内置组件keep-alive

内置组件keep-alive 有时候我们不希望组件被重新渲染影响使用体验&#xff1b;或者处于性能考虑&#xff0c;避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。 开启keep-alive 生命周期的变化 初次进入时&#xff1a;…