文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-10-03 21:41

关注

文章目录

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

1. 再谈 “协议”

1.1 结构化数据

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

结构化数据:

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

1.2 序列化和反序列化

在这里插入图片描述

2. 网络版计算器

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

2.1 服务端

服务端创建步骤:

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

2.2 协议定制

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

在这里插入图片描述

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

综上:

(2) 协议定制的问题

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

我们采用这样的方式:

协议设计格式:

在这里插入图片描述

Protocol.hpp

#include#include#include#include#include#include#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#include#includeusing 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)

运行结果:

在这里插入图片描述

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

3.1 简单介绍

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

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

在这里插入图片描述

3.2 使用

yum install -y jsoncpp-devel
#include 

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

在这里插入图片描述

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

Protocol.hpp

#include#include#include#include#include#include#include"Util.hpp"#includeusing 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://blog.csdn.net/Ryujianli/article/details/132722939

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯