协议定制 + Json序列化反序列化

文章目录

  • 协议定制 + Json序列化反序列化
    • 1. 再谈 "协议"
      • 1.1 结构化数据
      • 1.2 序列化和反序列化
    • 2. 网络版计算器
      • 2.1 服务端
      • 2.2 协议定制
        • (1) 网络发送和读取的正确理解
        • (2) 协议定制的问题
      • 2.3 客户端
      • 2.4 代码
    • 3. Json实现序列化反序列化
      • 3.1 简单介绍
      • 3.2 使用

协议定制 + Json序列化反序列化

1. 再谈 “协议”

1.1 结构化数据

协议是一种 “约定”,socket api的接口, 在读写数据时,都是按 “字符串” 的方式来发送接收的。如果我们要传输一些"结构化的数据" 怎么办呢?

结构化数据:

比如我们在QQ聊天时,并不是单纯地只发送了消息本身,是把自己的头像、昵称、发送时间、消息本身一起发送给别人,这种一起发送的就是结构化数据。

1.2 序列化和反序列化

  • 序列化:就是将对象转化成字节序列的过程。便于在传递和保存对象时保证对象的完整性和可传递性同时对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
  • 反序列化:就是将字节序列转化成对象的过程。便于根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。

在这里插入图片描述

  • 在socket编程的基础上,我们发现在实际生活中网络通信并不是单单发送一条消息本身,它包含了很多其他类型的数据,所以我们引入了结构化数据的概念,将这些各种类型的数据都定义在一个结构体中形成结构化数据方便被上层设置与读取。

  • 发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转化回结构体;实现序列化和反序列化方便网络通信。

  • 那么发送方发送的结构化数据序列化成字符串,接收方收到后是怎么知道反序列化成结构化数据呢?这是因为两者间存在定址好的协议。所以协议的本质就是双方约定好的某种格式的数据,常见的就是用结构体或者类来表达。

2. 网络版计算器

我们需要实现一个服务器版的计算器,客户端把要计算的两个数和计算类型发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。

2.1 服务端

服务端创建步骤:

  1. 调用socket,创建套接字
  2. 调用bind,绑定端口
  3. 调用listen,将套接字状态设置为监听
  4. 调用accept,获取新连接
  5. 处理读取与写入的问题(重点)

2.2 协议定制

(1) 网络发送和读取的正确理解

在这里插入图片描述

客户端和服务器通信时,会调用read和write函数,它们是把数据直接发送到对端吗?不是

  • TCP协议有自己的发送缓冲区和接收缓冲区
  • 调用write本质:把用户所对应的数据拷贝到TCP的发送缓冲区
  • 调用read本质:把数据从接收缓冲区拷贝到用户层
  • 所以read和write的本质是拷贝函数
  • 把数据拷贝到TCP发送缓冲区后,剩下的数据怎么发,是由TCP决定的,所以TCP又叫做传输控制协议
  • 因为发送和接收是成对的,可以同时进行的,所以TCP协议是全双工的

综上:

  • TCP通信的本质是自己发送缓冲区的数据经过网络拷贝到对方的接收缓冲区中
  • 网络通信的本质也是拷贝

(2) 协议定制的问题

在定制协议之前先解决一个问题,之前在使用TCP协议时我们只是简单的读取,没有考虑TCP是面向字节流的,读取数据不完整的问题。这里同样存在相同的问题,如果一下子对方发送了很多报文,这些报文都堆积在TCP的接收缓冲区中,你怎么保证自己读到的是一个完整的报文呢?

我们采用这样的方式:

  • 对报文定长
  • 使用特殊符号(在报文与报文之间增加特殊符号)
  • 自描述方式(自己设计协议)

协议设计格式:

在这里插入图片描述

Protocol.hpp

#include<string>
#include<iostream>
#include<vector>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include"Util.hpp"
using namespace std;// 给网络版本计算器定制协议
namespace Protocol_ns
{#define SEP " "#define SEP_LEN strlen(SEP)   // 绝对不能写成sizeof#define HEADER_SEP "\r\n"#define HEADER_SEP_LEN strlen("\r\n")// "长度"\r\n" "_x op _y"\r\n// "10 + 20" => "7"r\n""10 + 20"\r\n => 报头 + 有效载荷// 请求/响应 = 报头\r\n有效载荷\r\n// 请求 = 报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n// "10 + 20" => "7"r\n""10 + 20"\r\nstring AddHeader(string&str){cout<<"AddHeader 之前:\n"<<str<<endl;string s=to_string(str.size());s+=HEADER_SEP;s+=str;s+=HEADER_SEP;cout<<"AddHeader 之后:\n"<<s<<endl;return s;}// "7"r\n""10 + 20"\r\n => "10 + 20" string RemoveHeader(const string&str,int len){cout<<"RemoveHeader 之前:\n"<<str<<endl;// 从后面开始截取string res=str.substr(str.size()-HEADER_SEP_LEN-len,len); cout<<"RemoveHeader 之后:\n"<<res<<endl;   return res;}int Readpackage(int sock,string&inbuffer,string*package){cout<<"ReadPackage inbuffer 之前:\n"<<inbuffer<<endl;// 边读取char buffer[1024];ssize_t s=recv(sock,&buffer,sizeof(buffer)-1,0);if(s<=0)return -1;buffer[s]=0;inbuffer+=buffer;cout<<"ReadPackage inbuffer 之中:\n"<<inbuffer<<endl;// 边分析,  "7"r\n""10 + 20"\r\nauto pos=inbuffer.find(HEADER_SEP);if(pos==string::npos)return 0;string lenStr=inbuffer.substr(0,pos);    // 获取头部字符串, 没有动inbufferint len=Util::toInt(lenStr);             // 得到有效载荷的长度 => "123" -> 123int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size();   // 得到整个报文长度if(inbuffer.size()<targetPackageLen)     // 不是一个完整的报文return 0;*package=inbuffer.substr(0,targetPackageLen);  // 提取到了报文有效载荷, 没有动inbufferinbuffer.erase(0,targetPackageLen);      // 从inbuffer中直接移除整个报文cout<<"ReadPackage inbuffer 之后:\n"<<inbuffer<<endl;return len;}// Request && Response都要提供序列化和反序列化功能// 1. 自己手写// 2. 用别人的 --- json, xml, protobufclass Request{public:Request(){}Request(int x,int y,char op):_x(x),_y(y),_op(op){}// 序列化: struct->stringbool Serialize(string* outStr)     {*outStr=""; string x_string=to_string(_x);string y_string=to_string(_y);// 手动序列化*outStr=x_string + SEP + _op + SEP + y_string;std::cout << "Request Serialize:\n"<< *outStr << std::endl;return true;}// 反序列化: string->structbool Deserialize(const string&inStr)   {// inStr:  10 + 20 => [0]=>10, [1]=>+, [2]=>20vector<string> result;Util::StringSplit(inStr,SEP,&result);if(result.size()!=3)return false;if(result[1].size()!=1)return false;_x=Util::toInt(result[0]);_y=Util::toInt(result[2]);_op=result[1][0];return true;}~Request(){}public:// _x op _y ==> 10 * 9 ? ==> 10 / 0 ?int _x;int _y;char _op;};class Response{public:Response(){}Response(int result,int code):_result(result),_code(code){}// 序列化: struct->stringbool Serialize(string* outStr)     {// _result _code*outStr=""; string res_string = to_string(_result);string code_string = to_string(_code);// 手动序列化*outStr=res_string + SEP + code_string;return true;}// 反序列化: string->structbool Deserialize(const string&inStr)   {// 10 0vector<string> result;Util::StringSplit(inStr,SEP,&result);if(result.size()!=2)return false;_result=Util::toInt(result[0]);_code=Util::toInt(result[1]);return true;}~Response(){}public:int _result;int _code;   // 0 success; 1,2,3,4代表不同错误码};}

Util.hpp

#pragma once#include<iostream>
#include<string>
#include<vector>
using namespace std;class Util
{
public:// 输入: const &// 输出: *// 输入输出: *static bool StringSplit(const string &str, const string &sep, vector<string> *result){// 10 + 20size_t start = 0;while (start < str.size()){auto pos = str.find(sep, start);if (pos == string::npos)break;result->push_back(str.substr(start, pos - start));// 更新位置start = pos + sep.size();}// 处理最后的字符串if(start<str.size())result->push_back(str.substr(start));return true;}static int toInt(const string&s)  // 字符串转整数{return atoi(s.c_str());}
};

2.3 客户端

客户端创建步骤:

  1. 调用socket,创建套接字
  2. 客户端不用自己bind端口
  3. 调用connect,连接服务器
  4. 处理读取与写入的问题

2.4 代码

完整的代码:lesson36 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

运行结果:

在这里插入图片描述

3. Json实现序列化反序列化

3.1 简单介绍

上面是自己定制协议实现序列化和反序列化,下面我们使用一些现成的方案来实现序列化和反序列化。C++常用的:protobuf 和 json,这里使用简单的 json。

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。Json数据由键值对组成,大括号表示对象,方括号表示数组。

在这里插入图片描述

3.2 使用

  • 安装json库
yum install -y jsoncpp-devel
  • 使用json包含的头文件:
#include <jsoncpp/json/json.h>

注意makefile文件要包含Json库的名称

在这里插入图片描述

我们在使用的时候直接创建Json对象来进行序列化和反序列化

Protocol.hpp

#include<string>
#include<iostream>
#include<vector>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include"Util.hpp"
#include<jsoncpp/json/json.h>
using namespace std;// #define MYSELF 1// 给网络版本计算器定制协议namespace Protocol_ns
{#define SEP " "#define SEP_LEN strlen(SEP)   // 绝对不能写成sizeof#define HEADER_SEP "\r\n"#define HEADER_SEP_LEN strlen("\r\n")// "长度"\r\n" "_x op _y"\r\n// "10 + 20" => "7"r\n""10 + 20"\r\n => 报头 + 有效载荷// 请求/响应 = 报头\r\n有效载荷\r\n// 请求 = 报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n报头\r\n有效载荷\r\n// 未来: "长度"\r\n"协议号\r\n""_x op _y"\r\n// "10 + 20" => "7"r\n""10 + 20"\r\nstring AddHeader(string&str){cout<<"AddHeader 之前:\n"<<str<<endl;string s=to_string(str.size());s+=HEADER_SEP;s+=str;s+=HEADER_SEP;cout<<"AddHeader 之后:\n"<<s<<endl;return s;}// "7"r\n""10 + 20"\r\n => "10 + 20" string RemoveHeader(const string&str,int len){cout<<"RemoveHeader 之前:\n"<<str<<endl;// 从后面开始截取string res=str.substr(str.size()-HEADER_SEP_LEN-len,len); cout<<"RemoveHeader 之后:\n"<<res<<endl;   return res;}int Readpackage(int sock,string&inbuffer,string*package){cout<<"ReadPackage inbuffer 之前:\n"<<inbuffer<<endl;// 边读取char buffer[1024];ssize_t s=recv(sock,&buffer,sizeof(buffer)-1,0);if(s<=0)return -1;buffer[s]=0;inbuffer+=buffer;cout<<"ReadPackage inbuffer 之中:\n"<<inbuffer<<endl;// 边分析,  "7"r\n""10 + 20"\r\nauto pos=inbuffer.find(HEADER_SEP);if(pos==string::npos)return 0;string lenStr=inbuffer.substr(0,pos);    // 获取头部字符串, 没有动inbufferint len=Util::toInt(lenStr);             // 得到有效载荷的长度 => "123" -> 123int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size();   // 得到整个报文长度if(inbuffer.size()<targetPackageLen)     // 不是一个完整的报文return 0;*package=inbuffer.substr(0,targetPackageLen);  // 提取到了报文有效载荷, 没有动inbufferinbuffer.erase(0,targetPackageLen);      // 从inbuffer中直接移除整个报文cout<<"ReadPackage inbuffer 之后:\n"<<inbuffer<<endl;return len;}// Request && Response都要提供序列化和反序列化功能// 1. 自己手写// 2. 用别人的class Request{public:Request(){}Request(int x,int y,char op):_x(x),_y(y),_op(op){}// 序列化: struct->stringbool Serialize(string* outStr)     {*outStr=""; 
#ifdef  MYSELFstring x_string=to_string(_x);string y_string=to_string(_y);// 手动序列化*outStr=x_string + SEP + _op + SEP + y_string;std::cout << "Request Serialize:\n"<< *outStr << std::endl;
#elseJson::Value root;   // Value: 一种万能对象, 接受任意的kv类型root["x"]=_x;root["y"]=_y;root["op"]=_op;// Json::FastWriter writer;  // writer: 是用来进行序列化的 struct -> stringJson::StyledWriter writer;*outStr=writer.write(root);
#endifreturn true;}// 反序列化: string->structbool Deserialize(const string&inStr)   {
#ifdef  MYSELF// inStr:  10 + 20 => [0]=>10, [1]=>+, [2]=>20vector<string> result;Util::StringSplit(inStr,SEP,&result);if(result.size()!=3)return false;if(result[1].size()!=1)return false;_x=Util::toInt(result[0]);_y=Util::toInt(result[2]);_op=result[1][0];#elseJson::Value root;   Json::Reader reader;  // Reader: 是用来反序列化的reader.parse(inStr,root);_x=root["x"].asUInt();_y=root["y"].asUInt();_op=root["op"].asUInt();#endifPrint();return true;}void Print(){std::cout << "_x: " << _x << std::endl;std::cout << "_y: " << _y << std::endl;std::cout << "_z: " << _op << std::endl;}~Request(){}public:// _x op _y ==> 10 * 9 ? ==> 10 / 0 ?int _x;int _y;char _op;};class Response{public:Response(){}Response(int result,int code):_result(result),_code(code){}// 序列化: struct->stringbool Serialize(string* outStr)     {// _result _code*outStr=""; 
#ifdef  MYSELFstring res_string = to_string(_result);string code_string = to_string(_code);// 手动序列化*outStr=res_string + SEP + code_string;#elseJson::Value root;   root["result"]=_result;root["code"]=_code;// Json::FastWriter writer;Json::StyledWriter writer;*outStr=writer.write(root);
#endifreturn true;}// 反序列化: string->structbool Deserialize(const string&inStr)   {
#ifdef  MYSELF// 10 0vector<string> result;Util::StringSplit(inStr,SEP,&result);if(result.size()!=2)return false;_result=Util::toInt(result[0]);_code=Util::toInt(result[1]);#elseJson::Value root;Json::Reader reader; reader.parse(inStr, root);_result = root["result"].asUInt();_code = root["code"].asUInt();
#endifPrint();return true;}void Print(){std::cout << "_result: " << _result << std::endl;std::cout << "_code: " << _code << std::endl;}~Response(){}public:int _result;int _code;   // 0 success; 1,2,3,4代表不同错误码};
}

完整代码:lesson36/NetCal_v2 · 遇健/Linux - 码云 - 开源中国 (gitee.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/74441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【送书活动】揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

python调用GPT实现:智能用例生成工具

工具作用&#xff1a; 根据输入的功能点&#xff0c;生成通用测试点 实现步骤 工具实现主要分2个步骤&#xff1a; 1.https请求调用Gpt,将返回响应结果保存为.md文件 2.用python实现 将 .md文件转换成.xmind文件 3.写个简单的前端页面&#xff0c;调用上述步骤接口 详细代…

操作系统强化认识之Shell编程学习与总结

目录 1.Shell的概述 2.Shell脚本入门 3.变量 3.1.系统预定义变量 3.2.自定义变量 3.3.特殊变量 4.运算符 5.条件判断 6.流程控制 6.1.if判断 6.2.case语句 6.3.for循环 6.4.while循环 7.read读取控制台输入 8.函数 8.1.系统函数 8.2.自定义函数 9.正则表示式入…

【C++入门到精通】C++入门 ——搜索二叉树(二叉树进阶)

阅读导航 前言一、搜索二叉树简介1. 概念2. 基本操作⭕搜索操作&#x1f36a;搜索操作基本代码&#xff08;非递归&#xff09; ⭕插入操作&#x1f36a;插入操作基本代码&#xff08;非递归&#xff09; ⭕删除操作&#x1f36a;删除操作基本代码&#xff08;非递归&#xff0…

MySQL无法查看系统默认字符集以及校验规则

show variables like character_set_database; show variables like collation_database;这个错误信息表示MySQL在尝试访问performance_schema.session_variables表时&#xff0c;发现该表不存在。这个问题可能是由于MySQL的版本升级导致的。解决这个问题的一种方法是运行mysql…

论文浅尝 | 训练语言模型遵循人类反馈的指令

笔记整理&#xff1a;吴亦珂&#xff0c;东南大学硕士&#xff0c;研究方向为大语言模型、知识图谱 链接&#xff1a;https://arxiv.org/abs/2203.02155 1. 动机 大型语言模型&#xff08;large language model, LLM&#xff09;可以根据提示完成各种自然语言处理任务。然而&am…

Java JUC 并发编程(笔记)

文章目录 再谈多线程并发与并行顺序执行并发执行并行执行 再谈锁机制重量级锁轻量级锁偏向锁锁消除和锁粗化 JMM内存模型Java内存模型重排序volatile关键字happens-before原则 多线程编程核心锁框架Lock和Condition接口可重入锁公平锁与非公平锁 读写锁锁降级和锁升级 队列同步…

[构建 Vue 组件库] 小尾巴 UI 组件库 —— 横向商品卡片(仿淘宝)

文章归档于&#xff1a;https://www.yuque.com/u27599042/row3c6 组件库地址 npm&#xff1a;https://www.npmjs.com/package/xwb-ui?activeTabreadmegitee&#xff1a;https://gitee.com/tongchaowei/xwb-ui 下载 npm i xwb-ui配置 按需导入 import {组件名 } from xwb-…

【Unity】 2D 游戏 库存模块实现

库存模块主要参考了 youtube 上的视频 BMo 的 Flexible INVENTORY SYSTEM in Unity with Events and Scriptable Objects 和 Simple Inventory UI in Unity With Grid Layouts 这两个视频是一个系列 还是一个视频也是 BMo的 How To INTERACT with Game Objects using UNITY E…

Nginx详解 第五部分:Ngnix反向代理(负载均衡 动静分离 缓存 透传 )

Part 5 一、正向代理与反向代理1.1 正向代理简介1.2 反向代理简介 二、配置反向代理2.1 反向代理配置参数2.1.1 proxy_pass2.1.2 其余参数 2.2 配置实例:反向代理单台web服务器2.3 代理转发 三、反向代理实现动静分离四、缓存功能五、反向代理客户端的IP透传5.1 原理概述5.2 一…

谁在为网络安全制造标尺?

“我们想帮助企业往后退一步&#xff0c;去全局的看一下自己的安全能力建设水平如何&#xff0c;以及在当下的阶段最应该做的安全建设是什么&#xff1f; ” 度量&#xff0c;对应的是更清晰的认知。而对企业安全而言&#xff0c;这种认知&#xff0c;也更在成为一把新的标尺…

Redis带你深入学习数据类型set

目录 1、set 2、set相关命令 2.1、添加元素 sadd 2.2、获取元素 smembers 2.3、判断元素是否存在 sismember 2.4、获取set中元素数量 scard 2.5、删除元素spop、srem 2.6、移动元素smove 2.7、集合中相关命令&#xff1a;sinter、sinterstore、sunion、sunionstore、s…

CSS:屏幕正中间有个元素A,元素A中有文字A,随着屏幕宽度的增加

始终需要满足以下条件&#xff1a; A元素垂直居中于屏幕***&#xff1b;A元素距离屏幕左右边距各10px&#xff1b;A元素里面的文字”A”的font-size:20px&#xff1b;水平垂直居中;A元素的高度始终是A元素宽度的50%; (如果搞不定可以实现为A元素的高度固定为200px;)请用 html及…

【Unity基础】3.脚本控制物体运动天空盒

【Unity基础】3.脚本控制物体运动&天空盒 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;搭建开发环境 &#xff08;1&#xff09;下载visual studio 在我们下载unity编译器的时候&…

Microsoft Edge网页视频播放绿屏解决方法(B站)

一&#xff1a;问题&#xff0c;在B站观看视频时有绿色条纹 二&#xff1a;查找原因&#xff0c;未知 三&#xff1a;解决方法 三.1网页设置关闭硬件加速 三.2 点击视频播放下的 “小齿轮”&#xff0c;然后点击“更多播放设置” 把播放策略 “默认” 改为“AVC” 四&…

Tomcat配置域名和端口

Tomcat配置域名和端口 1.进入tomcat文件夹2. cd 到你的tomcat下3. 修改server.xml文件中监听端口4. 重启tomcat 1.进入tomcat文件夹 2. cd 到你的tomcat下 3. 修改server.xml文件中监听端口 继续修改server.xml中Host 4. 重启tomcat 进入bin ./shutdown.sh ./startup.sh …

etcd分布式存储

etcd分布式存储 etcd简介etcd下载安装etcd常用命令etcd配置参数etcd集群golang操作etcd

rrweb入门

rrweb 背景 rrweb 是 record and replay the web&#xff0c;是当下很流行的一个录制屏幕的开源库。与我们传统认知的录屏方式&#xff08;如 WebRTC&#xff09;不同的是&#xff0c;rrweb 录制的不是真正的视频流&#xff0c;而是一个记录页面 DOM 变化的 JSON 数组&#x…

【鲁棒电力系统状态估计】基于投影统计的电力系统状态估计的鲁棒GM估计器(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SpringCloud Alibaba 入门到精通 - Nacos

SpringCloud Alibaba 常用组件 一、基础结构搭建1.父工程创建2.子工程创建 二、Nacos&#xff1a;注册中心1.服务端搭建2.注册中心-客户端搭建3.注册中心-管理页面4.注册中心-常用配置5.注册中心-核心功能总结 三、Nacos注册中心集成Load Balancer 、OpenFeign1.Nacos客户端集成…