【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&…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
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))…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...

相关类相关的可视化图像总结
目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)
注:文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件:STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...

使用ch340继电器完成随机断电测试
前言 如图所示是市面上常见的OTA压测继电器,通过ch340串口模块完成对继电器的分路控制,这里我编写了一个脚本方便对4路继电器的控制,可以设置开启时间,关闭时间,复位等功能 软件界面 在设备管理器查看串口号后&…...

基于谷歌ADK的 智能产品推荐系统(2): 模块功能详解
在我的上一篇博客:基于谷歌ADK的 智能产品推荐系统(1): 功能简介-CSDN博客 中我们介绍了个性化购物 Agent 项目,该项目展示了一个强大的框架,旨在模拟和实现在线购物环境中的智能导购。它不仅仅是一个简单的聊天机器人,更是一个集…...