Linux网络——自定义协议与序列化

news/2024/11/14 0:09:14 标签: linux, 网络, 服务器

一、协议

协议是一种 " 约定 ". socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的。如
果我们要传输一些 " 结构化的数据 ",依然可以通过协议。
其实,协议就是双方约定好的结构化的数据。

二、网络计算器

假如说,想要制作一个网络计算器,我们就需要给出特定的协议,让客户端发出的运算条件能够被服务端接收并计算再进行返回。

我们已经了解,协议就是通信双方约定好的结构化数据,所以我们自定义协议,就可以通过结构体来实现,例如自制一个专用于加减乘除取模的计算器,我们制定如下协议:

struct Request

{

    int x;

    int y;

    char oper;//+ - * / %        

};

在该协议中,x只用于第一个运算数,y只用于第二个运算数,oper为运算符。

struct Response

{

    int result;

    int code;//0:success 1:dev zero 2.非法操作

};

在该协议中,result为运算结果,code表示运算情况,0表示成功运算,1为除0错误,2为非法使用其他运算符。

但是仅仅有了上述结构体,就可以实现用户与服务器的完美通信了吗,并不能

我们的服务器是Linux系统,但是客户端呢?一定也是Linux系统吗?当然不一定,客户端可以是windows,安卓以及iOS,甚至客户端和服务端所使用的编程语言也不相同,更重要的是,网络通信是以字节流的方式,我们并不能直接传递结构体数据

那么解决这一问题,可以通过下述方法:

定义结构体来表示我们需要交互的信息, 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体,所有的系统,语言,它都认识字符串, 这个过程叫做 "序列化 " "反序列化"

1.序列化和反序列化

在上述结构体中,一个运算是由结构体的三部分共同构成的,而序列化,就是将这三部分整合成一个字符串,即"x oper y"这样一个字符串,三部分之间通过自己规定的字符隔开,比如空格,这样我们就把一个结构体转换成一个字符串,通过网络传输之后,再在接受方将字符串进行分割,重新组成结构体,即反序列化

在库中,封装了很多能够实现序列化和反序列化的工具,包括xml、json、protobuf等等,其中json是c++标准库所封装的,所以本篇文章我们就来分享json实现序列化反序列化。


2.Jsoncpp

Jsoncpp 是一个用于处理 JSON 数据的 C++ 。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。

下面是Linux两种不同环境下,按照json库的方法:

ubuntusudo apt-get install libjsoncpp-dev

Centos: sudo yum install jsoncpp-devel


3.序列化

因为刚下载的json库,并没有进行链接,所以使用json库需要包含头文件:

#include <jsoncpp/json/json.h>

下面我们将上边给出的Request类进行序列化,来看代码:

    //序列化
    void Serialize(string *out)
    {
        //1.使用现成的库
        Json::Value root;
        root["x"] = _x;
        root["y"] = _x;
        root["oper"] = _oper;

        Json::FastWriter writer;
        *out = writer.write(root); 
    }

    int main()
    {
        Request req(1,2,'+');
        string out;
        req.Serialize(&out);
        cout << out << endl;
        return 0;
    }

定义一个Json库中的Value对象,该对象中重载了“[]”,通过键值对的方式,将成员变量与其对应的值捆绑,并记录在root对象中,紧接着定义Json库中的FastWriter对象,其中的write函数,能够将Value对象中存放的键值对转换为字符串并返回。

结果如下:

该字符串就是通过Json库序列化之后形成的,称为Json串

当然除了普通的内置类型数据,Json还可以序列化Value对象自己,以及数组等各种类型的数据


 4.反序列化

直接来看代码:

    // 反序列化
    void Deserialize(const string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(in, root);
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _oper = root["oper"].asInt();
        cout << _x << ' ' << _y << ' ' << _oper << endl;
    }
    
    int main()
    {
        string in = "{\"oper\":43,\"x\":1,\"y\":1}";
        Request req;
        req.Deserialize(in);
        return 0;
    }

反序列化,需要定义Json类中的Reader对象,调用其中的parse函数传入要反序列化的Json串,以及Value对象用于接收反序列化后的键值对数据。随后,通过asInt()函数,将数据以整型方式获取,注意单字符也是整型,后续通过ASCLL码转换

结果如下:


5.设计协议报头

单单进行序列化,是无法满足直接通过网络进行传输的,因为在传输过程中,可能出现阻塞,导致最终可能无法得到完整的数据序列。所以我们还需要给协议添加报头,使得得到的整个报文格式完整,这里我们设计报文格式为:

"len"\r\n"{json}"\r\n

  • len:表示json串有效载荷的长度。
  • 中间\r\n:用于区分len和json串。
  • 结尾\r\n:暂时无用,方便debug。

具体方法如下:

static const string sep = "\r\n";
//添加报头
string Encode(const string &jsonstr)
{
    int len = jsonstr.size();
    string strlen = to_string(len);
    return strlen + sep + jsonstr + sep;
}

添加报头较为简单,获取到json串的长度,转为string类型,在进行拼接即可。

//拆解报头
string Decode(string &packagestream)
{
    //是否拥有完整的中间sep
    auto pos = packagestream.find(sep);
    if(pos == string::npos) return string();
    //获得json串长度
    string strlen = packagestream.substr(0,pos);
    int len = stoi(strlen);

    //得到报文完整长度
    int total = strlen.size() + len + 2 * sep.size();
    if(packagestream.size() < total) return string();
    //得到json串
    string jsonstr = packagestream.substr(pos + sep.size(),len);
    //将得到的json串从原数据流中删除
    packagestream.erase(total);
    return jsonstr;
}

在拆解报头中,首先要进行判断,该数据流是否包含完整的中间sep,不包含说明数据不全,不做拆解;进而通过json串的长度,能够计算出整个报文的长度判断数据流的长度是否小于整个报文的长度,如果小于,则说明没有完整的报文,不做拆解;如果大于,说明包含完整的报文,就可以进行拆解,得到json串,最后将拆解的报文从原数据流中删除

随后在进行TCP/UDP通信时,我们只需要将要发送数据先序列化并添加报头,得到完整的报文,再在接收数据时,去除报头,进而将报文反序列化,从而得到数据



http://www.niftyadmin.cn/n/5750914.html

相关文章

error MSB3325:无法导入以下密钥文件xxx,该密钥文件可能受密码保护

错误提示信息(类似如下)&#xff1a; error MSB3325: 无法导入以下密钥文件: F:\...\Common.pfx。该密钥文件可能受密码保护。若要更正此问题&#xff0c;请尝试再次导入证书&#xff0c;或手动将证书安装到具有以下密钥容器名称的强名称 CSP: VS_KEY_A65F207BE95F57D0 出现此…

文献阅读 | Nature Methods:使用 STAMP 对空间转录组进行可解释的空间感知降维

文献介绍 文献题目&#xff1a; 使用 STAMP 对空间转录组进行可解释的空间感知降维 研究团队&#xff1a; 陈金妙&#xff08;新加坡科学技术研究局&#xff09; 发表时间&#xff1a; 2024-10-15 发表期刊&#xff1a; Nature Methods 影响因子&#xff1a; 36.1&#xff0…

RK3568平台开发系列讲解(GPIO篇)GPIO的sysfs调试手段

🚀返回专栏总目录 文章目录 一、内核配置二、GPIO sysfs节点介绍三、命令行控制GPIO3.1、sd导出GPIO3.2、设置GPIO方向3.3、GPIO输入电平读取3.4、GPIO输出电平设置四、Linux 应用控制GPIO4.1、控制输出4.2、输入检测4.3、使用 GPIO 中断沉淀、分享、成长,让自己和他人都能有…

@ComponentScan:Spring Boot中的自动装配大师

文章目录 1. 什么是ComponentScan注解&#xff1f;2. 为什么需要ComponentScan注解&#xff1f;3. 如何使用ComponentScan注解&#xff1f;4. ComponentScan注解的高级用法5. 注意事项6. 结语推荐阅读文章 在Spring Boot的世界里&#xff0c;自动装配&#xff08;Auto-wiring&a…

GitHub每日最火火火项目(11.13)

项目名称&#xff1a;dockur / macos 项目介绍&#xff1a;“dockur / macos”项目致力于将 OSX&#xff08;macOS&#xff09;系统放置于 Docker 容器内。在当今多样化的技术应用场景中&#xff0c;这一举措具有重要意义。对于开发者而言&#xff0c;能够在不同的环境中快速部…

算力100问☞第1问:算力为什么重要?

算力之所以重要&#xff0c;小编简单概括了以下几个核心方面&#xff0c;供大家参考借鉴。 1、算力&#xff1a;科学进步的坚固基石 在现代科技领域&#xff0c;算力是支撑各项高科技突破与发展的基础。无论是基因测序、药物研发、天气预报&#xff0c;还是自动驾驶和智能制造…

FRTC8563实时时钟芯片的作用

FRTC8563是NYFEA徕飞公司推出的一款实时时钟芯片&#xff0c;采用SOP-8封装形式。这种封装形式具有体积小、引脚间距小、便于集成等特点&#xff0c;使得FRTC8563能够方便地应用于各种电子设备中&#xff0c;如&#xff1a;安防摄像机、监控摄像机、行车记录仪、车载电子等。 F…

通过物流分拣系统来理解RabbitMQ的消息机制

RabbitMQ作为一个消息中间件&#xff0c;通过队列和路由机制&#xff0c;帮助应用程序高效传递消息。而它的消息流转过程&#xff0c;其实可以用物流分拣系统来直观理解。 在一个典型的物流分拣系统中&#xff0c;包裹会经过多个节点&#xff08;比如分拣中心、配送站&#xf…