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

序列化和反序列的学习

一:重谈协议

1 理解网络协议,可以把它想象成网络世界里的“交通规则”和“通用语言”。它是一套预先定义好的规则、标准和约定,使得不同设备、不同系统之间能够顺利地进行通信和数据交换。

我们从TCP协议上面理解一下,首先TCP服务是全双工的,怎么理解是全双工的呢,就是如果使用TCP服务,在客户端或者是服务端,都有两个缓冲区---一个是发送缓冲区,一个是接收缓冲区,我们在TCP接收发送文件不是用的是read,wirte吗,当要接收数据时,就从接收缓冲区里读数据交给用户空间,也就是从内核到用户,从而发送数据时,write函数就把用户空间的数据拷贝到发送缓冲区里,也就是从用户层到内核层。刷新什么的由TCP自己决定,全全交给了OS。所以TCP又叫做传输控制协议,里面有各种报头来确认数据传输的正确性。

这个确认数据的完整性或者正确性什么的,UDP和TCP就有了各自的特点,UDP是面向报文的,相当于快递,他就会要求传递报文必须是完整的,TCP是相当于自来水接水,所以呢他就有可能传过来的数据是一段一段的,我们在报头里就会有一些报文的长度啦,还有什么分隔符啦,分开报头和有效载荷了什么的。而这些要求就是我们今天要说的序列化和反序列化。

二:序列化和反序列化

我们所说的协议就是一种约定,客户端和服务端用的是同一套协议,举个例子,假设客户端发送请求,这个报文里面呢设置报文长度,有效载荷传过来的数据,性别啦,年龄了等等。而服务端接收到的数据就是已经序列化好的,就是把这些数据按照一定顺序排列好了给你发送过来了,接着就要把它反序列化,就是把这些存的东西一个一个的对应的从字符串里拿出来。说简单点了就是把字符串里的内容存放在对应的结构体里。协议是一种 "约定". socket api 的接口。

实现序列化和反序列化大体上有两种方法:

第一种就是自定义的方式,我自己规定传过来的数据 结构是什么样,从而让你读取到一个序列化好的,你可以通过反序列化拿到对应的数据,然后再返回一个序列化的结果,服务端接收到数据,也能通过反序列化拿到对应的结果

第二种方式就是用现成的 JSON 序列化 (Serialize),将内存中的数据结构(如对象、数组、字符串、数字、布尔值等)转换成符合 JSON 格式的字符串的过程。JSON 反序列(Deserialize),将一个符合 JSON 格式的字符串 解析并转换回内存中的数据结构(如对象、数组等)的过程。

三:使用序列化和反序列化实现一个简单的网络计算器

客户端和服务端没什么太大的变化。我们今天主要写的就是一个序列化和反序列化

我们直接用一个JSON的,比较方便,但是你前提的安装一下,

Ubuntu的:sudo yum install -y jsoncpp-devl

Centos的:sudo apt install -y jsoncpp-devl

1.我们需要构建两个类,一个请求,一个应答

请求在发送之前,客户端要进行序列化

请求在发送之后,服务端要进行反序列化

应答在发送之前,服务端要进行序列化

应答在发送之后,客户端要进行反序列化

我们实现的计算器比较简单 请求就是 一个数字 x 一个数字 再加一个符号,例子 30 + 20 

    int _x;int _y;char _oper;

然后把他进行序列化

     bool Serialize(std::string& out_string){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::StreamWriterBuilder wb;std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());std::stringstream ss;w->write(root, &ss);out_string = ss.str();return true;}

这个out_string ,是作为输出型参数,把序列化的结果存进out_string

反序列化

 bool deserialize(std::string& in_string ){Json::Value root;Json::Reader reader;bool parsingSuccessful = reader.parse(in_string, root);if (!parsingSuccessful){return false;}_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;}

in_string 就是作为一个输入型参数,把对应的结果存给对方就可以

应答的话就是一个结果,还有一个结果描述符,0设为正常,1设置为 / 或%  0了等等,这些都可以自己约定。

private:int _result;int _code;

序列化和反序列化道理一致,我们就不再分开展示了。

完整的代码Protocol.hpp

class Response
{public:Response() : _result(0), _code(0){}Response(int result, int code) : _result(result), _code(code){}~Response(){}bool Serialize(std::string& out_string){Json::Value root;root["result"] = _result;root["code"] = _code;Json::StreamWriterBuilder wg;std::unique_ptr<Json::StreamWriter>  w(wg.newStreamWriter());std::stringstream ss;w->write(root,&ss); //注意这里一个取地址,一个不取地址out_string = ss.str();return true;}bool deserialize(std::string& in_string){Json::Value root;Json::Reader reader;bool parsesucess = reader.parse(in_string,root);if(!parsesucess){std::cout<<"parseucess false"<<std::endl;return false;}_result = root["result"].asInt();_code = root["code"].asInt();return true;}int Result() const { return _result; }int Code() const { return _code; }void SetResult(int res) { _result = res;}void SetCode(int c) {_code = c;}private:int _result;int _code;
};

2.客户端

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include "Log.hpp"
#include "Common.hpp"
#include "Protocol.hpp"using namespace LogModule;int main(int argc , char* argv[])
{if(argc != 3 ){std::cout<<"Clinet need two arguments"<<std::endl;return 1;}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){LOG(LogLevel::ERROR) << "create scokfd false";}struct sockaddr_in peer;memset(&peer,0,sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = ::htons(port);peer.sin_addr.s_addr = ::inet_addr(ip.c_str());//客户端不用bind 绑定 ,tcp是面向连接的,connect 会自动绑定int n = ::connect(sockfd,CONV(&peer),sizeof(peer));if(n<0){LOG(LogLevel::ERROR)<<"connect false";return  1;}std::string message;while(true){int x, y;char oper;std::cout << "input x: ";std::cin >> x;std::cout << "input y: ";std::cin >> y;std::cout << "input oper: ";std::cin >> oper;Request req(x,y,oper);//序列化std::string message;req.Serialize(message);Encode(message);char inbuffer[1024];// n = ::write(sockfd, message.c_str(), message.size());n = ::send(sockfd,message.c_str(),message.size(),0);if(n > 0){//int m = ::read(sockfd, inbuffer, sizeof(inbuffer));int m =::recv(sockfd,inbuffer,sizeof(inbuffer),0);if(m > 0){inbuffer[m] = 0;std::string tmp = inbuffer;std::string content;Decode(tmp,&content);Response resp;resp.deserialize(content);std::cout<<resp.Result()<<resp.Code()<<std::endl;}elsebreak;}else break;}::close(sockfd);return 0;
}

3.服务端

TCPServer.hpp

#pragma once
#include <iostream>
#include "Common.hpp"
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
#include <functional>
#include "Log.hpp"
#include "Internet.hpp"
#include "ThreadPool.hpp"#define BackWait 8using namespace LogModule;
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
using handler_t = std::function<std::string(std::string& tmp)>;uint16_t defaultport = 8080;
std::string defaultip = "127.0.0.1";class TcpServer
{struct Thread {int sockfd;TcpServer* self;};
public:TcpServer(handler_t hander, uint16_t port = defaultport, std::string ip = defaultip): _port(port), _ip(ip), _isrunning(false), _listensockfd(-1),_hander(hander){}~TcpServer(){}void InitServer(){_listensockfd = ::socket(AF_INET,SOCK_STREAM,0);if(_listensockfd < 0){LOG(LogLevel::FATAL) << "create sockfd false";Die(1);}LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = ::htons(_port);local.sin_addr.s_addr = INADDR_ANY;// //面向连接的,还要等待随时被连接// //所以要设置为监听模式// 2. bindint n = ::bind(_listensockfd,CONV(&local),sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success, sockfd is : " << _listensockfd;// 3. cs,tcp是面向连接的,就要求tcp随时随地等待被连接// tcp 需要将socket设置成为监听状态int m = ::listen(_listensockfd,8); if(m < 0 ){LOG(LogLevel::FATAL) <<"监听失败";Die(LISTEN_ERR);}LOG(LogLevel::FATAL) <<"监听成功";}void HandlerRequest(int sockfd){char buffer[1024];std::string package;while(true){//接受消息ssize_t n = ::read(sockfd,buffer,sizeof(buffer)-1);if(n>0){buffer[n] = 0;LOG(LogLevel::DEBUG)<<"接受到消息了#:"<<buffer;package += buffer;std::string cmd_result = _hander(package);::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0); // 写入也是不完善}else if(n == 0){LOG(LogLevel::INFO) << "client quit: " << sockfd;break;}elsebreak;}}static void* ThreadHandler(void* args){//用线程也要等待回收(join) 必须等待回收的话就会阻塞,所以让线程自己结束释放资源pthread_detach(pthread_self());Thread* tmp = (Thread*)args;tmp->self->HandlerRequest(tmp->sockfd);return nullptr;}void Start(){_isrunning = true;while(true){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = ::accept(_listensockfd,CONV(&peer),&len);//建立连接之后,这个对应的文件描述符才负责传信(接待)if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error: " << strerror(errno);continue;}LOG(LogLevel::INFO) << "accept success, sockfd is : " << sockfd;InetAddr inetaddr(peer);LOG(LogLevel::INFO) << "client info: " << inetaddr.Addr();//version 0//HandlerRequest(sockfd);// version -1 多线程版本// pid_t pid = fork();//::signal(SIGCHLD,SIG_IGN);// if(pid == 0)// {//     //子进程再创建孙子进程,子进程直接退掉,由系统进程1 来回收管理孙子进程//     //子进程和父进程 各有一张文件描述符表 文件都是通过引用计数进行管理的//     //就像管道一样//     ::close(_listensockfd);//     if(fork() > 0)//     {//         exit(0);//     }//     HandlerRequest(sockfd);//     exit(0);// }// 给出建议父进程不要关闭文件描述符,这个设计叫权责分明// 现在语法执行没错,如果修改内容容易有错// ::close(sockfd);// pid_t waitid = ::waitpid(pid,nullptr,0);// if(waitid<0)// {//     LOG(LogLevel::ERROR)<<"回收父进程失败";// }//version 2 用多线程   // pthread_t pid;// Thread* data = new Thread;// data->self = this;// data->sockfd = sockfd;// pthread_create(&pid,nullptr,ThreadHandler,data);//version 3 线程池task_t f = std::bind(&TcpServer::HandlerRequest,this,sockfd);ThreadPool<task_t>::getInstance()->Equeue([&sockfd,this](){this->HandlerRequest(sockfd);});}}private:int _listensockfd; //这个文件描述符 只负责监听(也就是送客人,不负责招待)uint16_t _port;std::string _ip;bool _isrunning;handler_t _hander;
};

TCPServer.cc

#include"TcpServer.hpp"
#include<memory>
#include"Log.hpp"
#include"Calculator.hpp"
#include"Protocol.hpp"
#include<functional>
#include"Daemon.hpp"using namespace LogModule;using Cal_t = std::function<Response(const Request& req)>;// using cal_fun = std::function<Response(const Request &req)>;// // package一定会有完整的报文吗??不一定把
// // 不完整->继续读
// // 完整-> 提取 -> 反序列化 -> Request -> 计算模块,进行处理class Parse
{public:Parse(Cal_t cal):_cal(cal){}std::string Entry(std::string& package){std::string message;std::string resptr;while(Decode(package,&message)){LOG(LogLevel::DEBUG) << "Content: \n" << message;if(message.empty())break;//反序列化Request req;if(!req.deserialize(message))break;//计算std::string tmp;Response resp = _cal(req);resp.Serialize(tmp);//添加长度字段Encode(tmp);//拼接应答,这样的话有多少个需求都会处理,最后统一返回resptr+=tmp;}return resptr;}private:Cal_t _cal;
};int main()
{//ENABLE_FILE_LOG();//Daemon(false,false);Cal cal;Parse parse([&cal](const Request& req){return cal.entry(req);});std::shared_ptr<TcpServer> tserver = std::make_shared<TcpServer>([&parse](std::string &package){return parse.Entry(package);});tserver->InitServer();tserver->Start();return 0;
}

4.计算端

#include<iostream>
#include<string>
#include<memory>
#include"Protocol.hpp"class Cal
{public:Cal(){}Response entry(const Request& req){Response resp;switch (req.Oper()){case '+':resp.SetResult(req.X() + req.Y());break;case '-':resp.SetResult(req.X() - req.Y());break;case '*':resp.SetResult(req.X() * req.Y());break;case '/':{if (req.Y() == 0){resp.SetCode(1); // 1 就是除0}else{resp.SetResult(req.X() / req.Y());}}break;case '%':{if (req.Y() == 0){resp.SetCode(2); // 2 就是mod 0}else{resp.SetResult(req.X() % req.Y());}}break;default:resp.SetCode(3);break;}return resp;}private:
};

5.Log.hpp

#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"
// + 日志的可变部分(<< "hello world"namespace LogModule
{using namespace Lock;std::string CurrentTime(){time_t time_stamp=::time(nullptr);struct tm curr;localtime_r(&time_stamp, &curr);char buffer[1024];snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon + 1,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return buffer;}enum class LogLevel{DEBUG=1,INFO,WARNING,ERROR,FATAL};std::string Level2String(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNING:return "WARNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "None";}}class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string & message) = 0 ;};class ControlStrategy : public LogStrategy{public:ControlStrategy(){}~ControlStrategy(){}void SyncLog(const std::string& message){LockGuard lockguard(_mut);std::cout<<message<<std::endl;}private:Mutex _mut;};/*std::filesystem::exists(_logpath)*/const std::string defaultlogpath = "./log/";const std::string defaultlogname = "log.txt";class FileStrategy : public LogStrategy{public:FileStrategy(const std::string& logpath=defaultlogpath,const std::string& logname = defaultlogname):_logpath(logpath),_logname(logname){LockGuard lockguard(_mut);if(std::filesystem::exists(_logpath)){return;    }try{std::filesystem::create_directories(_logpath);}catch(std::filesystem::filesystem_error& t){std::cerr<<t.what()<<std::endl;}}~FileStrategy(){}void SyncLog(const std::string& message){LockGuard lockgurad(_mut);std::string log = _logpath+_logname;std::ofstream out(log,std::ios::app);if(!out.is_open()){std::cout<<"打开失败"<<std::endl;return;}out<<message<<"\n";out.close();}private:Mutex _mut;std::string _logpath;std::string _logname;};class Logger{public:Logger(){_strategy=std::make_shared<ControlStrategy>(); }void EnableControl(){_strategy=std::make_shared<ControlStrategy>(); }void EnableFile(){_strategy=std::make_shared<FileStrategy>();}~Logger(){}//一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16]class LogMessage{public:LogMessage(LogLevel level,const std::string &filename, int line,Logger& logger):_time(CurrentTime()),_level(level),_pid(::getpid()),_name(filename),_line(line),_logger(logger){std::stringstream ssbuffer;ssbuffer << "[" << _time << "] "<< "[" <<Level2String(_level) << "] "<< "[" << _pid << "] "<< "[" << _name << "] "<< "[" << _line << "] - ";_loginfo = ssbuffer.str();//std::cout<<_loginfo<<std::endl;         }template<class T>LogMessage &operator<<(const T& info){std::stringstream ss;ss<<info;_loginfo += ss.str();return *this;}~LogMessage(){if(_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}private:std::string _time;LogLevel _level;;pid_t _pid;std::string _name;int _line;std::string _loginfo;Logger &_logger;};LogMessage operator()(LogLevel level, const std::string &filename, int line){return LogMessage(level, filename, line,*this);}private:std::shared_ptr<LogStrategy> _strategy;};Logger logger;
#define LOG(level) logger(level,__FILE__,__LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableControl()
#define ENABLE_FILE_LOG() logger.EnableFile()}

四 重谈七层协议

五层协议: 物理层  数据链路层 网络层 应用层  传输层 

七层协议:物理层  数据链路层  网络层 应用层  传输层  会话层 表示层

五层模型是将 OSI 七层模型中的会话层、表示层和应用层这三层合并成了一个单一的应用层。

为什么这样合并?
实际应用:在实际的 TCP/IP 协议栈中,会话管理(如建立、管理和终止会话)和数据表示(如数据加密、压缩、格式转换)的功能通常由应用程序本身或应用层协(如 HTTP、TLS/SSL)来实现,而不是由一个独立的、通用的协议层来处理。也就是说应用层实现大部分,所以为了不冲突,就由用户层自我决定。

http://www.xdnf.cn/news/1387387.html

相关文章:

  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(五)
  • Word - Word 查找文本中的特定内容
  • Redis vs Elasticsearch:核心区别深度解析
  • c++二叉搜索树
  • 在Linux的环境下安装GitLab(保姆级别)
  • Ubuntu下的压缩及解压缩
  • Llama-index学习文档
  • AI驱动万物智联:IOTE 2025深圳展呈现无线通信×智能传感×AI主控技术融合
  • 【Python办公】CSV按列去重工具
  • LangChain实战(三):深入理解Model I/O - Prompts模板
  • 聊聊Prompt Engineering (提示词工程)
  • Rust Web框架Axum学习指南之响应和异常封装
  • websocket建立连接过程
  • AI供应链优化+AI门店排班:蜜雪冰城降本20%、瑞幸提效的AI商业落地实战
  • 港科大开放世界长时域具身导航!LOVON:足式机器人开放词汇目标导航
  • LeetCode Hot 100 Python (1~10)
  • 1 分钟 Maya 动画渲染要多久?5 天还是 5 小时
  • linux系统学习(15.启动管理)
  • 第一百零二章:AI的“未来电影制片厂CEO”:多模态系统落地项目实战(完整 AI 视频创作平台)
  • 极飞科技AI智慧农业实践:3000亩棉田2人管理+产量提15%,精准灌溉与老农操作门槛引讨论
  • 组件的生命周期:`useEffect` 的威力与副作用处理
  • 随机森林的 “Bootstrap 采样” 与 “特征随机选择”:如何避免过拟合?(附分类 / 回归任务实战)
  • 华为云CCE的Request和Limit
  • Ethercat主从站移植时的问题记录(二)—无法进入OP状态且从站PDI中断不触发
  • 什么是Jmeter? Jmeter工作原理是什么?
  • linux上安装methylkit -- 安全下车版 (正经版: Linux环境下安装methylKit的实践与避坑指南)
  • springboot java开发的rocketmq 顺序消息保证
  • CAN总线(Controller Area Network Bus)控制器局域网总线(二)
  • 无人机图传模块原理及作用——开启飞行视野的关键技术
  • 第二阶段WinForm-9:委托复习