当前位置: 首页 > news >正文

Linux网络序列化与反序列化(6)

文章目录

  • 前言
  • 一、什么是协议?
    • 协议的概念
    • 结构化数据的传输
    • 序列化与反序列化
  • 二、Request & Response
    • Request类
    • Response类
    • 综合
  • 三、网络版计算器
    • 定义请求和响应协议
    • Tcp服务端设计
    • 业务端处理逻辑
    • Tcp客户端代码
  • 总结


前言

  Hello,我又回来了,也是该静下心来好好继续沉淀了
  不过话说真是离谱,之前的很多知识都快忘记了,幸好有博客的帮助,虽然费时间也是真的费时间,不过总体来说还是利大于弊的


一、什么是协议?

协议的概念

  协议就是指的网络协议,网络协议是通信计算机双方必须共同遵从的一组约定,比如怎么建立连接、怎么互相识别等。

  本质上就是一种约定

结构化数据的传输

  在网络通信中,数据通常以字节流的形式发送和接收,但是举个例子,在QQ群聊中,除了文字消息外,还包含头像、时间和昵称,这些信息都需要以某种方式发送给对方,如果逐个发送就很麻烦,接收方也很难处理,所以我们考虑将这些数据进行打包

在这里插入图片描述

  这样的话,未来接收方只需要通过 对象.url、对象.time 就可以了拿到数据了

  而这里的结构体如 message 就是传说中的业务协议!是不是很惊讶!所以其实没那么玄乎,放开心就行

序列化与反序列化

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

在这里插入图片描述

二、Request & Response

  接下来我们就通过实例来感受什么是序列化,什么是反序列化

  序列化的作用是将类中的成员变量转换成字符串格式,方便在网络中传输;反序列化的作用是将字符串解析回类的成员变量,恢复为结构化数据

  知道这个后,我们来实现一个比较简易的计算器

Request类

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

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

构造函数:

Request()
{}

  这个是默认构造函数,不进行任何初始化操作

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

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

序列化函数: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” 的格式

反序列化函数: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 和 _y。substr(0, left) 获取左侧字符串,即第一个操作数,substr(right + SPACE_LEN) 获取右侧字符串,即第二个操作数。
  4. 从字符串 str 中获取操作符 _op,位于第一个空格后的位置。
  5. 如果解析成功,返回 true;否则返回 false。

在这里插入图片描述

  至于atoi函数,大家可以自行翻阅头文件cstdlib

  具体我们来重点关注一下如何从字符串中获得想要的操作符_op

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

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

成员变量:

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

Response类

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

构造函数:

Response()
{}

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

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

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

序列化函数:Serialize()

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

  功能:将 Response 对象的两个成员变量 _code 和 _ret 组合成字符串。

反序列化函数: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 对象

成员变量:

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; // 计算结果的状态码
};

三、网络版计算器

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

  总体框架图如下:
在这里插入图片描述

定义请求和响应协议

  写到这里其实你可以再次回顾一下OSI七层模型,看看协议对应那一层,相信我,你会有更深的感悟的

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;   // 计算结果
};

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);}}
};

业务端处理逻辑

  处理请求的逻辑被封装在 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;}
}

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;
};

总结

  通过序列化与反序列化,应用程序与网络通信得到了有效解耦,事实上,序列化与反序列化这种工作也轮不到我们来做,因为有更好更强的库,比如 Json、XML、Protobuf 等,这个我们后面再说

http://www.xdnf.cn/news/1438237.html

相关文章:

  • Linux文本处理——awk
  • 飞牛OS Nas,SSH安装宝塔后,smb文件不能共享问题
  • STM32——串口
  • 2025年- H109-Lc1493. 删掉一个元素以后全为 1 的最长子数组(双指针)--Java版
  • 别再误会了!Redis 6.0 的多线程,和你想象的完全不一样
  • 从入门到实战:Linux sed命令全攻略,文本处理效率翻倍
  • 【机器学习深度学习】向量模型与重排序模型:RAG 的双引擎解析
  • 使用DataLoader加载本地数据 食物分类案例
  • GitHub Classroom:编程教育的高效协作方案
  • MySQL查询limit 0,100和limit 10000000,100有什么区别?
  • Shell编程从入门到实践:基础语法与正则表达式文本处理指南
  • 如何在部署模型前训练出完美的AI提示词
  • C# 中这几个主流的 ORM(对象关系映射器):Dapper、Entity Framework (EF) Core 和 EF 6
  • 11.《简单的路由重分布基础知识探秘》
  • 硬件:51单片机
  • 为什么需要锁——多线程的数据竞争是怎么引发错误的
  • 系统架构——过度设计
  • YOLOv8改进有效系列大全:从卷积到检测头的百种创新机制解析
  • 【C++上岸】C++常见面试题目--数据结构篇(第十七期)
  • 02-Media-2-ai_rtsp.py 人脸识别加网络画面RTSP推流演示
  • 51单片机(单片机基础,LED,数码管)
  • Spring Boot手写10万敏感词检查程序
  • UCIE Specification详解(十三)
  • C++ 条件变量,互斥锁
  • 【c++】多态+RTTI (运行时的类型识别信息)
  • 深度学习篇---DenseNet
  • 深入解析Linux进程概念与操作系统核心
  • 深度学习篇---SGD优化器
  • 「数据获取」《安徽建设统计年鉴》(2002-2007)(2004、2006缺失)(获取方式看绑定的资源)
  • spring boot驴友结伴游网站的设计与实现(代码+数据库+LW)