【Linux】自定协议和序列化与反序列化
目录
一、序列化与反序列化概念
二、自定协议实现一个加法网络计算器
(一)TCP如何保证接收方的接收到数据是完整性呢?
(二)自定义协议
(三)自定义协议的实现
1、基础类
2、序列化与反序列化
3、报头的增删
4、从缓冲区内提取完整报文
5、自定协议在服务器与客户端的实现
三、使用Json进行序列化和反序列化
(一)概念
(二)Json的安装
(三)针对序列化和反序列化的改造
一、序列化与反序列化概念
在之前的文章中,我们调用网络接口进行数据传输时都是使用字符串作为数据直接进行传输的,但是在一些场景中单个字符串是不能满足需要的,可能需要一个结构体或是数据结构,那么如何将结构体或数据结构进行网络传输呢?
序列化:将数据结构或对象转换为字节流,以便它们可以存储到磁盘上、通过网络传输或在不同的程序间共享;
反序列化:存储的字节流或文本数据重新转换为原来的数据结构或对象。这个过程用于恢复存储的或传输的数据,以便程序能再次使用它。
当我们需要发送一个结构体时,我们可以先将其进行序列化转换为字节流再进行网络传输,而接收方可以通过将其反序列化从而恢复传输的数据,以此达到数据传输的目的。
二、自定协议实现一个加法网络计算器
(一)TCP如何保证接收方的接收到数据是完整性呢?
UDP是面向数据报,而TCP是面向字节流的。
在UDP协议传输数据时,由于数据的发送和接收都是按数据报的格式进行的,每个数据报是独立传输的。虽然UDP协议本身不保证数据的完整性和可靠性,但在网络传输没有出现丢包或错误的情况下,数据是完整的。
但在使用TCP协议传输数据时,由于数据的发送和接收都是按字节流的形式进行的,发送和接收到的数据不一定是完整的,假如TCP的服务端读取速度小于TCP客户端的发送速度,那么在缓冲区一定堆积了大量的报文,那么如何从缓冲区内提取到一条完整的报文数据呢?
其实我们调用接口进行网络数据传输本质实际是拷贝。发送方发送数据,接收方接收数据,本质实际是发送方缓冲区内容拷贝给接收方缓冲区。
对于上述的问题,如果发送方发送数据的速度过快,导致接收方缓冲区内堆积了大量的报文,那么如何从大量数据中提取出一个完整的报文呢?
实际上可以定制协议,以下是协议设计方式:
- 定长(规定每个报文的固定长度)
- 特殊符合(在报文之间加上特殊符合用于分割报文)
- 自描述方式(数据本身描述其格式、大小等)
(二)自定义协议
本文采用自描述方式设计自定协议:
首先本文是针对加法计算器而做的协议,而这个协议不仅要包括数据的序列化和反序列化,还要有增添减少报头分割符的接口。除此之外,因为使用面向字节流进行输出,还要保证如何从缓冲区内提取一个完整的结构。
(三)自定义协议的实现
1、基础类
class Request
{
public:int _x; //左操作数int _y; //右操作数char _op; //操作符
};
class Response
{
public:int _exitcode; //退出码int _result; //计算结果
};
2、序列化与反序列化
class Request
{
public:Request() {}Request(int x, int y, char op) : _x(x), _y(y), _op(op) {}//序列化bool serialize(string &out){out.clear();out += to_string(_x);out += SEP;out += _op;out += SEP;out += to_string(_y);return true;}//反序列化bool deserialize(const string &in){auto left = in.find(SEP);auto right = in.rfind(SEP);if (left == string::npos || right == string::npos || left == right)return false;if (right - left - SEP_LEN != 1)return false;string x_string = in.substr(0, left);string y_string = in.substr(right + SEP_LEN);if (x_string.empty() || y_string.empty())return false;_x = stoi(x_string);_y = stoi(y_string);_op = in[left + SEP_LEN];return true;}
public:int _x;int _y;char _op;
};
class Response
{
public:Response() {}Response(int exitcode, int result) : _exitcode(exitcode), _result(result) {}//序列化bool serialize(string &out){out.clear();out += to_string(_exitcode);out += SEP;out += to_string(_result);return true;}//反序列化bool deserialize(const string &in){auto index = in.find(SEP);if (index == string::npos)return false;string code = in.substr(0, index);string result = in.substr(index + SEP_LEN);if (code.empty() || result.empty())return false;_exitcode = stoi(code);_result = stoi(result);return true;}
public:int _exitcode;int _result;
};
3、报头的增删
#define SEP " "
#define SEP_LEN strlen(SEP)
#define SEP_LINE "\r\n"
#define SEP_LINE_LEN strlen(SEP_LINE)
//"text_len/r/text/r/n"
//增添报头
bool enLength(string &text)
{if (text.empty())return false;int len = text.size();string len_string = to_string(len);if (len_string.empty())return false;string ret;ret += len_string;ret += SEP_LINE;ret += text;ret += SEP_LINE;text = ret;return true;
}
//删除报头
bool deLength(const string &package, string &text)
{auto index = package.find(SEP_LINE);if (index == string::npos)return false;string len_string = package.substr(0, index);int len = stoi(len_string);text.clear();text = package.substr(index + SEP_LINE_LEN, len);return true;
}
4、从缓冲区内提取完整报文
bool recvPackage(const int &fd, string &inbuffer, string &text)
{while (true){char buffer[1024];ssize_t n = recv(fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;inbuffer += buffer;auto index = inbuffer.find(SEP_LINE);string len_string = inbuffer.substr(0, index);int len = stoi(len_string);int total = len + SEP_LINE_LEN * 2 + len_string.size();if (inbuffer.size() < total){cout << "正在等待后续消息" << endl;continue;}text = inbuffer.substr(0, total);inbuffer.erase(0, total);break;}elsereturn false;}return true;
}
5、自定协议在服务器与客户端的实现
//服务端
void handlerEntery(const int &fd, func_t func){string inbuffer;while (1){// 收到ReqRequest req;string req_str, req_text;if (recvPackage(fd, inbuffer, req_str) == false)return;// 去报头cout << "接收数据:" << req_str << endl;bool ret = deLength(req_str, req_text);if (ret == false)logMessage(ERROR, "Server : deLength fail");cout << "接收数据正文:" << req_text << endl;// 反序列化ret = req.deserialize(req_text);if (ret == false)logMessage(ERROR, "Server : deserialize fail");// 计算Response resp;func(req, resp);// 序列化string resp_text;ret = resp.serialize(resp_text);if (ret == false)logMessage(ERROR, "Server : deserialize fail");cout << "发送数据:" << resp_text << endl;// 加报头ret = enLength(resp_text);if (ret == false)logMessage(ERROR, "Server : enLength fail");cout << "发送数据正文:" << resp_text << endl;// 发送Respsend(fd, resp_text.c_str(), resp_text.size(), 0);}}
//客户端
void run(){struct sockaddr_in addr;bzero(&addr, 0);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(_Sip.c_str());addr.sin_port = htons(_Sport);if (connect(_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1){cerr << strerror(errno) << endl;exit(2);}else{string inbuffer;while (true){// 读取数据string line;cout << " please input#\n";getline(cin, line);Request req = parseLine(line);// 序列化string req_text;bool ret = req.serialize(req_text);if (ret == false)logMessage(ERROR, "Client : serialize fail");// 加报头ret = enLength(req_text);if (ret == false)logMessage(ERROR, "Client : enLength fail");// 发数据send(_fd, req_text.c_str(), req_text.size(), 0);// 接收数据string resp_str, resp_text;if (!recvPackage(_fd, inbuffer, resp_str))continue;// 去报头ret = deLength(resp_str, resp_text);if (ret == false)logMessage(ERROR, "Client : deLength fail");// 反序列化Response resp;ret = resp.deserialize(resp_text);if (ret == false)logMessage(ERROR, "Client : deserialize fail");cout << "exitcode :" << resp._exitcode << "\tresulet : " << resp._result << endl;}}}
三、使用Json进行序列化和反序列化
(一)概念
从上文可以得知,手动进行序列化和反序列化非常繁杂。实际上我们可以使用现成的方案。
Json是一种轻量级的数据交换格式,广泛用于网络应用中,尤其是在客户端与服务器之间的数据交换。它易于阅读和编写,同时也便于机器解析和生成。
(二)Json的安装
首先登录云服务器后输入指令:
sudo yum install -y jsonapp-devel
使用Json除了需要引入头文件以外还需要在编译中加入 -ljsoncpp 选项。
#include <jsoncpp/json/json.h>
g++ -g -o Server Server.cpp -std=c++11 -ljsoncpp
(三)针对序列化和反序列化的改造
class Request
{
public:Request() {}Request(int x, int y, char op) : _x(x), _y(y), _op(op) {}//序列化bool serialize(string &out){Json::Value root;root["left"] = _x;root["right"] = _y;root["op"] = _op;Json::FastWriter writer;out = writer.write(root); return true;}//反序列化bool deserialize(const string &in){Json::Value root;Json::Reader reader;reader.parse(in, root);_x = root["left"].asInt();_y = root["right"].asInt();_op = root["op"].asInt();return true;}
public:int _x;int _y;char _op;
};
class Response
{
public:Response() {}Response(int exitcode, int result) : _exitcode(exitcode), _result(result) {}//序列化bool serialize(string &out){Json::Value root;root["exitcode"] = _exitcode;root["result"] = _result;Json::FastWriter writer;out = writer.write(root);return true;}//反序列化bool deserialize(const string &in){Json::Value root;Json::Reader reader;reader.parse(in, root);_exitcode = root["exitcode"].asInt();_result = root["result"].asInt();return true;}public:int _exitcode;int _result;
};
相关文章:

【Linux】自定协议和序列化与反序列化
目录 一、序列化与反序列化概念 二、自定协议实现一个加法网络计算器 (一)TCP如何保证接收方的接收到数据是完整性呢? (二)自定义协议 (三)自定义协议的实现 1、基础类 2、序列化与反序列…...
C++基础系列【19】运算符重载
博主介绍:程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章,首发gzh,见文末👇…...

Python-04BeautifulSoup网络爬虫
2025-03-04-BeautifulSoup网络爬虫 记录BeautifulSoup网络爬虫的核心知识点 文章目录 2025-03-04-BeautifulSoup网络爬虫 [toc]1-参考网址2-学习要点3-核心知识点1. 安装2. 导入必要的库3. 发送 HTTP 请求4. 创建 BeautifulSoup 对象5. 解析 HTML 内容5.1 查找标签5.2 根据属性…...

芯科科技通过全新并发多协议SoC重新定义智能家居连接
MG26系列SoC现已全面供货,为开发人员提供最高性能和人工智能/机器学习功能 致力于以安全、智能无线连接技术,建立更互联世界的全球领导厂商Silicon Labs(亦称“芯科科技”,NASDAQ:SLAB),日前宣…...

python-leetcode-零钱兑换 II
518. 零钱兑换 II - 力扣(LeetCode) 这个问题是 完全背包问题 的一个变体,可以使用 动态规划 来解决。我们定义 dp[i] 为凑成金额 i 的硬币组合数。 思路: 定义 DP 数组 设 dp[i] 表示凑成金额 i 的组合数,初始化 dp[…...

【RabbitMQ】Producer之TTL过期时间 - 基于AMQP 0-9-1
这篇文章和大家分享Producer发布消息时如何设置消息过期时间,包括队列级别和消息级别,还有如何设置队列的过期时间。 消息过期时间 给消息设置TTL,在超过TTL值后,消息就会变成dead message(死信)…...

演示汉字笔顺的工具
视频需要审核,还是gif比较方便,本来就不长。 给小学生辅导汉字笔顺的时候,先是发现“百度汉语”里面有很多类似的笔顺的动画,非常方便。但总是需要上网,而且百度上并不提供针对特定汉字的方便的检索途径,加…...

JVM简单了解
一、JVM概述 目录 一、JVM概述 1.jvm的作用 2.jvm的组成 2.1类加载 2.1.1加载 2.1.2链接 2.1.3初始化 2.1.4类加载器分类 2.1.5双亲委派机制 2.2运行时数据区 2.2.1程序计数器 2.2.2虚拟机栈 2.2.3本地方法栈 2.2.4java堆内存 2.2.5方法区 2.3本地方法库接口 …...

【CSS—前端快速入门】CSS 选择器
CSS 1. CSS介绍 1.1 什么是CSS? CSS(Cascading Style Sheet),层叠样式表,用于控制页面的样式; CSS 能够对网页中元素位置的排版进行像素级精确控制,实现美化页面的效果;能够做到页面的样式和 结构分离; 1…...

【MYSQL数据库异常处理】执行SQL语句报超时异常
MYSQL执行SQL语句异常:The last packet successfully received from the server was 100,107 milliseconds ago. The last packet sent successfully to the server was 100,101 milliseconds ago. 这个错误表明 MySQL 服务器与 JDBC 连接之间的通信超时了。通常由…...

【Day9】make/makeFile如何让项目构建自动化起飞
【Day9】make/makeFile如何让项目构建自动化起飞 使用make命令编写makefile文件依赖管理增量构建makefile注释:#makefile其他语法 make/makefile递归式工作过程 在Linux中,项目自动化构建是指使用一系列工具和脚本来自动执行软件项目的编译、测试、打包和…...

【单片机】嵌入式系统的硬件与软件特性
嵌入式系统的软件结构 嵌入式系统的软件结构一般分为 不带操作系统(Bare Metal) 和 带操作系统(RTOS / Linux) 两种。不同的软件架构适用于不同的应用场景,如 简单控制系统、实时控制系统、物联网、工业自动化等。 嵌…...

C语言学习笔记-初阶(30)深入理解指针2
1. 数组名的理解 在上一个章节我们在使用指针访问数组的内容时,有这样的代码: int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0]; 这里我们使用 &arr[0] 的方式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址&…...

ROM修改进阶教程------修改安卓机型SELinux宽容的几种方式方法 以及第三方系统中如何关闭SELinux宽容
SELinux是一种强制访问控制安全机制,用于增强Linux系统的安全性。在某些情况下,可能需要对 SELinux 进行宽容设置,以满足特定的应用需求。当SELinux处于宽容模式时,系统允许违反安全策略的行为发生,但不会阻止这些行为,通常会在日志中记录这些违规事件。这与强制模式不同…...

亚马逊云科技Marketplace(中国区)上架专业服务产品, “云生态连接器”价值凸显
近日,由西云数据运营的亚马逊云科技Marketplace(中国区)正式支持专业服务产品。此次发布将大幅简化企业对云专业服务的采购流程,实现云软件从规划、部署到支持的全生命周期管理,同时也为合作伙伴提供了更多的销售机会。…...

【音视频】音频基础
一、音频基础 1.1 声音的物理性质 ——振动 声音是一种由物体振动引发的物理现象,如小提琴的弦声等。物体的振动使其四周空气的压强产生变化,这种忽强忽弱变化以波的形式向四周传播,当被人耳所接收时,我们就听见了声音。 1.2 声…...
策略模式的C++实现示例
核心思想 策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装在独立的类中,使得它们可以互相替换。策略模式让算法的变化独立于使用它的客户端,从而使得客户端可以根据需要动态切换算法,而不需要修改…...

本地部署pangolin获取谱系,从而达到预测新冠的流行趋势
步骤 1:安装Docker 注:此步骤忽略,可通过Docker官网对于文档进行安装,地址如下 Docker: Accelerated Container Application Developmenthttps://www.docker.com/ 步骤 2:拉取Pangolin镜像 docker pull staphb/pangolin:latest 步…...

【我的 PWN 学习手札】House of Emma
House of Emma 参考文献 第七届“湖湘杯” House _OF _Emma | 设计思路与解析-安全KER - 安全资讯平台 文章 - house of emma 心得体会 - 先知社区 前一篇博客【我的 PWN 学习手札】House of Kiwi-CSDN博客的利用手法有两个关键点,其一是利用__malloc_assert进入…...
4 Redis4 List命令类型讲解
Redis 列表(List)命令详解 1. Redis 列表(List)简介 Redis 列表(List)是一个简单的字符串列表,按照插入顺序排序。它可以用作 栈(Stack) 和 队列(Queue&…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...