【计算机网络学习之路】序列化,反序列化和初识协议

文章目录

  • 前言
  • [一. 序列化和反序列化](#一. 序列化和反序列化)
  • [二. 初识协议](#二. 初识协议)
  • 结束语

前言

本系列文章是计算机网络学习的笔记,欢迎大佬们阅读,纠错,分享相关知识。希望可以与你共同进步。

本篇博文讲解应用层的序列化和反序列化,还有见一下简单的应用层传输协议

一. 序列化和反序列化

在前篇TCP和UDP服务器编写时,业务只是简单的echo客户端发送的数据,但实际生活中,要传输的数据往往复杂 的多。使用结构体或类可以保存更多数据,但传输过程中,可能会遇到网络通信两端的操作系统不同 ,结构体/类的大小不同,内存对齐策略不一致等问题。所以网络传输十分不建议传输结构体或类。

这时,序列化和反序列化诞生了。

序列化通俗来说,就是将结构体/类转化为字符串;而反序列化就是将字符串转化为结构体

序列化最重要的作用:在传递和保存对象时,保证对象的完整性和可传递性等问题。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。

反序列化最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象

核心作用就是对象状态的保存和重建。(整个过程核心点就是字节流所保存的对象状态及描述信息)

1.自己实现

案例

假如我们现在要实现一个网络版本的计算器

我们把客户端发送的数据称为需求(Request),服务器返回的数据叫做响应(Responce)

此案例的需求有三个变量:数字x,数字y,操作数op

响应有两个变量:结果result,结果码code

需求和响应都需要有序列化和反序列化两个功能函数

代码如下:

使用分割符方便Request提取变量,分隔符------空格
1+1 =>(添加分隔符) 1 + 1

cpp 复制代码
#define SEP " " //分隔符
#define SEP_LEN strlen(SEP)
// 请求
class Request
{
public:
    Request() {}

    // 序列化 结构体=>字符串
    bool Serialize(std::string *outStr)
    {
        *outStr = "";
        std::string x_string = std::to_string(_x);
        std::string y_string = std::to_string(_y);
        //添加分隔符
        *outStr = x_string + SEP + _op + SEP + y_string;
        return true;
    }
    // 反序列化 字符串=>结构体
    bool Deserialize(const std::string &str)
    {
        std::vector<std::string> result;
        Util::StringSplit(str, SEP, &result);
        //必须提取出三个变量
        if (result.size() != 3)
            return false;
            
        _x = atoi(result[0]);
        _op = result[1][0];
        _y = atoi(result[2]);
        return true;
    }
public:
    int _x;
    int _y;
    char _op;
};

// 响应
class Responce
{
public:
    Responce() : _result(0), _code(0)
    {
    }
    // 序列化 结构体->字符串
    bool Serialize(std::string *outStr)
    {
        *outStr = "";
        std::string result_string = std::to_string(_result);
        std::string code_string = std::to_string(_code);
        *outStr = result_string + SEP + code_string;
        return true;
    }
	//反序列化 字符串->结构体
    bool Deserialize(const std::string &str)
    {
        std::vector<std::string> result;
        Util::StringSplit(str, SEP, &result);
        if (result.size() != 2)
            return false;

        _result = atoi(result[0]);
        _code = atoi(result[1]);
        return true;
    }
public:
    int _result;
    int _code;
};

2. JSON

序列化一般我们不自己操作,可以使用别的封装好的序列化,比如:JSON,ProtocolBuffer,FlatBuffer,DIMBIN

本篇文章介绍json的使用

JSON 的语法规则总结起来有:

  • 数组(Array)用方括号("[]")表示。
  • 对象(0bject)用大括号("{}")表示。
  • 名称/值对(name/value)组合成数组和对象。
  • 名称(name)置于双引号中,值(value)有字符串、数值、布尔值、null、对象和数组。
  • 并列的数据之间用逗号(",")分隔

序列化后可视效果也较好,比如:

cpp 复制代码
{
	"x":10,
	"y":22,
	"op":*
}

代码:

cpp 复制代码
// 请求
class Request
{
public:
    Request() {}

    // 结构体=>字符串
    bool Serialize(std::string *outStr)
    {
        // Value,一种万能对象,接收任意的kv类型
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["op"] = _op;
        // 序列化
        Json::StyledWriter writer;
        *outStr = writer.write(root);
        return true;
    }
    // 字符串=>结构体
    bool Deserialize(const std::string &str)
    {
        Json::Value root;
        // 反序列化
        Json::Reader reader;
        reader.parse(str, root);
		//提取变量
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _op = root["op"].asInt();
        return true;
    }
public:
    int _x;
    int _y;
    char _op;
};
// 响应
class Responce
{
public:
    Responce() : _result(0), _code(0)
    {}
    // 结构体->字符串
    bool Serialize(std::string *outStr)
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;

        // 序列化
        Json::StyledWriter writer;
        *outStr = writer.write(root);
        return true;
    }

    bool Deserialize(const std::string &str)
    {
        Json::Value root;
        //反序列化
        Json::Reader reader;
        reader.parse(str, root);
		//提取变量
        _result = root["result"].asInt();
        _code = root["code"].asInt();
        return true;
    }
public:
    int _result;
    int _code;
};

二. 初识协议

在网络通信中,客户端,服务器收发数据其实是如下这样的

我们调用的recv,send等接口,只是将我们定义的缓冲区的数据拷贝到TCP的缓冲区,或者将数据从TCP缓冲区拷贝到上层

TCP是面向字节流的,可以认为,数据是一个字节一个字节传输的

如果,客户端发送一个hello,recv接口会将发送缓冲区的数据一次性拷贝到上层,但可能此时通过网络传输,只传送了hel,服务器的接受缓冲区只有hel,此时recv并没有读取到完整报文,并且我们不知道什么时候读到了完整报文
协议就是为了解决这类问题而诞生的

基于本次网络计算机的案列,简单设计的协议比如,添加长度和\r\n报头

比如:"1 + 1" =>"5""\r\n""1 + 1""\r\n"

解析:通过找到第一个\r\n,获取有效载荷(1 + 1)的长度,再根据长度提取有效载荷

代码如下:

cpp 复制代码
#define HEADER_SEP "\r\n" // 报头分隔符
#define HEADER_SEP_LEN strlen(HEADER_SEP)
// 读取数据,并尝试提取一个完整报文
// 完整报文:"有效载荷长度""\r\n""有效载荷""\r\n"
//inbuffer保存所有读取的数据,package是一个完整报文,需要输出
int ReadPackage(int sock, std::string &inbuffer, std::string *package)
{
    // 读数据
    char buffer[1024];
    int n = recv(sock, buffer, sizeof(buffer) - 1, 0);
    if (n > 0)
        buffer[n] = '\0';
    else if (n == 0)//写端关闭
        return -1;
    else if (n < 0)//读取异常
        return -2;
    //将本次读取的数据保存
    inbuffer += buffer;
	//开始查找报头分隔符
    size_t start = 0;
    auto pos = inbuffer.find(HEADER_SEP, start);
    if (pos == std::string::npos)
        return 0; //第一个分隔符都没有,不是完整报文
	//提取长度
    std::string lenStr = inbuffer.substr(0, pos);
    int len = Util::toInt(lenStr); // 有效载荷的长度
    // 完整报文的长度
    int targetPackageLen = lenStr.size() + len + 2 * HEADER_SEP_LEN;
    if (inbuffer.size() < targetPackageLen)
        return 0; //长度不足完整报文
    // 提取一个完整报文,并输出
    *package = inbuffer.substr(0, targetPackageLen);
    inbuffer.erase(0, targetPackageLen);

    return len;
}

结束语

本篇博客到此结束,感谢看到此处。

欢迎大家纠错和补充

如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。

相关推荐
汤米粥2 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
行然梦实8 分钟前
学习日记_20241110_聚类方法(K-Means)
学习·kmeans·聚类
Hacker_Nightrain10 分钟前
网络安全CTF比赛规则
网络·安全·web安全
马船长14 分钟前
制作图片木马
学习
秀儿还能再秀25 分钟前
机器学习——简单线性回归、逻辑回归
笔记·python·学习·机器学习
WCF向光而行30 分钟前
Getting accurate time estimates from your tea(从您的团队获得准确的时间估计)
笔记·学习
向懒羊羊学习的大猫1 小时前
【专题】计算机网络之网络层
计算机网络
网络安全指导员1 小时前
恶意PDF文档分析记录
网络·安全·web安全·pdf
wang09071 小时前
工作和学习遇到的技术问题
学习
fakaifa2 小时前
CRMEB Pro版v3.1源码全开源+PC端+Uniapp前端+搭建教程
前端·小程序·uni-app·php·源码下载