当前位置: 首页 > news >正文

Linux网络之序列化和反序列化

目录

序列化和反序列化


上期我们学习了基于TCP的socket套接字编程接口,并实现了一个TCP网络小程序,本期我们将在此基础上进一步延伸学习,实现一个网络版简单计算器。

序列化和反序列化

在生活中肯定有这样一个情景。

上图大家肯定不陌生,上图是群聊机器人发送的一条消息记录,通过这个记录我们可以看到机器人的头像,机器人的昵称,机器人发送的消息内容,机器人发送消息的时间。我上述数据统称为一次发送产生的数据。使用qq的人是很多的,所以上述数据也是很多,为了方便进行管理数据,操作系统会将一次发送的数据进行管理,这就要用到六子真言,“先描述,后组织”。所以操作系统会定义一个结构体,结构体中会定义一些成员表述上述的数据,我们称之为结构化数据。

 图示如下。

总的来说。

序列化就是将结构化的数据转为字符串数据,反序列化就是将字符串数据转为结构化数据。

序列化和反序列化的优点。

  1. 为了应用层网络通信的便捷,因为在网络通信中,字符串更易于传输。
  2.  为了方便上层端口使用结构化数据内部的成员,将应用和网络进行了解耦。比如发送端发送什么类型的数据,接收端就接收什么类型的数据,双方都不用关心结构化数据在网络中是怎么样传送的。

情景一:制造一个网络计算器小程序(不使用序列化和反序列化)。

Sock.hpp

#include <iostream>
#include <sys/types.h>
#include <cerrno>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>using namespace std;class Socket
{
public:// 1.创建套接字static int Sock(){int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0){cout << "socket create error" << errno << endl;exit(1);}return fd;}// 2.绑定IP和端口号static void Bind(int sock, uint16_t port){struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){cout << "bind error" << errno << endl;exit(2);}}// 3.监听连接static void Listen(int sock){if (listen(sock, 5) < 0){cout << "listen error" << errno << endl;exit(3);}}// 4.获取连接static int Accept(int sock){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(sock, (struct sockaddr *)&peer, &len);if (newfd < 0){cout << "accept error" << errno << endl;exit(4);}return newfd;}// 5.发送链接static void Connect(int fd, string ip, uint16_t port){struct sockaddr_in sever;sever.sin_family = AF_INET;sever.sin_addr.s_addr = inet_addr(ip.c_str());sever.sin_port=htons(port);if (connect(fd, (struct sockaddr *)&sever, sizeof(sever)) == 0){cout << "connect success!" << endl;}else{cout << "connect error" << endl;exit(5);}}
};

Sock.hpp封装了socket套接字的相关接口,方便后续进行使用。

Protocol.hpp

#pragma once
#include <iostream>
#include <string>
using namespace std;// 客户端发送请求
typedef struct request
{int x;int y;char op;} request_t;// 服务端做出响应(回应)
typedef struct response
{int code;   // code表示sever端计算的结果是否正确int result; // 表示计算的结果
} response_t;

可以理解为这是我们定制的一个协议,也就是结构化的数据。客户端发送request请求,服务器端发送response回应。 

 CalClient.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Protocol.hpp"
#include "Sock.hpp"
#include <unistd.h>
#include <string.h>using namespace std;int main(int args, char *argv[])
{// 1.创建套接字文件int fd = Socket::Sock();// 2.发送链接Socket::Connect(fd, argv[1], atoi(argv[2]));// 3.发送请求request_t req;memset(&req, 0, sizeof(req));cout << "请输入x: ";cin >> req.x;cout << "请输入y: ";cin >> req.y;cout << "请输入op: ";cin >> req.op;write(fd, &req, sizeof(req));// 写都是输入型参数,读都是输出型参数// 4.获取回应response_t res;memset(&res, 0, sizeof(res));size_t s = read(fd, &res, sizeof(res));if (s == sizeof(res)){cout << "获取到了回应: " << res.code << " " << res.result << endl;}return 0;
}

client客户端,发送要计算的数据,将其打包成为request请求发送给sever服务器端,并接收sever服务器端的回应。 

要注意的是一般情况下write接口中传入输入型参数,read中传入输出型参数。

CalSever.cc

#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"
#include <pthread.h>
#include <unistd.h>
#include <string.h>
using namespace std;// 5.提供服务
void *handle(void *args)
{int newfd = *(int *)args;delete (int *)args;// 获取请求request_t req;size_t s = read(newfd, &req, sizeof(req));if (s == sizeof(req)){// 处理请求response_t res = {0, 0};switch (req.op){case '+':res.result = req.x + req.y;break;case '-':res.result = req.x - req.y;break;case '*':res.result = req.x * req.y;break;case '/':if (req.y == 0){res.code = 1;cout << "除零错误" << endl;break;} // 除零错误res.result = req.x / req.y;break;case '%':if (req.y == 0){res.code = 2;cout << "除零错误" << endl;break;}// 除零错误res.result = req.x % req.y;break;default:res.code = 3; // 操作符非法cout << "请输入合法的操作符 " << endl;break;}cout << "request: " << req.x << req.op << req.y << endl;// 做出回应write(newfd, &res, sizeof(res));}close(newfd);
}int main(int args, char *argv[])
{// 1.创建套接字文件int listenfd = Socket::Sock();// 2.绑定IP和端口号Socket::Bind(listenfd, atoi(argv[1]));// 3.进行监听Socket::Listen(listenfd);for (;;){// 4.获取连接int fd = Socket::Accept(listenfd);int *newfd = new int(fd);pthread_t tid;pthread_create(&tid, nullptr, handle, (void *)newfd);}return 0;
}

sever服务器端获取client端发送的request请求,并向client客户端做出response回应,将最终的计算结果返回。 

 运行结果如下。

运行结果符合预期。

情景二:制造一个网络计算器小程序(使用序列化和反序列化)。

我们使用json第三方库进行序列化和反序列化。

Protocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;// 客户端发送请求
typedef struct request
{int x;int y;char op;} request_t;// 服务端做出响应(回应)
typedef struct response
{int code;   // code表示sever端计算的结果是否正确int result; // 表示计算的结果
} response_t;//  request_t -> string
string SerializeRequest(const request_t &req)
{// 序列化Json::Value root; // 可以承装任何对象, json是一种kv式的序列化方案root["datax"] = req.x;root["datay"] = req.y;root["operator"] = req.op;Json::FastWriter writer;string json_string = writer.write(root);return json_string;
}// string -> request_t
void DeserializeRequest(const string &json_string, request_t &out)
{// 反序列化Json::Reader reader;Json::Value root;reader.parse(json_string, root);out.x = root["datax"].asInt();out.y = root["datay"].asInt();out.op = (char)root["operator"].asInt();
}string SerializeResponse(const response_t &resp)
{//序列化Json::Value root;root["code"] = resp.code;root["result"] = resp.result;Json::FastWriter writer;string res = writer.write(root);return res;
}void DeserializeResponse(const string &json_string, response_t &out)
{// 反序列化Json::Reader reader;Json::Value root;reader.parse(json_string, root);out.code = root["code"].asInt();out.result = root["result"].asInt();
}

序列化中有Value类对象和Writer类对象,Value类对象是一个万能对象是一个kv结构,可以接收任何对象。Writer类对象进行结构化数据转为字符串。

反序列化中有Value类对象和Reader类对象,Value对象和序列化一样,Reader类对象实现字符串转为结构化对象。

CalClient.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Protocol.hpp"
#include "Sock.hpp"
#include <unistd.h>
#include <string.h>using namespace std;int main(int args, char *argv[])
{// 1.创建套接字文件int fd = Socket::Sock();// 2.发送链接Socket::Connect(fd, argv[1], atoi(argv[2]));// 3.发送请求request_t req;memset(&req, 0, sizeof(req));cout << "请输入x: ";cin >> req.x;cout << "请输入y: ";cin >> req.y;cout << "请输入op: ";cin >> req.op;// 对req进行序列化,然后写入string json_string = SerializeRequest(req);write(fd, json_string.c_str(), json_string.size());// 写都是输入型参数,读都是输出型参数// 4.获取回应// 对获取到的回应进行反序列化char buffer[1024];size_t s = read(fd, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;response_t res;memset(&res, 0, sizeof(res));string json_string1 = buffer;DeserializeResponse(json_string1, res);cout << "获取到了回应: " << res.code << " " << res.result << endl;}return 0;
}

client端先对发送的结构化请求序列化成字符串,然后进行序列化字符串的发送,最终对从sever端收到的序列化字符串进行反序列化成结构化响应。 

CalSever.cc

#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"
#include <pthread.h>
#include <unistd.h>
#include <string.h>
using namespace std;// 5.提供服务
void *handle(void *args)
{int newfd = *(int *)args;delete (int *)args;// 获取请求,对接受到的request请求进行反序列化char buffer[1024];request_t req;memset(&req, 0, sizeof(req));size_t s = read(newfd, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;string jsonstring = buffer;DeserializeRequest(jsonstring, req);// 处理请求response_t res = {0, 0};switch (req.op){case '+':res.result = req.x + req.y;break;case '-':res.result = req.x - req.y;break;case '*':res.result = req.x * req.y;break;case '/':if (req.y == 0){res.code = 1;cout << "除零错误" << endl;break;} // 除零错误res.result = req.x / req.y;break;case '%':if (req.y == 0){res.code = 2;cout << "除零错误" << endl;break;}// 除零错误res.result = req.x % req.y;break;default:res.code = 3; // 操作符非法cout << "请输入合法的操作符 " << endl;break;}cout << "request: " << req.x << req.op << req.y << endl;// 做出回应// 对回应进行序列化string json_string1 = SerializeResponse(res);write(newfd, json_string1.c_str(), json_string1.size());}close(newfd);
}int main(int args, char *argv[])
{// 1.创建套接字文件int listenfd = Socket::Sock();// 2.绑定IP和端口号Socket::Bind(listenfd, atoi(argv[1]));// 3.进行监听Socket::Listen(listenfd);for (;;){// 4.获取连接int fd = Socket::Accept(listenfd);int *newfd = new int(fd);pthread_t tid;pthread_create(&tid, nullptr, handle, (void *)newfd);}return 0;
}

sever端先对从client端收到的序列化字符串数据进行反序列化成结构化请求, 然后最终将结构化响应序列化成字符串发送给client端。

运行结果如下。

运行结果符合预期。 

以上便是本期的序列化和反序列化所有内容,需要注意的是,不论是我们今天写的简单计算器小程序,和前两期写的基于TCP和UDP的网络小程序都其实是处于应用层的。

本期内容到此结束^_^ 

http://www.lryc.cn/news/527406.html

相关文章:

  • linux设置mysql远程连接
  • react-native网络调试工具Reactotron保姆级教程
  • erase() 【删数函数】的使用
  • 性能测试丨内存火焰图 Flame Graphs
  • AIGC的企业级解决方案架构及成本效益分析
  • Linux 入门 常用指令 详细版
  • 【R语言】流程控制
  • 猿人学第一题 js混淆源码乱码
  • 计算机组成原理(2)王道学习笔记
  • 【AI日记】25.01.26
  • 三. Redis 基本指令(Redis 快速入门-03)
  • 设计模式的艺术-代理模式
  • C#新语法
  • 微信小程序压缩图片
  • 通义灵码插件保姆级教学-IDEA(安装及使用)
  • windows下本地部署安装hadoop+scala+spark-【不需要虚拟机】
  • 倍频增量式编码器--角度插值法输出A,B(Aangular Interpolation)
  • LSM对于特殊数据的优化手段
  • 83,【7】BUUCTF WEB [MRCTF2020]你传你[特殊字符]呢
  • Go语言入门指南(二): 数据类型
  • 2025.1.26机器学习笔记:C-RNN-GAN文献阅读
  • FAST-DDS and ROS2 RQT connect
  • GESP2024年3月认证C++六级( 第三部分编程题(2)好斗的牛)
  • 记一次STM32编译生成BIN文件过大的问题(基于STM32CubeIDE)
  • 【暴力洗盘】的实战技术解读-北玻股份和三变科技
  • Day42:列表的组合
  • mantisbt添加修改用户密码
  • DroneXtract:一款针对无人机的网络安全数字取证工具
  • 简单树形菜单
  • Windows 靶机常见服务、端口及枚举工具与方法全解析:SMB、LDAP、NFS、RDP、WinRM、DNS