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

应⽤层⾃定义协议与序列化

目录

1.序列化和反序列化

2.⽹络版计算器实现

2.1 Socket封装

2.2 序列化和反序列化

2.2.1 Jsoncpp

2.2.2 序列化

2.2.3 反序列化

2.3 定制协议

2.4 任务类

2.5 服务端实现

2.6 客户端实现


1.序列化和反序列化

对于网络双方通信,发送方经过应用层,传输层,网络层,数据链路层,物理层这五层才能发送消息,对于一段消息,可能有多个字段,例如:姓名,性别,年龄等,之后OS会对这些多个字段进行序列化,也就是把这些多个字段进行合并成一个字段,对于接受方就要进行反序列化,把一个字段的信息转化为原本多个字段的信息

在TCP协议中,利用解read、write这种文件操作的接口进行网络通信传输,本质上是拷贝,在TCP协议传输层中有两个缓冲区,一种是发送缓冲区,一种是接受缓冲区,当使用read、write这种文件操作的接口时,就会把信息拷贝到缓冲区,之后实际数据什么时候发,发多少,出错了怎么办,由TCP控制,所以TCP叫做传输控制协议,因此在任何⼀台主机上,TCP连接既有发送缓冲区,⼜有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双⼯

由于TCP中实际数据什么时候发,发多少出错了怎么办,由TCP控制,而在TCP中要读到完整的信息,也是用接收方自己确定,而这种就是TCP面向字节流的特性

2.⽹络版计算器实现

2.1 Socket封装

首先可以对套接字(Socket)进行封装

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "inerAddr.hpp"using namespace LogModuls;
namespace SocketModuls
{const static int gbacklog = 16;// 模版方法模式// 基类socket, 大部分方法,都是纯虚方法class Socket{public:virtual ~Socket() {}virtual void SocketDrDie()=0;//创建套接字virtual void Bind(uint16_t port)=0;//绑定端口virtual void ListenDiDie(int backlog)=0;//监听套接字virtual std::shared_ptr<Socket> Accept(inerAddr *addr)=0;//接受连接virtual void Close()=0;//关闭套接字virtual int Recv(std::string *out) = 0;//读取数据virtual int Send(const std::string &message) = 0;//发送数据virtual int Connect(const std::string &ip,uint16_t port) = 0;//连接服务器public://服务端构建套接字void BuildTcpServerSocketMethod(uint16_t port, int backlog = gbacklog){SocketDrDie();Bind(port);ListenDiDie(backlog);}//客户端构建套接字void BuildTcpClientSocketMethod(){SocketDrDie();}};const static int defaultfd=-1;class TcpSocket : public Socket{public:TcpSocket():_sockfd(defaultfd){}TcpSocket(int fd):_sockfd(fd){}virtual ~TcpSocket() {}virtual void SocketDrDie() override//创建套接字{_sockfd=::socket(AF_INET,SOCK_STREAM,0);if(_sockfd<0){LOG(LogLevel::ERROR)<<"create socket error";exit(SOCKET_ERR);}LOG(LogLevel::INFO)<<"create socket success";}virtual void Bind(uint16_t port) override//绑定端口{inerAddr addr(port);int n=::bind(_sockfd,addr.NetAddrPtr(),addr.NetAddrLen());if(n<0){LOG(LogLevel::ERROR)<<"bind socket error";exit(BIND_ERR);}LOG(LogLevel::INFO)<<"bind socket success";}virtual void ListenDiDie(int backlog) override//监听套接字{int n=::listen(_sockfd,backlog);if(n<0){LOG(LogLevel::ERROR)<<"listen socket error";exit(LISTEN_ERR);}LOG(LogLevel::INFO)<<"listen socket success";}//接受连接std::shared_ptr<Socket> Accept(inerAddr *addr) override//addr为输出参数{struct sockaddr_in client_addr;socklen_t client_addr_len=sizeof(client_addr);int client_fd=::accept(_sockfd,(struct sockaddr*)&client_addr,&client_addr_len);if(client_fd<0){LOG(LogLevel::ERROR)<<"accept socket error";exit(ACCEPT_ERR);}addr->SetAddr(client_addr);return std::make_shared<TcpSocket>(client_fd);}//读取数据int Recv(std::string *out)override{char buf[1024];ssize_t n=::recv(_sockfd,buf,sizeof(buf)-1,0);//相当于readif(n>0){buf[n]=0;*out+=buf;}return n;}//发送数据int Send(const std::string &message)override{return send(_sockfd,message.c_str(),message.size(),0);}void Close()//关闭套接字{if(_sockfd>0)::close(_sockfd);}int Connect(const std::string &ip,uint16_t port) override//连接服务器{inerAddr addr(ip,port);return ::connect(_sockfd,addr.NetAddrPtr(),addr.NetAddrLen());  }private:int _sockfd;};
}

2.2 序列化和反序列化

在上文了解到,无论对于客户端还是服务端都需要进行序列化和反序列化,此时可以利用Jsoncpp来进行

2.2.1 Jsoncpp

Jsoncpp 是⼀个⽤于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,⼴泛⽤于各种需要处理 JSON 数据的 C++ 项⽬中。
特性
1. 简单易⽤:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
2. ⾼性能:Jsoncpp 的性能经过优化,能够⾼效地处理⼤量 JSON 数据。
3. 全⾯⽀持:⽀持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null。
4. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,⽅便开发者调试。
安装
ubuntusudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

2.2.2 序列化

序列化指的是将数据结构或对象转换为⼀种格式,以便在⽹络上传输或存储到⽂件中。Jsoncpp提供了多种⽅式进⾏序列化:
1.使⽤ Json::Value toStyledString ⽅法:
优点:将 Json::Value 对象直接转换为格式化的JSON字符串。
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";std::string s = root.toStyledString();std::cout << s << std::endl;return 0;
}
转换后的格式如下
{"name" : "joe","sex" : "男"
}
2.使⽤ Json::StreamWriter
优点:提供了更多的定制选项,如缩进、换⾏符等。
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>intmain()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder; // StreamWriter的⼯⼚std::unique_ptr<Json::StreamWriter>writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}
$./ test.exe{"name" : "joe","sex" : "男"
}

3.使⽤ Json::FastWriter

优点:⽐ StyledWriter 更快,因为它不添加额外的空格和换⾏符。

#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>intmain()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::FastWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
}
$./ test.exe{"name" : "joe", "sex" : "男"}

2.2.3 反序列化

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp提供了以下⽅法进⾏反序列化:
使⽤ Json::Reader
优点:提供详细的错误信息和位置,⽅便调试。
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{// JSON 字符串std::string json_string = "{\"name\":\"张三\", \"age\":30,\"city\":\"北京\"}";// 解析 JSON 字符串Json::Reader reader;Json::Value root;// 从字符串中读取 JSON 数据bool parsingSuccessful = reader.parse(json_string, root);if (!parsingSuccessful){// 解析失败,输出错误信息std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;return 1;}// 访问 JSON 数据std::string name = root["name"].asString();int age = root["age"].asInt();std::string city = root["city"].asString();// 输出结果std::cout << "Name: " << name << std::endl;std::cout << "Age: " << age << std::endl;std::cout << "City: " << city << std::endl;return 0;
}Name : 张三
Age : 30 
City : 北京

2.3 定制协议

对于客户端和服务端来说,他们双方都需要进行反序列化和序列化

class Request//客户端发出请求
{
public:Request(){}Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper){}std::string ToString(){// 序列化Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter writer;std::string str = writer.write(root);return str;}bool ParseFromString(std::string &str){// 反序列化Json::Reader reader;Json::Value root;if (!reader.parse(str, root)){return false;}_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();return true;}int X() { return _x; }int Y() { return _y; }char Oper() { return _oper; }private:int _x;int _y;char _oper;
};class Response//服务端发送给客户端的应答
{
public:Response(){}Response(int result, int code) : _result(result), _code(code){}std::string ToString(){// 序列化Json::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;std::string str = writer.write(root);return str;}bool ParseFromString(std::string &str){// 反序列化Json::Reader reader;Json::Value root;if (!reader.parse(str, root)){return false;}_result = root["result"].asInt();_code = root["code"].asInt();return true;}void SetResult(int res){_result = res;}void SetCode(int code){_code = code;}void ShowResult(){std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;}private:int _result; // 运算结果,无法区分清楚应答是计算结果,还是异常值int _code;   // 0:sucess, 1,2,3,4->不同的运算异常的情况
};

由于客户端发给服务端是一个字符串的形式,因此可以在字符串加上一些限定符和字符串的长度来进行封装

    const std::string sep = "\r\n";   // 协议分隔符// 封装报头std::string Encode(const std::string &jsonstr){// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\nstd::string len = std::to_string(jsonstr.size());return len + sep + jsonstr + sep; // 应用层封装报头}

同时,由于Tcp是字节流的,因此要对字符串(报文)进行判断,是否是一个完整的报文

   // 判断是否有完整的报文bool Decode(std::string &data, std::string *jsonstr){// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\nsize_t pos = data.find(sep);if (pos == std::string::npos){return false; // 让调用方继续从内核中读取数据}std::string lenstr = data.substr(0, pos);int len = std::stoi(lenstr);int json_len = len + lenstr.size() + 2 * sep.size(); // 完整报头的长度if (data.size() < json_len){return false; // 让调用方继续从内核中读取数据}*jsonstr = data.substr(pos + sep.size(), len);data.erase(0, json_len); // 移除已解析的数据return true;}

之后就是客户端发送请求和服务端处理请求,协议代码完整如下

const std::string sep = "\r\n";                       // 协议分隔符
using func_t = std::function<Response(Request &req)>; // 让上层调用者传入处理函数class Protocol//协议
{
public:Protocol(){}Protocol(func_t func) : _func(func){}// 封装报头std::string Encode(const std::string &jsonstr){// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\nstd::string len = std::to_string(jsonstr.size());return len + sep + jsonstr + sep; // 应用层封装报头}// 判断是否有完整的报文bool Decode(std::string &data, std::string *jsonstr){// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\nsize_t pos = data.find(sep);if (pos == std::string::npos){return false; // 让调用方继续从内核中读取数据}std::string lenstr = data.substr(0, pos);int len = std::stoi(lenstr);int json_len = len + lenstr.size() + 2 * sep.size(); // 完整报头的长度if (data.size() < json_len){return false; // 让调用方继续从内核中读取数据}*jsonstr = data.substr(pos + sep.size(), len);data.erase(0, json_len); // 移除已解析的数据return true;}// 服务端处理请求void GetRequest(std::shared_ptr<Socket> &sock, inerAddr &client){// 从套接字中读取数据std::string data;while (true){int n = sock->Recv(&data);if (n > 0){std::string jsonstr;while (Decode(data, &jsonstr)){// 此时jsonstr是完整的报文Request req;bool parse_ret = req.ParseFromString(jsonstr);if (!parse_ret)continue;Response resp = _func(req); // 调用业务处理函数LOG(LogLevel::INFO) << "client:" << client.getIP() << ":" << client.getPort() << "Request:" << req.X() << req.Oper() << req.Y();// 序列化std::string resp_str = resp.ToString();// 添加报头std::string send_data = Encode(resp_str);// 发送sock->Send(send_data.c_str());}}else if (n == 0){LOG(LogLevel::INFO) << "client:" << client.getIP() << ":" << client.getPort() << "Quit!";break;}else{LOG(LogLevel::WARN) << "client:" << client.getIP() << ":" << client.getPort() << "recv error";break;}}}// 客户端发送请求bool GetResponse(std::shared_ptr<Socket> &client, std::string &res_buff, Response *resp){while (true){int n = client->Recv(&res_buff);if (n > 0){std::string jsonstr;while (Decode(res_buff, &jsonstr)){// 反序列化resp->ParseFromString(jsonstr);}return true;}else if (n == 0){LOG(LogLevel::ERROR) << "client disconnect";return false;}else{LOG(LogLevel::ERROR) << "recv error";return false;}}}std::string BuildRequestString(int x, int y, char oper){// 1. 构建一个完整的请求Request req(x, y, oper);// 2. 序列化std::string json_req = req.ToString();return Encode(json_req);}~Protocol(){}private:func_t _func;
};

2.4 任务类

对计算任务处理进行封装

#pragma once#include "Protocol.hpp"//协议
#include <iostream>
class Cal
{
public:Response Execute(Request &req){Response resp(0, 0); // code: 0表示成功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除零错误}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;}
};

2.5 服务端实现

#include "Socket.hpp"//套接字封装
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>using namespace SocketModuls;
using namespace LogModuls;
using ioservice_t=std::function<void(std::shared_ptr<Socket> &sock,inerAddr &client)>;class TcpServer
{public:TcpServer(uint16_t port,ioservice_t ioservice):m_port(port),m_isRun(false),m_ioservice(ioservice),m_socket(std::make_unique<TcpSocket>()){m_socket->BuildTcpSocketMethod(m_port);}void Start(){m_isRun = true;while (m_isRun){inerAddr client;//建立连接auto sock=m_socket->Accept(&client);if (sock==nullptr){continue;}LOG(LogLevel::INFO)<<"accept a client";//处理连接多进程pid_t pid=fork();if(pid<0){LOG(LogLevel::ERROR)<<"fork error";exit(FORK_ERR);}else if(pid==0){m_socket->Close();if(fork()>0)exit(OK);m_ioservice(sock,client);exit(OK);}else{sock->Close();pid_t rid = ::waitpid(pid, nullptr, 0);}}m_isRun = false;}~TcpServer(){}private:uint16_t m_port;bool m_isRun;std::unique_ptr<Socket> m_socket;ioservice_t m_ioservice;
};
#include "Protocol.hpp"
#include "Tcpserver.hpp"
#include"NetCal.hpp"//任务类
#include <memory>void Usage(std::string proc)
{std::cerr << "Usage: " << proc << " port" << std::endl;
}// ./tcpserver 8080
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERR);}//顶层std::unique_ptr<Cal> cal=std::make_unique<Cal>();//协议层std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>([&cal](Request &req)->Response{return cal->Execute(req);});//服务层std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]),[&protocol](std::shared_ptr<Socket> &sock, inerAddr &client){protocol->GetRequest(sock, client);});tsvr->Start();return 0;
}

2.6 客户端实现

#include "Socket.hpp"
#include "Common.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <string>
#include <memory>using namespace SocketModuls;void GetDataFromStdin(int *x, int *y, char *oper)
{std::cout << "Please Enter x: ";std::cin >> *x;std::cout << "Please Enter y: ";std::cin >> *y;std::cout << "Please Enter oper: ";std::cin >> *oper;
}
int main(int argc,char* argv[])
{if(argc!=3){std::cerr<<"Usage: "<<argv[0]<<" ip port"<<std::endl;exit(USAGE_ERR);}std::string ip=argv[1];uint16_t port=atoi(argv[2]);std::shared_ptr<Socket> client=std::make_shared<TcpSocket>();client->BuildTcpClientSocketMethod();if(client->Connect(ip,port)!=0){std::cerr<<"Connect error"<<std::endl;exit(CONNECT_ERR);}std::unique_ptr<Protocol> protocol=std::make_unique<Protocol>();std::string resp_buff;while(true){int x, y;char oper;GetDataFromStdin(&x, &y, &oper);std::string req_str=protocol->BuildRequestString(x, y, oper);//发送client->Send(req_str);//接收Response resp;bool res=protocol->GetResponse(client, resp_buff,&resp);if(res==false)break;//打印结果resp.ShowResult();}client->Close();return 0;
}

http://www.dtcms.com/a/495603.html

相关文章:

  • MySQL 逗号分隔的字符串查询的集中方式
  • 第12章 STM32 ADC采集内部温度传感器和基准电压的配置和测试
  • 网站推广公司 wordpress.net做网站之前设置
  • 台州网站制作公司个人免费注册公司网站
  • 怎么网站建设到百度怎么样找回网站密码
  • 如何检测网站死链沧州做网站的
  • 河南特色农产品识别系统:让AI守护“中原味道”
  • Linux系统--信号--信号屏蔽(阻塞)核心函数
  • GaussDB 应用侧报Read timed out解决方法
  • 一种解决java fst序列化兼容性问题的方法
  • ROS系统中常用的数据传输方式——参数
  • 网站移动端是什么问题吗移动商城积分兑换
  • 淘宝API数据采集的日志监控与异常报警
  • 熊猫(安卓):识字软件
  • 水题记录2.3
  • Google Landmarks Dataset v2 (GLDv2):500万地标图像的识别与检索基准​(数据集概览、下载与使用全流程​)
  • 在设置feign请求的请求头透传(Header Propagation)时获取不到当前服务请求头的信息
  • 服装网站设计网站强制分享链接怎么做的
  • DevExpress WPF中文教程:Data Grid - 如何使用虚拟源?(二)
  • WPF中的变换(Transform)功能详解
  • 北京做网站主机开通成功网站建设中
  • 工业显示器在微铣削机床中的应用
  • 合肥手机网站建设陕西网站建设平台
  • 东莞网站推广及优化平台营销策略
  • exp4j并发解决
  • 学习博弈本身过程脑是怎么看的?
  • 风险感知中枢:监测预警系统的架构与核心
  • 使用ROS2 + Qt编写一个简易计算器
  • 校园资料分享平台|基于SpringBoot和Vue的校园资料分享平台(源码+数据库+文档)
  • 企业花钱做的网站出现违禁词本地wordpress 手机浏览器