【Linux】应用层自定义协议与序列化
🌈 个人主页:Zfox_
🔥 系列专栏:Linux
目录
- 一:🔥 应用层
- 🦋 再谈 "协议"
- 🦋 网络版计算器
- 🦋 序列化 和 反序列化
- 二:🔥 重新理解 read、 write、 recv、 send 和 tcp 为什么支持全双工
- 三:🔥 开始实现
- 🦋 定制协议
- 四:🔥 为关于流式数据的处理
- 五:🔥 Jsoncpp
- 🦋 特性
- 🦋 安装
- 🦋 序列化
- 🦋 反序列化
- 🦋 总结
- 六:🔥 共勉
一:🔥 应用层
📚 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层
🦋 再谈 “协议”
协议是一种 “约定”. socket api 的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些 “结构化的数据” 怎么办呢?
其实, 协议就是双方约定好的结构化的数据
🦋 网络版计算器
- 例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端
📚 约定方案一:
- 客户端发送一个形如"1+1"的字符串;
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格;
- …
📚 约定方案二:
定义结构体来表示我们需要交互的信息;- 发送数据时将这个
结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;- 这个过程叫做
"序列化"和"反序列化"
🦋 序列化 和 反序列化

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是 ok 的. 这种约定, 就是 应用层协议
但是, 为了让我们深刻理解协议, 我们打算自定义实现一下协议的过程。
- 我们采用方案 2, 我们也要体现协议定制的细节
- 我们要引入 序列化 和 反序列化, 只不过我们直接采用现成的方案 – jsoncpp库
- 我们要对 socket 进行字节流的读取处理
二:🔥 重新理解 read、 write、 recv、 send 和 tcp 为什么支持全双工

所以:
- 在任何一台主机上, TCP 连接既有发送缓冲区, 又有接受缓冲区, 所以, 在内核中, 可以在发消息的同时, 也可以收消息, 即全双工
- 这就是为什么一个 tcp sockfd 读写都是它的原因
- 实际数据什么时候发, 发多少, 出错了怎么办, 由 TCP 控制, 所以 TCP 叫做传输控制协议
- 那么,也就需要应用层保证自己报文的完整性
三:🔥 开始实现
🦋 定制协议
📚 基本结构
- 定制基本的结构化字段, 这个就是协议
报文的自描述字段:
“len\r\nx op y\r\n” : \r\n 不属于报文的一部分, 约定

#pragma once#include <string>
#include <iostream>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>// _x _oper _y
class Request
{
public:Request() : _x(0), _y(0), _oper(0) {}Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}bool Serialize(std::string &out_string){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::StreamWriterBuilder wb;std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());std::stringstream ss;w->write(root, &ss);out_string = ss.str();return true;}bool Deserialize(std::string in_string) {Json::Value root;Json::Reader reader;bool parsingSuccessful = reader.parse(in_string, root);if(!parsingSuccessful){std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;return false;}_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;}void Print(){std::cout << _x << std::endl;std::cout << _oper << std::endl;std::cout << _y << std::endl;}int X() const {return _x;}int Y() const {return _y;}char Oper() const {return _oper;}private:int _x;int _y;char _oper;
};class Response
{
public:Response(): _result(0), _code(0) {}Response(int result, int code) : _result(result), _code(code) {}bool Serialize(std::string &out_string){Json::Value root;root["result"] = _result;root["code"] = _code;Json::StreamWriterBuilder wb;std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());std::stringstream ss;w->write(root, &ss);out_string = ss.str();return true;}bool Deserialize(std::string in_string){Json::Value root;Json::Reader reader;bool parsingSuccessful = reader.parse(in_string, root);if(!parsingSuccessful){std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;return false;}_result = root["result"].asInt();_code = root["code"].asInt();return true;}int Result() const {return _result;}int Code() const {return _code;}void SetResult(int result) {_result = result;}void SetCode(int code) {_code = code;}private:int _result; // 结果int _code; // 出错码: 0, 1, 2, 3, 4
};
四:🔥 为关于流式数据的处理
- 你如何保证你每次读取就能读完请求缓冲区的所有内容?
- 你怎么保证读取完毕或者读取没有完毕的时候, 读到的就是一个完整的请求呢?
- 处理 TCP 缓冲区中的数据, 一定要保证正确处理请求
const std::string Sep = "\r\n";// 报头
bool Encode(std::string &message)
{if(message.empty()) return false;std::string package = std::to_string(message.size()) + Sep + message + Sep;message = package;return true;
}// len\n{json}\n 解包
// 123\r\n{json}\r\n -> {json}
bool Decode(std::string &package, std::string *content)
{auto pos = package.find(Sep);if(pos == std::string::npos) return false; std::string content_length_str = package.substr(0, pos); int content_length = std::stoi(content_length_str);int full_length = content_length_str.size() + content_length + 2 * Sep.size(); if(package.size() < full_length) return false;*content = package.substr(pos + Sep.size(), content_length);package.erase(0, full_length);return true;
}
📚 完整的处理过程应该是:

五:🔥 Jsoncpp
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。 Jsoncpp 是开源的, 广泛用于各种需要处理 JSON 数据的 C++ 项目中。
🦋 特性
- 简单易用: Jsoncpp 提供了直观的 API, 使得处理 JSON 数据变得简单。
- 高性能: Jsoncpp 的性能经过优化, 能够高效地处理大量 JSON 数据。
- 全面支持: 支持 JSON 标准中的所有数据类型, 包括对象、 数组、 字符串、 数字、 布尔值和 null。
- 错误处理: 在解析 JSON 数据时, Jsoncpp 提供了详细的错误信息和位置, 方便开发者调试。
当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时, 确实存在不同的做法和工具类可供选择。 以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:
🦋 安装
C++
ubuntu: sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
🦋 序列化
📚 序列化指的是将数据结构或对象转换为一种可以存储或传输的格式(如字节流、JSON、XML 等)
Jsoncpp 提供了多种方式进行序列化:
- 使用
Json::StreamWriter:
○ 优点: 提供了更多的定制选项, 如缩进、 换行符等。
○ 示例:
C++
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}$ ./test.exe
{"name" : "joe","sex" : "男"
}
- 使用 Json::Value 的
toStyledString方法:
○ 优点: 将 Json::Value 对象直接转换为格式化的 JSON 字符串。
○ 示例:
C++
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";std::string s = root.toStyledString();std::cout << s << std::endl;return 0;
} $ ./test.exe
{"name" : "joe","sex" : "男"
}
- 使用
Json::FastWriter
○ 优点: 比 StyledWriter 更快, 因为它不添加额外的空格和换行符。
○ 示例:
C++
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::FastWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
} $ ./test.exe
{"name":"joe","sex":"男"}#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";// Json::FastWriter writer;Json::StyledWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
} $ ./test.exe
{"name" : "joe","sex" : "男"
}
🦋 反序列化
反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。 Jsoncpp 提供了以下方法进行反序列化:
- 使用
Json::Reader:
○ 优点: 提供详细的错误信息和位置, 方便调试。
○ 示例:
C++
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>int main() {// JSON 字符串std::string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";// 解析 JSON 字符串Json::Reader reader;Json::Value root;// 从字符串中读取 JSON 数据bool parsingSuccessful = reader.parse(json_string, root);if (!parsingSuccessful) {// 解析失败, 输出错误信息std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;return 1;
} // 访问 JSON 数据std::string name = root["name"].asString();int age = root["age"].asInt();std::string city = root["city"].asString();// 输出结果std::cout << "Name: " << name << std::endl;std::cout << "Age: " << age << std::endl;std::cout << "City: " << city << std::endl;return 0;
} $ ./test.exe
Name: 张三
Age: 30
City: 北京
- 使用 Json::CharReader 的派生类(不推荐了, 上面的足够了):
○ 在某些情况下, 你可能需要更精细地控制解析过程, 可以直接使用 Json::CharReader 的派生类。
○ 但通常情况下, 使用 Json::parseFromStream 或 Json::Reader 的 parse 方法就足够了。
🦋 总结
• toStyledString、 StreamWriter 和 FastWriter 提供了不同的序列化选项,你可以根据具体需求选择使用。
• Json::Reader 和 parseFromStream 函数是 Jsoncpp 中主要的反序列化工具,它们提供了强大的错误处理机制。
• 在进行序列化和反序列化时, 请确保处理所有可能的错误情况, 并验证输入和输出的有效性。
六:🔥 共勉
以上就是我对 【Linux】应用层自定义协议与序列化 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉

相关文章:
【Linux】应用层自定义协议与序列化
🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 一:🔥 应用层 🦋 再谈 "协议"🦋 网络版计算器🦋 序列化 和 反序列化 二:🔥 重新理解 read、…...
深度学习中的张量 - 使用PyTorch进行广播和元素级操作
深度学习中的张量 - 使用PyTorch进行广播和元素级操作 元素级是什么意思? 元素级操作在神经网络编程中与张量的使用非常常见。让我们从一个元素级操作的定义开始这次讨论。 一个_元素级_操作是在两个张量之间进行的操作,它作用于各自张量中的相应元素…...
gitignore忽略已经提交过的
已经在.gitignore文件中添加了过滤规则来忽略bin和obj等文件夹,但这些文件夹仍然出现在提交中,可能是因为这些文件夹在添加.gitignore规则之前已经被提交到Git仓库中了。要解决这个问题,您需要从Git的索引中移除这些文件夹,并确保…...
h5使用video播放时关掉vant弹窗视频声音还在后台播放
现象: 1、点击遮罩弹窗关闭,弹窗的视频已经用v-if销毁,但是后台会自己从头开始播放视频声音。但是此时已经没有视频dom 2、定时器在打开弹窗后3秒自动关闭弹窗,则正常没有问题。 原来的代码: //页面 <a click&quo…...
Widows搭建sqli-labs
使用ms17_010渗透win7 ms17_010针对windows445端口(共享文件), 现有一台win7虚拟机IP 192.168.80.129, 开放445端口, 使用msf渗透该虚拟机 auxiliary 使用auxiliary判断目标主机是否适用smb17_010漏洞 这里发现80网段, 有一台主机适用 exploit 使用search ms17_010 type:expl…...
为AI聊天工具添加一个知识系统 之46 蒙板程序设计(第一版):Facet六边形【意识形态:操纵】
本文要点 要点 (原先标题冒号后只有 “Facet”后改为“Face六边形【意识形态】” ,是 事后想到的,本文并未明确提出。备忘在这里作为后续的“后期制作”的备忘) 前面讨论的(“之41 纯粹的思维”)中 说到,“意识”三…...
ASP.NET Core WebApi接口IP限流实践技术指南
在当今的Web开发中,接口的安全性和稳定性至关重要。面对恶意请求或频繁访问,我们需要采取有效的措施来保护我们的WebApi接口。IP限流是一种常见的技术手段,通过对来自同一IP地址的请求进行频率控制,可以有效地防止恶意攻击和过度消…...
文件移动工具 (File Mover)
这是一个简单但功能强大的Python脚本,用于递归遍历目录并将指定格式的文件移动到目标目录。默认支持移动PDF文件,但也可以通过参数指定其他文件格式。 功能特点 递归遍历源目录及其所有子目录支持移动任意指定格式的文件自动处理目标目录中的文件重名情…...
PTA L1-039 古风排版
中国的古人写文字,是从右向左竖向排版的。本题就请你编写程序,把一段文字按古风排版。 输入格式: 输入在第一行给出一个正整数N(<100),是每一列的字符数。第二行给出一个长度不超过1000的非空字符串&a…...
Docker 镜像加速的配置
解决拉取镜像报错:Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while 在使用 Docker 过程中,拉取镜像的速度常常会受到网络状况的影响,尤其是在国内网络环境下,…...
简历_使用优化的Redis自增ID策略生成分布式环境下全局唯一ID,用于用户上传数据的命名以及多种ID的生成
系列博客目录 文章目录 系列博客目录WhyRedis自增ID策略 Why 我们需要设置全局唯一ID。原因:当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题。 问题:id的规律性太明显、…...
PHP的HMAC_SHA1和HMAC_MD5算法方法
很多做对接的小伙伴们都会遇到签名加密的问题,常用的就是hmac_sha1加密和hmac_md5加密,最开始用的是sha1加密,后来用到了md5加密,我以为直接把sha1改为md5就好了,结果试来试去跟文档提示的示例结果都对不上,…...
二进制/源码编译安装mysql 8.0
二进制方式: 1.下载或上传安装包至设备: 2.创建组与用户: [rootopenEuler-1 ~]# groupadd mysql [rootopenEuler-1 ~]# useradd -r -g mysql -s /bin/false mysql 3.解压安装包: tar xf mysql-8.0.36-linux-glibc2.12-x86_64.ta…...
2025-1-15-十大经典排序算法 C++与python
文章目录 十大经典排序算法比较排序1. 冒泡排序2. 选择排序3. 插入排序4. 希尔排序5. 归并排序6. 快速排序7. 堆排序 非比较排序8. 计数排序9. 桶排序10. 基数排序 十大经典排序算法 十大经典排序算法可以分为比较排序和非比较排序: 前者包括冒泡排序、选择排序、插入排序、希…...
头盔识别技术
本项目参考b站视频https://www.bilibili.com/video/BV1EhkiY2Epg/?spm_id_from333.999.0.0&vd_source6c722ac1eba24d4cbadc587e4d1892a7 1.下载miniconda 使用 Miniconda 来管理 Python 环境(如 yolov8),就可以通过 conda create -n y…...
DeepSeek-v3在训练和推理方面的优化
1. 基础架构:MLA,大幅减少了KV cache大小。(计算量能不能减少?) 2. 基础架构:MoE,同等参数量(模型的”能力“)下,训练、推理的计算量大幅减少。 3. MoE的load…...
将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch(3 纯python的经济方案)
前情: 将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch(1)-CSDN博客 将 AzureBlob 的日志通过 Azure Event Hubs 发给 Elasticsearch(2)-CSDN博客 python脚本实现 厉害的小伙伴最终使用python脚本免费…...
1️⃣Java中的集合体系学习汇总(List/Map/Set 详解)
目录 01. Java中的集合体系 02. 单列集合体系 1. Collection系列集合的遍历方式 (1)迭代器遍历(2)增强for遍历编辑(3)Lambda表达式遍历 03.List集合详解 04.Set集合详解 05.总结 Collection系列…...
闪豆多平台视频批量下载器
1. 视频链接获取与解析 首先,在哔哩哔哩网页中随意点击一个视频,比如你最近迷上了一个UP主的美食制作视频,想要下载下来慢慢学。点击视频后,复制视频页面的链接。复制完成后,不要急着关闭浏览器,因为接下来…...
深入解析:如何用Java爬取淘宝分类详情接口(cat_get)
一、前言 淘宝分类详情接口(cat_get)是淘宝开放平台提供的一个接口,允许开发者获取淘宝商品的分类详情,包括分类ID、分类名称、父分类等信息。这些数据对于电商分析、市场研究和商品分类管理等具有重要价值。本文将详细介绍如何使…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
