Linux网络编程——序列反序列化

本章Gitee仓库:序列反序列化

0. 前言

tcp是面向字节流的,但是如何保证读取的数据是一个完整的报文呢?

管道也是面向字节流,写端写了一大堆的数据,然后读端可能一下子全部读上来了,这一堆数据如何区分,是一个需要考虑的问题

tcp是传输控制协议,它要管:

  1. 什么时候发送
  2. 发送多少数据
  3. 如果出错了如何解决

我们用户在应用层,通过write将数据写到tcp发送缓冲区,然后write直接返回,但是这个数据并不一定发送给对方了,write只是将数据从用户拷贝到内核,而具体什么时候发、怎么发,都是由tcp决定,因为只有tcp才清楚当前网络的状况和接收方的接收能力

tcp是操作系统的网络模块部分,就可以理解为,用户将数据给了操作系统

接收方通过read将接收缓冲区的数据拷贝到用户层的缓冲区,怎么读这些报文是个问题。

所以需要在用户层需要定制协议

1. 认识协议

比如说要实现一个网络计算器,一个算式要包括数据和运算符号,例如1 + 12 * 2

那就可以定义一个结构,规定的每次发送的数据格式:

struct task
{
    int data1;	//数据1
    int data2;	//数据2
    char op;	//运算符
}

然后接收方那边有一个相同的结构体进行接收,然后将数据提取出来即可,然后计算的结果,也要规定格式,例如是否正确,如果不正确是什么原因导致:

struct calc
{
    int result;	//结果
    int code;	//退出码
}

以上这种以结构化的方式把约定表达出来,这就是协议。

2. 序列号与反序列化

对于不同的平台struct的大小可能不一样,有的可能会内存对齐,有的又不会,所以不能够直接将结构体对象发送给对方。

例如我们微信聊天的时候:

image-20240218230034206

我们发送的消息不只是消息本身,还携带了发送的数据,谁发送的,以及我们的头像

struct msg
{
    string info;	//消息
    string nickname;	//昵称
    string time;	//时间
}

这里是三个字符串,肯定是不能分开发的,不然消息就混乱了,所以就需要打个包,将这三个字符串转成一个字符串发送,这叫序列化;接收方收到这一个字符串之后,就将这一个字符串再解析成三个字符串,这就叫反序列化

image-20240218231157926

序列反序列化的主要目的就是方便网络进行接收。

所以在定制协议的时候,只需要考虑定制什么样的结构化数据,然后将这个结构化的数据序列化成一个大字符串。

这里面也有一个隐含的问题,就是如何将这个大字符串反序列化,所以我们在发的时候,要设置好标记

3. 自定义协议——网络计算器

网络版本的计算机,就要涉及到数据包的发送,所以这里就需要定制协议。

要将一个结构化的数据转成字符串,就要设置后间隔标志,以便于反序列化:

protocol.hpp:

#pragma once

#include<iostream>

const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";

//添加报头
std::string Encode(std::string &content)
{
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;
    return package;
}

//去掉报头
bool Decode(std::string &package, std::string *content)
{
    //"len"\n"x op y"\n
    size_t pos = package.find(protocol_sep);
    if(pos == std::string::npos)    return false;
    std::string len_str = package.substr(0, pos);
    size_t len = std::stoi(len_str);
    size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len)  return false;
    
    *content = package.substr(pos+1, len);
    
    //移除已提取报文
    package.erase(0, total_len);
    return true;
}

class Request
{
public:
    Request(int data1, int data2, char op)
    :data1_(data1),data2_(data2),op_(op)
    {}
    Request()
    {}
public:
    //序列化
    bool Serialize(std::string *out)
    {
        //有效载荷
        //x op y
        std::string s = std::to_string(data1_);
        s += blank_space_sep;
        s += op_;
        s += blank_space_sep;
        s += std::to_string(data2_);
        
        *out = s;
        
        return true;
    }
    //反序列化
    bool Deserialize(const std::string &in)
    {
        //"data1 op data2"
        size_t leftpos = in.find(blank_space_sep);
        if(leftpos == std::string::npos)    return false;
        std::string part1 = in.substr(0, leftpos);

        size_t rightpos = in.rfind(blank_space_sep);
        if(rightpos == std::string::npos)   return false;
        std::string part2 = in.substr(rightpos+1);

        if(leftpos+1 != rightpos-1)    return false;
        op_ = in[leftpos+1];
        data1_ = std::stoi(part1);
        data2_ = std::stoi(part2);
        
        return true;
    }

    void DebugPrint()
    {
        std::cout << "receive a request: " << data1_ << op_ << data2_ << std::endl;
    }
public:
    int data1_;
    int data2_;
    char op_;
};


class Response
{
public:
    Response(int res, int code)
    :result_(res),exitcode_(code)
    {}
    Response()
    {}
public:
    //序列化
    bool Serialize(std::string *out)
    {
        //有效载荷
        //"result exitcode"
        std::string s = std::to_string(result_);
        s += blank_space_sep;
        s += std::to_string(exitcode_);

        *out = s;
        
        return true;
    }
    //反序列化
    bool Deserialize(const std::string &in)
    {
        //"result exitcode"
        size_t pos = in.find(blank_space_sep);
        if(pos == std::string::npos)    return false;
        std::string part1 = in.substr(0, pos);
        std::string part2 = in.substr(pos+1);

        result_ = std::stoi(part1);
        exitcode_ = std::stoi(part2);
        
        return true;
    }

    void DebugPrint()
    {
        std::cout << "ret calc done, result: " << result_ << " exitcode: " << exitcode_ << std::endl;
    }

public:
    int result_;
    int exitcode_;
};

这个协议可以自己定,下面稍微讲解一下本次自定义的协议

这里规定,以空格为分隔符:data1 op data2,需要向网络发送的时候,会添加一个报头,格式为"字符串长度"\n报文\n;收到这个请求之后,先将这个大字符串反序列化,提取data1 op data2,当然这里可能解析的不是一个完整的报文,所以需要判断解析的报文不超过原始字符串的长度。

对于计算结果的序列和反序列化,比请求的稍微简单一点,这里就不多说了,代码里面有注释

运行演示:

GIF 2024-2-20 1-15-48

4. json

当然序列反序列化也不用每次我们自己手动去做,我们可以采用json

C++要使用json,是需要安装第三方库的jsoncpp

sudo yum install -y jsoncpp-devel

image-20240220012523958

test.cc:

#include<iostream>
#include<jsoncpp/json/json.h>

int main()
{

    Json::Value js;
    js["json"] = "js";
    js["aaa"] = "AAA";

    Json::Value v1;
    v1["data1"] = 100;
    v1["data2"] = 20;
    v1["op"] = '+';
    v1["rejs"] = js;    //json里面套json
    
    //序列化
    //Json::FastWriter fw;
    Json::StyledWriter sw;
    std::string ser = sw.write(v1);
    std::cout << ser << std::endl;

    //反序列化
    Json::Value v2;
    Json::Reader r;
    //参数1:需要反序列化的字符串    参数2:反序列化的结果    参数3:直接缺省先
    r.parse(ser, v2);

    int data1 = v2["data1"].asInt();
    int data2 = v2["data2"].asInt();
    char op = v2["op"].asInt();
    Json::Value tmp = v2["rejs"];
    std::string s1 = tmp["json"].asString();
    std::string s2 = tmp["aaa"].asString();

    std::cout << data1 << std::endl;
    std::cout << data2 << std::endl;
    std::cout << op << std::endl;
    std::cout << s1 << std::endl;
    std::cout << s2 << std::endl;


    return 0;
}

image-20240220014818730

这样就可以使用条件编译,来选择是用我们自己写的序列反序列化,还是直接使用json

编译的时候加上-D选项,例如要定义#define Myself 1,就可以加上-DMyself=1

#pragma once

#include<iostream>
#include<jsoncpp/json/json.h>
//#define Myself 1

const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";

//添加报头
std::string Encode(std::string &content)
{
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;
    return package;
}

//去掉报头
bool Decode(std::string &package, std::string *content)
{
    //"len"\n"x op y"\n
    size_t pos = package.find(protocol_sep);
    if(pos == std::string::npos)    return false;
    std::string len_str = package.substr(0, pos);
    size_t len = std::stoi(len_str);
    size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len)  return false;
    
    *content = package.substr(pos+1, len);
    
    //移除已提取报文
    package.erase(0, total_len);
    return true;
}

class Request
{
public:
    Request(int data1, int data2, char op)
    :data1_(data1),data2_(data2),op_(op)
    {}
    Request()
    {}
public:
    //序列化
    bool Serialize(std::string *out)
    {
#ifdef Myself
        //有效载荷
        //x op y
        std::string s = std::to_string(data1_);
        s += blank_space_sep;
        s += op_;
        s += blank_space_sep;
        s += std::to_string(data2_);
        
        *out = s;
        
        return true;
#else
        Json::Value root;
        root["data1"] = data1_;
        root["data2"] = data2_;
        root["op"] = op_;
        Json::FastWriter fw;
        *out = fw.write(root);
        return true;
#endif
    }
    //反序列化
    bool Deserialize(const std::string &in)
    {
#ifdef Myself
        //"data1 op data2"
        size_t leftpos = in.find(blank_space_sep);
        if(leftpos == std::string::npos)    return false;
        std::string part1 = in.substr(0, leftpos);

        size_t rightpos = in.rfind(blank_space_sep);
        if(rightpos == std::string::npos)   return false;
        std::string part2 = in.substr(rightpos+1);

        if(leftpos+1 != rightpos-1)    return false;
        op_ = in[leftpos+1];
        data1_ = std::stoi(part1);
        data2_ = std::stoi(part2);
        
        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        data1_ = root["data1"].asInt();
        data2_ = root["data2"].asInt();
        op_ = root["op"].asInt();
        return true;
#endif
    }

    void DebugPrint()
    {
        std::cout << "receive a request: " << data1_ << op_ << data2_ << std::endl;
    }
public:
    int data1_;
    int data2_;
    char op_;
};


class Response
{
public:
    Response(int res, int code)
    :result_(res),exitcode_(code)
    {}
    Response()
    {}
public:
    //序列化
    bool Serialize(std::string *out)
    {
#ifdef Myself
        //有效载荷
        //"result exitcode"
        std::string s = std::to_string(result_);
        s += blank_space_sep;
        s += std::to_string(exitcode_);

        *out = s;
        
        return true;
#else
        Json::Value root;
        root["result"] = result_;
        root["exitcode"] = exitcode_;
        Json::FastWriter fw;
        *out = fw.write(root);
        return true;
#endif
    }
    //反序列化
    bool Deserialize(const std::string &in)
    {
#ifdef Myself
        //"result exitcode"
        size_t pos = in.find(blank_space_sep);
        if(pos == std::string::npos)    return false;
        std::string part1 = in.substr(0, pos);
        std::string part2 = in.substr(pos+1);

        result_ = std::stoi(part1);
        exitcode_ = std::stoi(part2);
        
        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        result_ = root["result"].asInt();
        exitcode_ = root["exitcode"].asInt();
        return true;
#endif
    }

    void DebugPrint()
    {
        std::cout << "ret calc done, result: " << result_ << " exitcode: " << exitcode_ << std::endl;
    }

public:
    int result_;
    int exitcode_;
};

本章自定义的协议,就是属于OSI七层模型中的表示层

  • 35
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加法器+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值