【网络】序列化和反序列化

news2024/11/21 2:24:53

🥁作者华丞臧.
📕​​​​专栏:【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 一、应用层
  • 二、网络版本计算器
    • 2.1 协议定制
    • 2.2 协议实现
      • encode
      • decode
      • 定制请求 -- Requset
      • 定制响应 -- Response
      • 输入参数读取
      • 测试
    • 2.3 使用第三方库
      • 测试


一、应用层

  • 程序员写的一个个解决实际问题满足日常需求的网络程序都是在应用层的。
  • 协议是一种“约定”,socket api的接口在读写数据时,都是按“字符串”的方式来发送接收的。
  • 如果我们要传输一些“结构化数据”,能直接传输吗?肯定是不能直接传输的,结构化数据存在一些问题,比如C语言中结构体需要结构体对齐,C++中类同样需要对齐,会浪费网络资源,并且不同操作系统的大小端在不同编译器下可能不同。

所以对于需要传输结构化数据,我们可以进行约定:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照约定的规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转回结构体。
  • 序列化:将结构体数据转换成字符串的过程。
  • 反序列化:将字符串数据转换回结构化数据的过程。

二、网络版本计算器

在进行结构化数据的网络通信时,需要将数据序列化成字符串再发送给对方,但是对方并不知道传输数据的长度,也不知道如何从字符串的数据中读取结构化的数据;因此在进行网络通信之间,通信的双发需要进行约定,比如:约定如何确定序列化数据的长度以及用什么格式来反序列化字符串。
在这里我们先手写一个定制协议,约定如下:

  • 计算器格式通常是:1+2、 2*3、9/3;
  • 序列化字符串首部几个字节表示长度;
  • 有效载荷通过空格隔开;
  • 表示长度的报头和有效载荷通过\r\n隔开;
  • 报文之间也是用\r\n隔开

2.1 协议定制

在这里插入图片描述
客户端和服务端代码请看👉序列化和反序列化

#pragma once
#include <iostream>
#include <string>
#include <assert.h>

#include "Log.hpp"

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define OPS "+-*/%"

// encode,对整个序列化之后的字符串进行添加长度
//"strlen\r\nXXXXXX\r\n"
std::string encode(std::string &in, uint32_t len)
{}

// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n    9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{}


// 定制请求 
class Request
{
public:
    Request()
    {}
    
    ~Request()
    {}
    
    int& getX()
    { return x_;}
    
    int& getY()
    { return y_;}
    
    char& getOp()
    { return op_;}
    
    // 序列化
    void serialization(std::string *out)
    {}

    // 反序列化
    bool deserialization(std::string &in)
    {}
    
    void debug()
    {}
private:
    // 需要计算的数据
    int x_; 
    int y_;
    // 需要计算的种类,"+ - * / %"
    char op_;
};

// 定制响应
class Response
{
public:
    Response()
    :result_(0)
    ,exitCode_(0)
    {}
    ~Response()
    {}
    // 序列化
    void serialization(std::string *out)
    {}
    // 反序列化
    bool deserialization(std::string &in)
    {}
public:
    // 退出状态,0标识运算结果合法,非0标识运算结果是非法的
    int exitCode_; 
    // 运算结果
    int result_;
};

bool makeReuquest(const std::string &str, Request *req)
{}

2.2 协议实现

encode

按照约定需要在序列化之后的字符串首部加上有效载荷的长度并用\r\n隔开,并且结尾也需要加上\r\n
在这里插入图片描述

// encode,对整个序列化之后的字符串进行添加长度
//"strlen\r\nXXXXXX\r\n"
std::string encode(std::string &in, uint32_t len)
{
    // "exitCode_ result_"
    // "len\r\n" "exitCode_ result_\r\n"
    std::string encodeStr = std::to_string(len);
    encodeStr += CRLF;
    encodeStr += in;
    encodeStr += CRLF;

    //std::cout << "debug->encode-> " << encodeStr << std::endl;
    return encodeStr;
}

decode

  • 第一步要先提取有效载荷的长度,再根据长度检查字符串中是否含有一个完整的有效载荷;
  • 如果具有完整的有效载荷,可以通过长度来提取有效载荷。
  • 注意如果需要进行多次提取,需要将读取出的字符串删除。
// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n    9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{
    assert(len);

    // 1. 确认是否是一个包含len的有效字符串
    *len = 0;
    size_t pos = in.find(CRLF);
    if(pos == std::string::npos)
        return "";

    // 2. 提取长度
    std::string strLen = in.substr(0, pos);
    int intLen = atoi(strLen.c_str());

    // 3. 确认有效载荷也是符合要求的
    int surplus = in.size() - 2 * CRLF_LEN - pos;
    if(surplus < intLen)
    {
        return "";
    }

    // 4. 确认有完整的报文结构
    std::string package = in.substr(pos + CRLF_LEN, intLen);
    *len = intLen;

    // 5. 将当前报文完整的从in中全部移除掉
    int remLen = strLen.size() + 2 *CRLF_LEN + package.size();
    in.erase(0, remLen);

    // 6. 正常返回
    return package;
}

定制请求 – Requset

定制请求,就是给请求方一个存放结构化数据的空间,请求方可以通过定制好的协议进行序列化得到字符串,然后就可以与服务端进行网络通信了;Request中的反序列化通常是给服务端提取请求方的结构化数据,所以服务端可以根据结构化数据向请求方进行响应。

序列化:

  • 序列化出的数据属于有效载荷,因此按照协议需要使用空格隔开;

反序列化:

  • 按照协议规定,结构化的数据在字符串中是通过空格隔开的,所以我们可以根据字符串中的两个空格将数据结构化提取出来。
#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)

// 定制请求 
class Request
{
public:
    Request()
    {}
    
    ~Request()
    {}

    int& getX()
    { return x_;}

    int& getY()
    { return y_;}
    
    char& getOp()
    { return op_;}

    // 序列化
    void serialization(std::string *out)
    {
        // 100 + 200 
        // "100 + 200"
        std::string dateOne = std::to_string(x_);
        std::string dateTwo = std::to_string(y_);
        std::string dateOp = std::to_string(op_);

        *out = dateOne;
        *out += SPACE;
        *out += op_;
        *out += SPACE;
        *out += dateTwo;
        //std::cout << *out << std::endl;
    }

    // 反序列化
    bool deserialization(std::string &in)
    {
        //std::cout << 0 << std::endl;
        // 100 + 200 
        size_t spaceOne = in.find(SPACE);
        if(spaceOne == std::string::npos) return false;
        //std::cout << 1 << std::endl;

        size_t spaceTwo = in.rfind(SPACE);
        if(spaceTwo == std::string::npos) return false;
        //std::cout << 2 << std::endl;


        std::string dateOne = in.substr(0, spaceOne);
        std::string dateTwo = in.substr(spaceTwo + SPACE_LEN);
        std::string dateOp = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));
        if(dateOp.size() != 1) 
        {
            printf("%d:%c\n", dateOp.size(), dateOp.c_str());
            return false;
        }
        //std::cout << 3 << std::endl;

        // 转成内部成员
        x_ = atoi(dateOne.c_str());
        y_ = atoi(dateTwo.c_str());
        op_ = atoi(dateOp.c_str());

        return true;
    }

    void debug()
    {
        std::cout << "x_" << x_ << std::endl;
        std::cout << "y_" << y_ << std::endl;
        std::cout << "op_" << op_ << std::endl;
    }
private:
    // 需要计算的数据
    int x_; 
    int y_;
    // 需要计算的种类,"+ - * / %"
    char op_;
};

定制响应 – Response

响应是服务端在对客户端的请求提供服务之后给客户端返回的结果,所以Response需要能够存放服务结果以及发生错误时的状态码;而结果和错误码是数据化结构,所以需要序列化之后在传输给客户端,客户端在拿到响应后需要进行反序列化拿到结果和状态码。序列胡和反序列化过程与定制请求类似。

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
// 定制响应
class Response
{
public:
    Response()
    :result_(0)
    ,exitCode_(0)
    {}
    
    ~Response()
    {}

    // 序列化
    void serialization(std::string *out)
    {
        // "exitCode_ result_"

        std::string ec = std::to_string(exitCode_);
        std::string res = std::to_string(result_);

        *out = ec;
        *out += SPACE;
        *out += res;
    }

    // 反序列化
    bool deserialization(std::string &in)
    {
        // "nextiCode_ result_"
        size_t pos = in.find(SPACE);
        if(pos == std::string::npos) return false;

        std::string ec = in.substr(0, pos);
        std::string res = in.substr(pos + SPACE_LEN);

        exitCode_ = atoi(ec.c_str());
        result_ = atoi(res.c_str());

        return true;
    }

public:
    // 退出状态,0标识运算结果合法,非0标识运算结果是非法的
    int exitCode_; 
    // 运算结果
    int result_;
};

输入参数读取

我们在客户端输入时是按照字符串的格式输入的,所以为了能够使用定制协议,需要进行反序列化即将输入的字符串数据按照协议中的结构化数据提取出来。

#define OPS "+-*/%"

bool makeReuquest(const std::string &str, Request *req)
{
    // 123+1  1*1 1/1
    char strtmp[1024];
    snprintf(strtmp, sizeof strtmp, "%s", str.c_str());
    char *left = strtok(strtmp, OPS);
    if (!left)
        return false;
    char *right = strtok(nullptr, OPS);
    if (!right)
        return false;
    char mid = str[strlen(left)];

    req->getX() = atoi(left);
    req->getY() = atoi(right);
    req->getOp() = mid;
    return true;
}

测试

  • IP:127.0.0.1 --》测试
    在这里插入图片描述
  • IP:119.91.213.117 --》测试
    在这里插入图片描述

2.3 使用第三方库

大佬也写了第三方库来支持序列化和反序列化,在这里我使用的jsoncpp,安装方式如下:

sudo yum install -y jsoncpp-devel

第三方库的使用较之我们自己实现无疑方便了很多,不用自己造轮子。

#pragma once
#include <iostream>
#include <string>
#include <assert.h>
#include <jsoncpp/json/json.h>

#include "Log.hpp"

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define OPS "+-*/%"

// 定制请求 
class Request
{
public:
    Request()
    {}
    
    ~Request()
    {}

    int& getX()
    { return x_;}

    int& getY()
    { return y_;}
    
    char& getOp()
    { return op_;}

    // 序列化
    void serialization(std::string *out)
    {
         //json
        // 1. Value对象,万能对象
        // 2. json是基于KV
        // 3. json有两套操作方法
        // 4. 序列化的时候,会将所有的数据内容,转换成为字符串
        Json::Value root;  //,万能对象,任意类型
        root["x"] = x_;
        root["y"] = y_;
        root["op"] = op_;
			
        Json::FastWriter fw;  
        //Json::StyledWriter fw;
        *out = fw.write(root); //序列化
    }

    // 反序列化
    bool deserialization(std::string &in)
    {
    Json::Value root;
    Json::Reader rd;
    rd.parse(in, root);

    x_ = root["x"].asInt();
    y_ = root["y"].asInt();
    op_ = root["op"].asInt();

    return true;
    }

    void debug()
    {
        std::cout << "x_" << x_ << std::endl;
        std::cout << "y_" << y_ << std::endl;
        std::cout << "op_" << op_ << std::endl;
    }
private:
    // 需要计算的数据
    int x_; 
    int y_;
    // 需要计算的种类,"+ - * / %"
    char op_;
};

// 定制响应
class Response
{
public:
    Response()
    :result_(0)
    ,exitCode_(0)
    {}
    
    ~Response()
    {}

    // 序列化
    void serialization(std::string *out)
    {
    Json::Value root;
    root["exitCode"] = exitCode_;
    root["result"] = result_;

    Json::FastWriter fw;
    *out = fw.write(root); //序列化
    }

    // 反序列化
    bool deserialization(std::string &in)
    {
    Json::Value root;
    Json::Reader rd;
    rd.parse(in, root);

    exitCode_ = root["exitCode"].asInt();
    result_ = root["result"].asInt();

    return true;
    }

public:
    // 退出状态,0标识运算结果合法,非0标识运算结果是非法的
    int exitCode_; 
    // 运算结果
    int result_;
};

测试

  • 使用Json::FastWriter fw;
    在这里插入图片描述
  • 使用Json::StyledWriter fw;
    在这里插入图片描述

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

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

相关文章

WSL2使用Nvidia-Docker实现深度学习环境自由部署

1. Win11 显卡驱动的安装 注意&#xff1a;WSL2中是不需要且不能安装任何显卡驱动的&#xff0c;它的显卡驱动完全依赖于 Win11 中的显卡驱动&#xff0c;因此我们只需要安装你显卡对应的 Win11 版本显卡驱动版本&#xff08;必须是 Win11 版本的驱动&#xff09;&#xff0c;…

Three.js高级应用--利用Three.js+WebGL实现fbx和obj格式模型的自定义加载

通过对webgl和three.js的不断学习与实践&#xff0c;在三维应用场景建设过程中&#xff0c;利用Three.js与webgl配合可以实现大部分三维场景的应用需求&#xff0c;这一篇主要讲述如何利用Three.js加载已有的模型&#xff0c;支持的三维模型格式有.fbx和.obj&#xff0c;同时.o…

English Learning - L2 第1次小组纠音 [ɑː] [ɔː] [uː] 2023.2.25 周六

English Learning - L2 第1次小组纠音 [ɑː] [ɔː] [uː] 2023.2.25 周六共性问题分析大后元音 [ɑː]大后元音 [ɔː]后元音 [uː]我的发音问题后元音 [uː]大后元音 [ɑː] 和 [ɔː]纠音过程第一次第二次第三次共性问题分析 大后元音 [ɑː] 嘴唇过于松散&#xff0c;没…

SpringMVC文件上传、下载、国际化配置

Java知识点总结&#xff1a;想看的可以从这里进入 目录3.6、文件上传、下载3.6.1、文件上传3.6.2、文件下载3.7、国际化配置3.6、文件上传、下载 3.6.1、文件上传 form 表单想要具有文件上传功能&#xff0c;其必须满足以下 3 个条件。 form 表单的 method 属性必须设置为 p…

Spring基础知识(Spring注解开发大全)

原本xml文件写法 文件头 文件信息 配置Bean 初步修改的xml文件写法 文件头 文件信息 <context:component-scan base-package"要扫描的包"/>注解开发Bean 第一步&#xff1a;写config文件 Configuration//代表xml文件的文件头 ComponentScan(“要扫描的包”…

大型JAVA版云HIS医院管理系统源码 Saas应用+前后端分离+B/S架构

SaaS运维平台多集团多医院入驻强大的电子病历完整文档 有源码&#xff0c;有演示&#xff01; 云HIS系统技术栈&#xff1a; 1、前端框架&#xff1a;AngularNginx 2、后台框架&#xff1a;JavaSpring&#xff0c;SpringBoot&#xff0c;SpringMVC&#xff0c;SpringSecurity&…

【2022.1.3】手脱压缩壳练习(含练习exe)

【2022.1.3】手脱压缩壳练习&#xff08;含练习exe&#xff09; 文章目录【2022.1.3】手脱压缩壳练习&#xff08;含练习exe&#xff09;0、简介1、单步跟踪法&#xff08;#&#xff09;方法介绍&#xff08;0&#xff09;练习exe下载&#xff08;1&#xff09;、查看源程序&am…

精确率与召回率,ROC曲线与PR曲线

精确率与召回率&#xff0c;ROC曲线与PR曲线 在机器学习的算法评估中&#xff0c;尤其是分类算法评估中&#xff0c;我们经常听到精确率(precision)与召回率(recall)&#xff0c;ROC曲线与PR曲线这些概念&#xff0c;那这些概念到底有什么用处呢&#xff1f; 首先&#xff0c…

Linux系统GPIO应用编程

目录应用层如何操控GPIOGPIO 应用编程之输出GPIO 应用编程之输入GPIO 应用编程之中断在开发板上测试GPIO 输出测试GPIO 输入测试GPIO 中断测试本章介绍应用层如何控制GPIO&#xff0c;譬如控制GPIO 输出高电平、或输出低电平。应用层如何操控GPIO 与LED 设备一样&#xff0c;G…

【办公类05-03】Python批量修改文件名前面的序号(已有的序号错了,需要改成正确的号码)

背景需求下载教程&#xff0c;手动输入编号&#xff0c;有一个编号错误&#xff0c;导致后面所有编号都错了。30实际是29&#xff0c;以此类推怎样才能快速修改编号数字&#xff1f;前期考虑到可能要改编号&#xff0c;所以在每个编号后面加“ ”&#xff08;空格&#xff09;&…

python版协同过滤算法图书管理系统

基于协同过滤算法的图书管理系统 一、简介&#xff08;v信&#xff1a;1257309054&#xff09; ​ 本系统基于推荐算法给用户实现精准推荐图书。 ​ 根据用户对物品或者信息的偏好&#xff0c;发现物品或者内容本身的相关性&#xff0c;或者是发现用户的相关性&#xff0c;然…

Typora上传文档图片链接失效的问题+PicGo布置图床在Github

文章目录typora图片链接失效原因PicGO开源图床布置先配置Github2.1先创建新仓库、用于存放图片2.2生成一个token&#xff0c;用picGo访问github3.下载picGo,并进行配置3.1 配置v4.1typora图片链接失效原因 因为你是保存在本地的&#xff0c;因此图片是不能访问&#xff0c;可以…

laravel 邮件发送

配置 Laravel 的邮件服务可以通过 config/mail.php 配置文件进行配置。 邮件中的每一项都在配置文件中有单独的配置项&#xff0c;甚至是独有的「传输方式」&#xff0c;允许你的应用使用不同的邮件服务发送邮件 mailers > [smtp > [transport > smtp,host > env(M…

【超级猜图案例上半部分的实现 Objective-C语言】

一、超级猜图这么一个案例: 1.实现之后的效果是这样的: 1)中间有一个图片,点一下,能放大,背景变半透明的黑色: 2)再点一下图片,或者点周围黑色的阴影,图片回归原状, 3)右边有一个“大图”按钮,点一下,实现跟点图片一样的效果, 4)左边有一个“提示”按钮,点…

【Java学习笔记】4.Java 对象和类

前言 本章介绍Java的对象和类。 Java 对象和类 Java作为一种面向对象语言。支持以下基本概念&#xff1a; 多态继承封装抽象类对象实例方法重载 本节我们重点研究对象和类的概念。 对象&#xff1a;对象是类的一个实例&#xff08;对象不是找个女朋友&#xff09;&#x…

为什么人们宁可用Lombok,也不把成员设为public?

目录专栏导读一、从零了解JavaBean1、基本概念2、JavaBean的特征3、JavaBean的优点二、定义最简单的JavaBean三、思考一个问题&#xff0c;为何属性是private&#xff0c;然后用get/set方法&#xff1f;四、下面系统的分析以下&#xff0c;why?五、不和谐的声音&#xff0c;禁…

MySQL实战解析底层---行锁功过:怎么减少行锁对性能的影响

目录 前言 从两阶段锁说起 死锁和死锁检测 前言 MySQL 的行锁是在引擎层由各个引擎自己实现的但并不是所有的引擎都支持行锁&#xff0c;比如MyISAM 引擎就不支持行锁不支持行锁意味着并发控制只能使用表锁&#xff0c;对于这种引擎的表&#xff0c;同一张表上任何时刻只能有…

[深入理解SSD系列综述 1.5] SSD固态硬盘参数图文解析_选购固态硬盘就像买衣服?

版权声明&#xff1a;付费作品&#xff0c;未经许可&#xff0c;不可转载前言SSD &#xff08;Solid State Drive&#xff09;&#xff0c;即固态硬盘&#xff0c;通常是一种以半导体闪存&#xff08;NAND Flash&#xff09;作为介质的存储设备。SSD 以半导体作为介质存储数据&…

Python进阶-----面对对象4.0(面对对象三大特征之--继承)

目录 前言&#xff1a; Python的继承简介 1.什么是继承 2.继承的好处 3.object类 继承的相关用法 1.继承的定义与法则 2.对继承的重写 3.&#xff08;单继承&#xff09;多层继承 4.多继承 5.多继承重写时调用父类方法 前言&#xff1a; 在讲之前&#xff0c;我想说说中…

servlet注解开发

文章目录servlet注解开发内容回顾响应对象 HttpServletResponse重定向与请求转发ServletConfig简介案例ServletContext简介案例Servlet 注解开发简介注解使用案例WebServlet 注解详细参数综合的增删改查案例登录注册功能servlet注解开发 内容回顾 响应对象 HttpServletRespon…