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

这篇具有很好参考价值的文章主要介绍了协议定制 + Json序列化反序列化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

1. 再谈 “协议”

1.1 结构化数据

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

结构化数据:

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

1.2 序列化和反序列化

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

协议定制 + Json序列化反序列化,linux网络编程,网络,linux

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

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

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

2. 网络版计算器

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

2.1 服务端

服务端创建步骤:

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

2.2 协议定制

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

协议定制 + Json序列化反序列化,linux网络编程,网络,linux

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

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

综上:

  • TCP通信的本质是自己发送缓冲区的数据经过网络拷贝到对方的接收缓冲区中
  • 网络通信的本质也是拷贝
(2) 协议定制的问题

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

我们采用这样的方式:

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

协议设计格式:

协议定制 + Json序列化反序列化,linux网络编程,网络,linux

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\n
    string 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\n
        auto pos=inbuffer.find(HEADER_SEP);
        if(pos==string::npos)
            return 0;
        
        string lenStr=inbuffer.substr(0,pos);    // 获取头部字符串, 没有动inbuffer
        int len=Util::toInt(lenStr);             // 得到有效载荷的长度 => "123" -> 123
        int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size();   // 得到整个报文长度
        if(inbuffer.size()<targetPackageLen)     // 不是一个完整的报文
            return 0;
        
        *package=inbuffer.substr(0,targetPackageLen);  // 提取到了报文有效载荷, 没有动inbuffer
        inbuffer.erase(0,targetPackageLen);      // 从inbuffer中直接移除整个报文

        cout<<"ReadPackage inbuffer 之后:\n"
            <<inbuffer<<endl;

        return len;
    }

    // Request && Response都要提供序列化和反序列化功能
    // 1. 自己手写
    // 2. 用别人的 --- json, xml, protobuf

    class Request
    {
    public:

        Request()
        {

        }

        Request(int x,int y,char op)
            :_x(x)
            ,_y(y)
            ,_op(op)
        {
        }

        // 序列化: struct->string
        bool 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->struct
        bool Deserialize(const string&inStr)   
        {
            // inStr:  10 + 20 => [0]=>10, [1]=>+, [2]=>20
            vector<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->string
        bool 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->struct
        bool Deserialize(const string&inStr)   
        {
            // 10 0
            vector<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 + 20
        size_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)

运行结果:

协议定制 + Json序列化反序列化,linux网络编程,网络,linux

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

3.1 简单介绍

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

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

协议定制 + Json序列化反序列化,linux网络编程,网络,linux

3.2 使用

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

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

协议定制 + Json序列化反序列化,linux网络编程,网络,linux

我们在使用的时候直接创建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\n
    string 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\n
        auto pos=inbuffer.find(HEADER_SEP);
        if(pos==string::npos)
            return 0;
        
        string lenStr=inbuffer.substr(0,pos);    // 获取头部字符串, 没有动inbuffer
        int len=Util::toInt(lenStr);             // 得到有效载荷的长度 => "123" -> 123
        int targetPackageLen=len+2*HEADER_SEP_LEN+lenStr.size();   // 得到整个报文长度
        if(inbuffer.size()<targetPackageLen)     // 不是一个完整的报文
            return 0;
        
        *package=inbuffer.substr(0,targetPackageLen);  // 提取到了报文有效载荷, 没有动inbuffer
        inbuffer.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->string
        bool Serialize(string* outStr)     
        {
            *outStr=""; 
#ifdef  MYSELF
            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;
#else
            Json::Value root;   // Value: 一种万能对象, 接受任意的kv类型
            root["x"]=_x;
            root["y"]=_y;
            root["op"]=_op;

            // Json::FastWriter writer;  // writer: 是用来进行序列化的 struct -> string
            Json::StyledWriter writer;

            *outStr=writer.write(root);
#endif
            return true;
        }

        // 反序列化: string->struct
        bool Deserialize(const string&inStr)   
        {
#ifdef  MYSELF
            // inStr:  10 + 20 => [0]=>10, [1]=>+, [2]=>20
            vector<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];

#else
            Json::Value root;   
            Json::Reader reader;  // Reader: 是用来反序列化的
            reader.parse(inStr,root);

            _x=root["x"].asUInt();
            _y=root["y"].asUInt();
            _op=root["op"].asUInt();
    
#endif
            Print();

            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->string
        bool Serialize(string* outStr)     
        {
            // _result _code
            *outStr=""; 
#ifdef  MYSELF
            string res_string = to_string(_result);
            string code_string = to_string(_code);

            // 手动序列化
            *outStr=res_string + SEP + code_string;

#else
            Json::Value root;   
            root["result"]=_result;
            root["code"]=_code;
            // Json::FastWriter writer;
            Json::StyledWriter writer;

            *outStr=writer.write(root);
#endif
            return true;
        }

        // 反序列化: string->struct
        bool Deserialize(const string&inStr)   
        {
#ifdef  MYSELF
            // 10 0
            vector<string> result;
            Util::StringSplit(inStr,SEP,&result);

            if(result.size()!=2)
                return false;

            _result=Util::toInt(result[0]);
            _code=Util::toInt(result[1]);

#else
            Json::Value root;
            Json::Reader reader; 
            reader.parse(inStr, root);

            _result = root["result"].asUInt();
            _code = root["code"].asUInt();
#endif
            Print();
            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)文章来源地址https://www.toymoban.com/news/detail-696961.html

到了这里,关于协议定制 + Json序列化反序列化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux后端服务器开发】协议定制(序列化与反序列化)

    目录 一、应用层协议概述 二、序列化与反序列化 Protocal.h头文件 Server.h头文件 Client.h头文件 server.cpp源文件 client.cpp源文件 什么是应用层 ?我们通过编写程序解决一个个实际问题、满足我们日常需求的网络程序,都是应用层程序。 协议是一种“约定”,socket的api接口,在读

    2024年02月16日
    浏览(30)
  • 【网络】协议定制+序列化/反序列化

    如果光看定义很难理解序列化的意义,那么我们可以从另一个角度来推导出什么是序列化, 那么究竟序列化的目的是什么? 其实序列化最终的目的是为了对象可以 跨平台存储,和进行网络传输 。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是

    2024年02月08日
    浏览(35)
  • TCP定制协议,序列化和反序列化

    目录 前言 1.理解协议 2.网络版本计算器 2.1设计思路 2.2接口设计 2.3代码实现: 2.4编译测试 总结         在之前的文章中,我们说TCP是面向字节流的,但是可能对于面向字节流这个概念,其实并不理解的,今天我们要介绍的是如何理解TCP是面向字节流的,通过编码的方式,自

    2024年02月12日
    浏览(24)
  • 【网络】应用层——协议定制 | 序列化和反序列化 | 初识http

    🐱作者:一只大喵咪1201 🐱专栏:《网络》 🔥格言: 你只管努力,剩下的交给时间! 在前面本喵已经带大家见识过了 scoket 网络通信的样子,现在开始深入学习网络的原理,本喵采取的策略是从顶层往底层讲解,也就是从应用层到数据链路层的顺序。 我们知道,协议就是

    2024年02月15日
    浏览(33)
  • 协议,序列化,反序列化,Json

    协议究竟是什么呢?首先得知道主机之间的网络通信交互的是什么数据,像平时使用聊天APP聊天可以清楚,用户看到的不仅仅是聊天的文字,还能够看到用户的头像昵称等其他属性。也就可以证明网络通信不仅仅是交互字符串那么简单。事实上网络通信还可能会通过一个结构

    2024年02月13日
    浏览(29)
  • 序列化协议:JSON和XML

    作者:CARROT 链接:https://www.zhihu.com/question/604811576/answer/3100483698 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。   json和xml都是数据传输的格式。比如我们开发过程中需要和网页交换数据,我们既可以使用json格式也可以使用xml格式。再

    2024年02月11日
    浏览(28)
  • Go语言网络编程入门:TCP、HTTP、JSON序列化、Gin、WebSocket、RPC、gRPC示例

    在本文中,我们将介绍Go语言中的网络编程的不同方式,包括TCP、HTTP、Gin框架、WebSocket、RPC、gRPC的介绍与连接实例,并对所有示例代码都给出了详细的注释,最后对每种模式进行了总结。 TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,提供

    2024年02月16日
    浏览(48)
  • 【探索Linux】P.30(序列化和反序列化 | JSON序列化库 [ C++ ] )

    当谈到网络编程时,序列化和反序列化是非常重要的概念。在上一篇文章中,我们已经了解了在Linux环境下实现简单的TCP网络程序的基础知识。本文将继续探讨序列化和反序列化,这些概念对于在网络上传输数据以及跨网络通信至关重要。通过深入了解序列化和反序列化,我

    2024年04月08日
    浏览(75)
  • 【Linux】自定义协议+序列化+反序列化

    喜欢的点赞,收藏,关注一下把! 协议是一种 “约定”。在前面我们说过父亲和儿子约定打电话的例子,不过这是感性的认识,今天我们理性的认识一下协议。 socket api的接口, 在读写数据时,都是按 “字符串”(其实TCP是字节流,这里是为了理解) 的方式来发送接收的。如

    2024年04月08日
    浏览(32)
  • 【Linux】应用层协议序列化和反序列化

    欢迎来到Cefler的博客😁 🕌博客主页:折纸花满衣 🏠个人专栏:题目解析 🌎推荐文章:C++【智能指针】 前言 在正式代码开始前,会有一些前提知识引入 在网络应用层中,序列化(Serialization)和反序列化(Deserialization)是将数据转换为可在网络上传输的格式,并从网络接

    2024年04月23日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包