序列化和反序列化~如何实现自定义协议?jsoncpp的使用

news2024/11/14 19:37:35

目录

 序列化反序列化的概念

 为什么要进行序列化和反序列化?

自定义协议实现业务

jsoncpp实现序列化和反序列化

序列化:

反序列化:

自定义协议+jsoncpp实现简易计算器服务整体逻辑 

Server.cc 

Client.cc

运行结果


 序列化反序列化的概念

        序列化就是把结构化的数据(比如一个结构体内部的数据)转换成字符串,方便在网络中进行传输,将应用和网络进行了解耦

        反序列化就是把序列化之后的字符串重新解析成结构化的数据,方便上层使用直接使用。 


 为什么要进行序列化和反序列化?

1.为了应用层网络通信的方便。我们发送结构化数据的时候,如果不进行序列化反序列化,是不便于网络传输的;将数据序列化为字符串以后,便于在网络中进行传输。(一个结构体要是硬要在网络中传输,那就要一个成员一个成员的以字符串的方式传输,非常麻烦)

2.为了方便上层使用结构体内部的成员,上层不需要对字符串进行解析,只需要直接使用下层反序列化得到的结构化数据即可。将应用和网络进行了解耦


自定义协议实现业务

什么是协议?

        双方约定好的结构化的数据,本质就是协议的表现。(相当于我们约定好,哪个字段是昵称,哪个是头像,哪个是消息等等)

目标:实现一个简易的计算器服务,能处理client发送过来的加减乘除等基本计算请求。

约定协议:client每次向server发送的请求结构包含两个操作数和一个操作符的字符串;而server每次给client的请求包含一个code表示状态码,一个result表示计算结果。

        因此,我们可以在自定义协议文件中定义如下两个结构体,这就是我们自己定义的协议,server和client都必须遵守。

// 定义的协议,client && server 都必须遵守,这就叫做自定义协议
//请求格式
typedef struct request
{
    int x;
    int y;
    char op; //"+-*/%"
} request_t;

// 响应格式
typedef struct response 
{
    int code;   // server运算完毕的计算状态: code(0:success), code(-1: div 0) ...
    int result; // 保存计算结果,先根据code判断是否计算成功,如果成功就查看result获取计算结果
}response_t;

jsoncpp实现序列化和反序列化

如果我们自己实现序列化和反序列化,会比较麻烦,因此可以借助一些工具,比如jsoncpp,帮助我们将结构化的数据序列化成字符串

        jsoncpp这个工具是c++里面常用的序列化和反序列化的工具。需要使用这个命令安装:sudo yum install -y jsoncpp-devel。这个安装的本质就是将下面这些头文件和库文件放到了对应的目录下

[zebra@VM-8-12-centos cpp]$ ls /usr/include/jsoncpp/json

assertions.h  autolink.h  config.h  features.h  forwards.h  json.h  reader.h  value.h  version.h  writer.h

[zebra@VM-8-12-centos cpp]$ ls /usr/lib64/libjson*

/usr/lib64/libjsoncpp.so    /usr/lib64/libjsoncpp.so.0.10.5  /usr/lib64/libjson-c.so.2.0.1  /usr/lib64/libjson.so.0.1.0

/usr/lib64/libjsoncpp.so.0  /usr/lib64/libjson-c.so.2        /usr/lib64/libjson.so.0

序列化:

1.引入头文件#include <jsoncpp/json/json.h>,在编译运行的时候要加上-ljsoncpp这个库

2.有一个request的结构体request_t req = {10, 20, '*'};

3.将结构体中的数据放到一个Json::Value类型的对象里面,这个对象可以装载任意类型的值(json是一种kv式的序列化方案) datax,datay,operator这些键都是自己取的名字

    Json::Value root; //可以承装任何对象, json是一种kv式的序列化方案

    root["datax"] = req.x;

    root["datay"] = req.y;

root["operator"] = req.op;

4.定义一个Json::FastWriter对象,然后调用里面的write方法,把Json::value对象转换成一个字符串,这样就可以进行传输啦。

这里的Writer有FastWriter和StyledWriter两种

    Json::FastWriter writer;

std::string json_string = writer.write(root);

反序列化:

1.有一个序列化后的string字符串(R的作用就是指明””里面的内容是一个字符串,否则要进行很多次转义)

std::string json_string = R"({"datax":10,"datay":20,"operator":42})";

2.定义一个Json::Reader对象,再定义一个Json::Value对象,然后调用parse方法,就可以把序列化后的字符串转换成一个Json::Value对象。

    reader.parse(json_string, root);

3.定义一个request结构体,然后从Json::Value对象里面把不同键对应的值取出来即可。(注意要调用as***方法指定对应值的类型),这里没有asChar,所以先转成int,然后强转成char

    request_t req;

    req.x = root["datax"].asInt();

    req.y = root["datay"].asInt();

    req.op = (char)root["operator"].asInt();

4.注意:反序列化完成,服务端拿到字符串数据以后要在字符串最后补上一个0,因为网络传输的时候并没有加上这个0,只有补上0,在C语言层面才可以把当前序列当做字符串看待)

/*** 
 * @description: 请求序列化
 * @param {request_t} &req
 * @return {*}
 */
std::string SerializeRequest(const request_t &req)
{
    Json::Value root; //可以承装任何对象, json是一种kv式的序列化方案
    root["datax"] = req.x;
    root["datay"] = req.y;
    root["operator"] = req.op;

    Json::FastWriter writer;
    std::string json_string = writer.write(root);
    return json_string;
}


/*** 
 * @description: 请求反序列化
 * @param {string} &json_string
 * @param {request_t} &out,这里的out是一个输出型参数,用于存放反序列化后的响应结构体
 * @return {*}
 */
void DeserializeRequest(const std::string &json_string, request_t &out)
{
    Json::Reader reader;
    Json::Value root;  //对于parse方法来说,root属于输出型参数

    reader.parse(json_string, root);
    out.x = root["datax"].asInt();
    out.y = root["datay"].asInt();
    out.op = (char)root["operator"].asInt();
}

/*** 
 * @description: 响应序列化
 * @param {response_t} &resp
 * @return {*}
 */
std::string SerializeResponse(const response_t &resp)
{
    Json::Value root;
    root["code"] = resp.code;
    root["result"] = resp.result;

    Json::FastWriter writer;
    std::string res = writer.write(root);

    return res;
}

/*** 
 * @description: 响应反序列化
 * @param {string} &json_string
 * @param {response_t} &out,同上,out是一个输出型参数
 * @return {*}
 */
void DeserializeResponse(const std::string &json_string, response_t &out)
{
    Json::Reader reader;
    Json::Value root;

    reader.parse(json_string, root);
    out.code = root["code"].asInt();
    out.result = root["result"].asInt();
}

自定义协议+jsoncpp实现简易计算器服务整体逻辑 

1.client根据我们的自定义协议构建好request请求,并使用jsoncpp将request请求进行序列化,然后发送给server

2.server接收到请求数据后,使用jsoncpp进行反序列化,根据自定义协议得到请求内容

3.server根据请求内容进行业务逻辑处理

4.server构建response响应,并使用jsoncpp进行序列化,发送给client。

5.client收到响应数据,使用jsoncpp反序列化,根据自定义协议得到响应结构response。

Server.cc 

#include <pthread.h>
#include "My_protocol.hpp"
#include "Sock.hpp"

/**
 * @description:使用手册,如果命令行参数不是2个就提示,因为要给出server启动时绑定的端口号
 * @param {string} proc
 * @return {*}
 */
static void Usage(string proc)
{
    cout << "Usage: " << proc << " port" << endl;
    exit(1);
}

void *HandlerRequest(void *args)
{
    int sock = *(int *)args;
    delete (int *)args;

    pthread_detach(pthread_self());  //分离线程,这样就不需要wait,由OS帮我们回收


    
    //1.读取请求
    char buffer[1024];
    request_t req;  //构建请求结构体,后序作为输出型参数传入反序列化函数
    ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
        buffer[s] = 0;
        cout << "获取到一个新的请求: " << buffer << endl;
        std::string str = buffer;

        //2.反序列化请求
        DeserializeRequest(str, req);

        //3.业务逻辑处理
        response_t resp = {0, 0};
        switch (req.op)
        {
        case '+':
            resp.result = req.x + req.y;
            break;
        case '-':
            resp.result = req.x - req.y;
            break;
        case '*':
            resp.result = req.x * req.y;
            break;
        case '/':
            if (req.y == 0)
                resp.code = -1; //代表除0
            else
                resp.result = req.x / req.y;
            break;
        case '%':
            if (req.y == 0)
                resp.code = -2; //代表模0
            else
                resp.result = req.x % req.y;
            break;
        default:
            resp.code = -3; //代表请求方法异常
            break;
        }
        cout << "request: " << req.x << req.op << req.y << endl;

        //4.序列化响应并发送
        std::string send_string = SerializeResponse(resp);   //序列化之后的字符串
        write(sock, send_string.c_str(),send_string.size());     
        cout << "服务结束: " << send_string << endl;
    }

    //5.关闭链接
    close(sock);
}

//命令行输入: ./Server port
int main(int argc, char *argv[])
{
    if (argc != 2)
        Usage(argv[0]);
    uint16_t port = atoi(argv[1]);

    int listen_sock = Sock::Socket();  //获取监听套接字(已封装好)
    Sock::Bind(listen_sock, port);  //绑定端口(已封装好)
    Sock::Listen(listen_sock);  //开始监听(已封装好)

    for (;;)
    {
        int sock = Sock::Accept(listen_sock);  //获取连接
        if (sock >= 0)
        {
            cout << "有新的连接来了" << endl;
            int *pram = new int(sock);
            pthread_t tid;
            pthread_create(&tid, nullptr, HandlerRequest, pram);  //创建一个线程来提供服务,执行业务逻辑
        }
    }

    return 0;
}

Client.cc

#include "My_protocol.hpp"
#include "Sock.hpp"

/**
 * @description: 使用手册,如果命令行参数不是3个就提示,因为要给出要访问的server服务的ip地址和端口号
 * @param {string} proc
 * @return {*}
 */
void Usage(string proc)
{
    cout << "Usage: " << proc << " server_ip server_port" << endl;
}
//命令行输入: ./Client server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    int sock = Sock::Socket();
    Sock::Connect(sock, argv[1], atoi(argv[2]));  //发起连接(已封装好)

    //构建请求
    request_t req;
    memset(&req, 0, sizeof(req));
    cout << "输入第一个操作数: ";
    cin >> req.x;
    cout << "输入第二个操作数: ";
    cin >> req.y;
    cout << "输入操作符: ";
    cin >> req.op;

    //序列化请求并发送
    std::string json_string = SerializeRequest(req);

    ssize_t s = write(sock, json_string.c_str(), json_string.size());

    //接收响应并反序列化响应
    char buffer[1024];
    s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
        response_t resp;
        buffer[s] = 0;
        std::string str = buffer;
        DeserializeResponse(str, resp);

        cout << "code[0:success]: " << resp.code << endl;
        cout << "result: " << resp.result << std::endl;
    }
    return 0;
}

运行结果

client 

 server

 

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

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

相关文章

重建control遗漏数据文件,reseltogs报ORA-1555错误处理----惜分飞

又一客户,误删除oracle redo导致数据库无法正常启动,自己尝试重建ctl,结果遗漏部分oracle数据文件并且尝试过resetlogs,导致部分文件resetlogs scn不一致.导致重建ctl失败Fri Feb 10 12:41:20 2023CREATE CONTROLFILE REUSE DATABASE "orcl"RESETLOGS NOARCHIVELOG M…

GO 中的 init 函数

前言 go 语言中有一个非常神奇的函数 init ,它可以在所有程序执行开始前被执行&#xff0c;并且每个 package 下面可以存在多个 init 函数&#xff0c;我们一起来看看这个奇怪的 init 函数。 init 特性 init 函数在 main 函数之前执行&#xff0c;并且是自动执行&#xff1b…

Docker网络实现原理

目录 1 Docker网络实现原理 1.2 为容器创建端口映射 方法一&#xff1a;随机映射端口&#xff08;从32768开始&#xff09; 方法二&#xff1a;指定映射端口 1.3 查看容器的输出和日志信息 二 Docker的网络模式 2.1 Docker的网络模式&#xff08;41&#xff09; 2.2 查看…

【大数据趋势】2月12日 货币发动机牵着港股和A股走,历史不会简单重演,但是会用类似的方法让人再踏入同一条河流

行情核心源头之一 : 离岸人民币和美元趋势历史对比&#xff0c;预示着一个阶段底部正在形成中 历史总是很容易忘记&#xff0c;应该很少有人记得18年发生了什么。还是让大数据程序来对比一下。【红色标记1】RSI预示着價格強度的动能情况&#xff0c;同样是达到了一个高点&…

2021 WAIC 世界人工智能大会参会总结

前言 2021 年世界人工智能大会&#xff08;WAIC&#xff09;于2021年7月7日至10日在上海世博展览馆举办&#xff0c;本届大会继续秉持「智联世界」的理念&#xff0c;以「众智成城」为主题&#xff0c;促进全球人工智能创新思想、技术、应用、人才和资本的集聚和交流&#xff…

JS逆向案例分享----prototype的妙用

方向不对&#xff0c;努力白费。爬虫也是如此。今天分享的案例很能说明问题。目标&#xff1a;某集团公司采购信息aHR0cHM6Ly9lYy5taW5tZXRhbHMuY29tLmNuL29wZW4vaG9tZS9wdXJjaGFzZS1pbmZv浏览器抓包&#xff0c;请求载荷里就一个加密参数param全局搜索“param:”&#xff0c;有…

【计组】DMA、数据完整性--《深入浅出计算机组成原理》(十三)

目录 一、DMA &#xff08;一&#xff09;理解DMA&#xff0c;一个协处理器 &#xff08;二&#xff09; Kafka 的实现原理 二、数据的完整性 &#xff08;一&#xff09;单比特翻转&#xff1a;软件解决不了的硬件错误 &#xff08;二&#xff09;海明码 1、海明码的纠错…

《狂飙》壁纸大嫂如此惊艳,做成日历壁纸天天看(7)

小朋友们好&#xff0c;大朋友们好&#xff01;我是猫妹&#xff01;话说兔年春节期间&#xff0c;一部反黑反腐电视剧横空出世&#xff0c;收视率和口碑都有不错的成绩&#xff01;这部电视剧叫《狂飙》&#xff01;你看了吗&#xff1f;我没看&#xff01;不过这丝毫不影响它…

C语言fread和fwrite的用法详解

fgets() 有局限性&#xff0c;每次最多只能从文件中读取一行内容&#xff0c;因为 fgets() 遇到换行符就结束读取。如果希望读取多行内容&#xff0c;需要使用 fread() 函数&#xff1b;相应地写入函数为 fwrite()。对于 Windows 系统&#xff0c;使用 fread() 和 fwrite() 时应…

Python+OpenCV 简单实现人脸检测多个和人脸识别 2(附代码)

如果dilb和face_recognition第三方包安装失败&#xff0c;请移步到Python 解决dilb和face_recognition第三方包安装失败_水w的博客-CSDN博客 上篇请移步到Pythondilb 简单实现人脸检测&#xff08;附代码&#xff09;_水w的博客-CSDN博客 本篇是在上篇的工作基础上进行的。 目…

k8s之apiserver

1、Kube-APIServer 启动APIServer 启动采用 Cobra 命令行&#xff0c;解析相关 flags 参数&#xff0c;经过 Complete(填充默认值)->Validate(校验) 逻辑后&#xff0c;通过 Run 启动服务。在 Run 函数中&#xff0c;按序分别初始化 APIServer 链(APIExtensionsServer、Kube…

【Java开发笔记】分库分表

【Java开发笔记】分库分表 1 分库分表基本概述 为什么要分库分表&#xff1f; 【性能角度】分库分表就是为了解决由于数据量多大而导致数据库性能下降的问题&#xff1a; 原来独立的数据库拆分成若干数据库组成将原来的大表&#xff08;存储近千万数据&#xff09;拆分为若干…

利用git reflog 命令来查看历史提交记录,并使用提交记录恢复已经被删除掉的分支

一.问题描述 当我们在操作中手误删除了某个分支&#xff0c;那该分支中提交的内容也没有了&#xff0c;我们可以利用git reflog这个命令来查看历史提交的记录从而恢复被删除的分支和提交的内容 二.模拟问题 1.创建git仓库&#xff0c;并提交一个文件 [rootcentos7-temp /da…

oracle10g安装教程

oracle 10g 安装 环境 操作系统&#xff1a;win 7 64位 内存&#xff1a;8G Oracle 10压缩包&#xff1a;10203_vista_w2k8_x86_production_db.zip 客户端&#xff1a;Oracle_client_win32.zip pl/sql&#xff1a;plsqldev1005.exe 背景 Oracle是众多中大型企业必选的数…

17万字数字化医院信息化建设大数据平台建设方案WORD

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 目录 第1章 医院信息化概述 1.…

MySQL-数据目录浅析

InnoDB 、 MyISAM 这样的存储引擎都是把表存储在磁盘上的&#xff0c;操作系统用文件系统来管理磁盘。 数据目录 MySQL服务器程序在启动时会到文件系统的某个目录下加载一些文件&#xff0c;之后在运行过程中产生的数据也都会存储到这个目录下的某些文件中&#xff0c;这个目…

JVM学习02:内存结构

JVM学习02&#xff1a;内存结构 1. 程序计数器 1.1、定义 Program Counter Register 程序计数器&#xff08;寄存器&#xff09; 作用&#xff1a;是记住下一条jvm指令的执行地址 特点&#xff1a; 是线程私有的不会存在内存溢出 1.2、作用 程序计数器物理上是由寄存器来实…

Spring中IOC框架结构是什么?都包含那些模块,各个模块具体是什么样的

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库Java设计模式克隆下载学习使用&#xff01; 7.自定义Spring框架 7.1 Spring框架使用回顾 7.1.1 数据访问层 定义UserDaoMapper接口及实现类 public interface UserMapper { public void add(); } pu…

k8s默认StorageClass,解决pvc一直处于“Pending”

文章目录报错详情排查思路查看 pvc 详细属性查看 nfs-provisioner pod日志解决方案报错详情 排查思路 查看 pvc 详细属性 [rootk8s-master01 /opt/zadig]# kubectl describe pvc pvc-sc Name: pvc-sc Namespace: default StorageClass: nfs-yinwu Status: …

代码随想录算法训练营第27天|● 93.复原IP地址 ● 78.子集 ● 90.子集II

93.复原IP地址 看完题后的思路 典型分割问题略lue略剪枝条件 sub&#xff1a; 1&#xff09; 不是一位首字母为0 2&#xff09;大于三位 3&#xff09;介于0-255之间 4) 当已分割得到3个时&#xff0c;第四个直接从startIndex到末尾就行 代码 ArrayList<String> slist…