【Linux】自定义协议——实现网络序列化和反序列化

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:承接上文内容【Linux】应用层协议序列化和反序列化

在这里插入图片描述


目录

  • 👉🏻代码实现如下
    • Calculate.hpp
    • Protocol.hpp
    • Socket.hpp
    • TcpClientMain.cc
    • TcpServer.hpp
    • TcpServerMain.cc
    • 实现效果
  • 👉🏻JSON
    • Centos环境下载JSON第三方库供c++编译
    • JsonCpp库中几个常用类
    • Json接口函数用法
    • 定义预处理器宏
    • 使用JSON序列化和反序列化
      • Protocol.hpp
      • Makefile
      • 效果展示

👉🏻代码实现如下

代码目录:
在这里插入图片描述
此次代码主要变动如下:
🔥TcpServerMain.cc:
1.HanlderRrequest:
a.读取报文
b.分析是否是一个完整的报文
c.当读到完整的报文后,进行反序列化
d.业务处理(计算器)
e.将计算结果的报文进行序列化
d.将报文再度Encode再度封装加上长度和\n
7.发送报文

🔥Calculate:

🔥TcpServer.hpp:
func_t
1.ThreadRun
a.读取报文
b.报文处理
c.发送处理

🔥TcpClientMain.cc:
1.构建请求,
2.对请求进行序列化
3.添加自描述报头
4.发送请求
5.读取响应
6.报文解析
7.创建response "result code"并进行反序列化
8.输出结果

Calculate.hpp

#pragma once#include<iostream>
#include<memory>
#include"Protocol.hpp"namespace CalCulateNS
{enum{Success = 0,DivZeroErr,ModZeroErr,UnKnowOper};class CalCulate{public:CalCulate() {}//该函数对传进来的需求进行计算处理,然后转换为返回格式转出shared_ptr<Protocol::Response> Cal(shared_ptr<Protocol::Request> req){//1.首先需要创建回应对象指针shared_ptr<Protocol::Response> resp = factory.BuildResponse();resp->SetCode(Success);//创建成功 //2.接下来就是对各种操作符进行选择计算switch (req->GetOper()){case '+':resp->SetResult(req->GetX() + req->GetY());break;case '-':resp->SetResult(req->GetX() - req->GetY());break;case '*':resp->SetResult(req->GetX() * req->GetY());break;case '/':{if (req->GetY() == 0){resp->SetCode(DivZeroErr);//除0错误}else{resp->SetResult(req->GetX() / req->GetY());}}break;case '%':{if (req->GetY() == 0){resp->SetCode(ModZeroErr);}else{resp->SetResult(req->GetX() % req->GetY());}}break;default:resp->SetCode(UnKnowOper);//未知错误break;}return resp;}~CalCulate(){}private:Protocol::Factory factory;};
}

实现计算器的业务功能

Protocol.hpp

#pragma once#include<iostream>
#include<string>
#include<memory>
using namespace std;namespace Protocol
{const string ProtSep = " ";//分隔符const string LineBreakSep = "\n";//换行符//封装报文:"len\nx op y\n"string Encode(const string& message){string len = to_string(message.size());string package = len+LineBreakSep+message+LineBreakSep;return package;}//解析报文bool Decode(string& package,string* message){//1.第一步我们要检查该报文是否有边界\n处理auto pos = package.find(LineBreakSep);if(pos==string::npos) return false;//2.第二步我们要判断有效载荷的长度是否如报头len中所说一样长string len = package.substr(0,pos);int messagelen = stoi(len);int totallen = len.size()+messagelen+2*LineBreakSep.size();//这里是整个报文的长度(报头加边界符加有效载荷)if(package.size()<totallen) return false;//3.如果上述没有问题,说明此时package内部一定有一个完整的报文了//我们现在就能对其解析,得到有效载荷 *message = package.substr(pos+LineBreakSep.size(),messagelen);package.erase(0,totallen);//package已经利用完,清空return true;}class Request
{
public:Request():_data_x(0),_data_y(0),_oper(0){}Request(int x,int y,char op):_data_x(x),_data_y(y),_oper(op){}void Inc(){_data_x++;_data_y++;}void Debug(){cout<<"_data_x: "<<_data_x<<endl;cout<<"_data_y: "<<_data_y<<endl;cout<<"_oper: "<<_oper<<endl;}bool Serialize(string* out)//序列化{*out = to_string(_data_x)+ProtSep+_oper+ProtSep+to_string(_data_y);return true;}bool Deserialize(string& in)//反序列化{//1.先找到操作符两边的分隔符auto left = in.find(ProtSep);if(left==string::npos) return false;//找不到则说明不是完整报文,失败auto right = in.rfind(ProtSep);if(right==string::npos) return false;//2.将报文中的x和y以及操作符都提取出来赋值 _data_x = stoi(in.substr(0,left));_data_y = stoi(in.substr(right+ProtSep.size()));string oper = in.substr(left+ProtSep.size(),right-(left+ProtSep.size()));if(oper.size()!=1) return false;//操作数长度只能为1_oper = oper[0];return true;}//获取_data_x、_data_y、_oper的外部接口int GetX(){return _data_x;}int GetY(){return _data_y;}char GetOper() {return _oper;}private:// _data_x _oper _data_y// 报文的自描述字段// "len\nx op y\n" : \n不属于报文的一部分,约定// 很多工作都是在做字符串处理!int _data_x;int _data_y;char _oper;//操作数
};class Response
{
public:Response():_result(0),_code(0){}Response(int result,int code):_result(result),_code(code){}bool Serialize(string* out){*out = to_string(_result) + ProtSep + to_string(_code);return true;}bool Deserialize(string& in){//1.找分隔符auto pos = in.find(ProtSep);if(pos==string::npos) return false;//2.提取_result = stoi(in.substr(0,pos));_code = stoi(in.substr(pos+ProtSep.size()));return true;}//设置result和code的值void SetResult(int res) { _result = res; }void SetCode(int code) {_code = code; }//提供result和code的外部接口int GetResult() { return _result; }int GetCode() { return _code; }
private:
// "_result _code"  序列int _result;int _code;
};//工厂模式,建造类设计模式,直接返回指针对象
class Factory
{
public:shared_ptr<Request> BuildRequest(){shared_ptr<Request> req = make_shared<Request>();return req;}shared_ptr<Request> BuildRequest(int x,int y,char op){shared_ptr<Request> req = make_shared<Request>(x,y,op);return req;}shared_ptr<Response> BuildResponse(){shared_ptr<Response> resp = make_shared<Response>();return resp;}shared_ptr<Response> BuildResponse(int result,int code){shared_ptr<Response> resp = make_shared<Response>(result,code);return resp;}};
}

自主定义协议,其中定义了需求和响应的类,提供了对需求和响应的序列化和反序列化的接口函数,
以及对报文的封装及解包的接口函数等等

Socket.hpp

#pragma once #include<iostream>
#include<string>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>#define Convert(addrptr) ((struct sockaddr*)addrptr)using namespace std;namespace Net_Work
{const static int defaultsockfd = -1;const int backlog = 5;enum{SocketError = 1,BindError,ListenError,};//封装一个基类:Socket接口类class Socket{public:virtual ~Socket(){}virtual void CreateSocketOrDie() = 0;//创建一个套接字virtual void BindSocketOrDie(uint16_t port) = 0;//套接字进行绑定网络信息virtual void ListenSocketOrDie(int backlog) = 0;//进行监听virtual Socket* AcceptConnection(string * peerip,uint16_t* peerport)=0;//接收连接,并返回一个新的套接字virtual bool ConnectServer(string& peerip,uint16_t peerport)=0;//连接服务端virtual int GetSockFd() = 0;//返回套接字描述符virtual void SetSockFd(int sockfd) = 0;//virtual void CloseSocket() = 0;//关闭套接字virtual bool Recv(string *buffer,int size) = 0;//用来接收信息virtual void Send(string& send_str) = 0;//用来发送信息public:void BuildListenSocketMethod(uint16_t port,int backlog)//创建一个监听服务{//1.创建套接字CreateSocketOrDie();//2.套接字进行绑定网络信息BindSocketOrDie(port);//3.开始监听ListenSocketOrDie(backlog);}bool BuildConnectSocketMethod(string& serverip,uint16_t& serverport)//创建一个连接服务{//1.创建套接字CreateSocketOrDie();return ConnectServer(serverip,serverport);}void BuildNormalSocketMethod(int sockfd){SetSockFd(sockfd);}};//实现Tcp套接字class TcpSocket:public Socket{public:TcpSocket(int sockfd = defaultsockfd ):_sockfd(sockfd){}~TcpSocket(){}/void CreateSocketOrDie() override//创建一个套接字{_sockfd = socket(AF_INET,SOCK_STREAM,0);if(_sockfd<0)exit(SocketError);}void BindSocketOrDie(uint16_t port) override//套接字进行绑定网络信息{//本地网络信息初始化struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;//服务端的ip由本地随机绑定local.sin_port = htons(port);//开始绑定int n = bind(_sockfd,Convert(&local),sizeof(local));if(n<0) exit(BindError);}void ListenSocketOrDie(int backlog) override//进行监听{int n = listen(_sockfd,backlog);if(n<0) exit(ListenError);}Socket* AcceptConnection(string * peerip,uint16_t* peerport)override//接收连接{struct sockaddr_in peer;//用来存储客户端的地址信息socklen_t len = sizeof(peer);int newsockfd = accept(_sockfd,Convert(&peer),&len);if(newsockfd<0)return nullptr;*peerport = ntohs(peer.sin_port);//网络序列本地化*peerip = inet_ntoa(peer.sin_addr);Socket* s = new TcpSocket(newsockfd);return s;}bool ConnectServer(string& serverip,uint16_t serverport)override//连接服务端{struct sockaddr_in server;//存储服务端的地址信息memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());//ip网络字节序化,4字节int n = connect(_sockfd,Convert(&server),sizeof(server));if(n==0)return true;elsereturn false;}int GetSockFd() override//返回套接字描述符{return _sockfd;}void SetSockFd(int sockfd) override//{_sockfd = sockfd;}void CloseSocket() override//关闭套接字{if(_sockfd>defaultsockfd)close(_sockfd);}bool Recv(std::string *buffer, int size) override{char inbuffer[size];ssize_t n = recv(_sockfd, inbuffer, size-1, 0);if(n > 0){//接收到了inbuffer[n] = 0;*buffer += inbuffer; // buffer可能是历史上所有发送来的消息,拼接在buffer后面return true;}else if(n == 0) return false;else return false;}void Send(std::string &send_str) override{// 多路转接我们再统一说send(_sockfd, send_str.c_str(), send_str.size(), 0);}private:int _sockfd;};
};

封装了socket网络通信,其中蕴含了创建套接字,进行绑定网络信息,网络监听,接收连接,连接服务端,发收消息等等接口函数……

TcpClientMain.cc

#include "Protocol.hpp"
#include "Socket.hpp"
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
#include <unistd.h>using namespace Protocol;// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage : " << argv[0] << " serverip serverport" << std::endl;return 0;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);Net_Work::Socket *conn = new Net_Work::TcpSocket();if (!conn->BuildConnectSocketMethod(serverip, serverport)){std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;}std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;std::unique_ptr<Factory> factory = std::make_unique<Factory>();srand(time(nullptr) ^ getpid());const std::string opers = "+-*/%^=&";while (true){// 1. 构建一个请求,遵守协议int x = rand() % 100; //[0, 99]usleep(rand() % 7777);int y = rand() % 100; //[0,99]char oper = opers[rand() % opers.size()];std::shared_ptr<Request> req = factory->BuildRequest(x, y, oper);// 2. 对请求进行序列化std::string requeststr;req->Serialize(&requeststr); //std::cout << requeststr << std::endl;// for teststd::string testreq = requeststr;testreq += " ";testreq += "= ";// 3. 添加自描述报头requeststr = Encode(requeststr);std::cout << requeststr << std::endl;// 4. 发送请求conn->Send(requeststr);std::string responsestr;while (true){// 5. 读取响应if(!conn->Recv(&responsestr, 1024)) break;// 6. 报文进行解析std::string response;if (!Decode(responsestr, &response))continue; // 我就不连续的处理了// 7.response "result code"auto resp = factory->BuildResponse();resp->Deserialize(response);// 8. 得到了计算结果,而且是一个结构化的数据std::cout << testreq << resp->GetResult() << "[" << resp->GetCode() << "]" << std::endl;break;}sleep(1);}conn->CloseSocket();return 0;
}

客户端代码,主要就是发起对服务端的连接,连接成功后,发送消息给服务端,中间对要发送的数据进行序列化封装,
最后接收服务端发送回来的信息并对其反序列化解包

TcpServer.hpp

#pragma once#include"Socket.hpp"
#include<pthread.h>
#include<functional>using func_t = std::function<std::string(std::string &, bool *error_code)>;class TcpServer;class ThreadData
{
public:ThreadData(TcpServer* tcp_this,Net_Work::Socket* sockp):_this(tcp_this),_sockp(sockp){}
public:TcpServer* _this;//TcpServer的指针对象Net_Work::Socket* _sockp;//套接字指针对象
};class TcpServer
{
public:TcpServer(uint16_t port,func_t handler_request):_port(port),_listensocket(new Net_Work::TcpSocket()),_hanlder_request(handler_request){_listensocket->BuildListenSocketMethod(_port,Net_Work::backlog);//开启监听事务}static void * ThreadRun(void* args)//因为pthread_create要求方法参数中的参数必须只有一个void*//所以必须变为静态,否则成员函数第一个参数默认隐式为this指针{//因为执行的是多线程,这里我们也没有封装线程的自动回收//所以为了不发生线程阻塞,我们要让当前线程与主线程分离,不影响主线程,并且自己做完任务自己回收pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);string inbufferstream;//用来存储Recv到的报文while(true){bool ok = true;//1.读取报文if(!td->_sockp->Recv(&inbufferstream,1024)) break;//如果读取报文失败,则退出//2.对报文内容进行处理,并接收处理后的返回值string send_string = td->_this->_hanlder_request(inbufferstream,&ok);if(ok){//如果处理的ok,则发送数据给客户端if(!send_string.empty()){td->_sockp->Send(send_string);}}elsebreak;}td->_sockp->CloseSocket();//关闭accept的新套接字delete td->_sockp;//销毁指针delete td;return nullptr;}void Loop(){while(true){string peerip;uint16_t peerport;Net_Work::Socket* newsocket = _listensocket->AcceptConnection(&peerip,&peerport);//接收客户端信息if(newsocket==nullptr) continue;cout<<"获取一个新连接,sockfd:"<<newsocket->GetSockFd()<<"client info: "<<peerip<<" "<<peerport<<endl;//用完后关闭newsocket//newsocket->CloseSocket();   //使用多线程进行处理任务pthread_t tid;ThreadData* td = new ThreadData(this,newsocket);pthread_create(&tid,nullptr,ThreadRun,td);//线程创建并执行相对应任务}}~TcpServer(){delete _listensocket;}private:uint16_t _port;Net_Work::Socket* _listensocket;public:func_t _hanlder_request;//request执行方法};

封装了服务端的职责功能:对客户端的监听,多线程执行客户端传来的任务

TcpServerMain.cc

#include<memory>
#include<unistd.h>//包含外部头文件
#include"Protocol.hpp"
#include"Socket.hpp"
#include"TcpServer.hpp"
#include"Calculate.hpp"using namespace Net_Work;
using namespace Protocol;
using namespace CalCulateNS;//HandlerRequest进行字节流数据解析,和调用业务处理方法得到处理结果返回
string  HandlerRequest(string& inbufferstream,bool * error_code)
{*error_code = true;//1.创建一个计算器对象,待会用来处理业务CalCulate calculte;//2.构建需求对象:用来进行将_data_x _oper _data_y进行序列化和反序列化unique_ptr<Factory> factory = make_unique<Factory>();//记得后面加()!!!shared_ptr<Request> req = factory->BuildRequest();string total_resp_string;//用来存储回应字符串string message;//用来存储报文解析后格式后的_data_x _oper _data_y//3.分析字节流,看是否有一个完整的报文while(Decode(inbufferstream,&message)){printf("message[%s] is waiting analysis\n",message.c_str());//4.此时Decode解析成功,我一定得到了一个完整的报文//接下来进行反序列化if (!req->Deserialize(message)){std::cout << "Deserialize error" << std::endl;*error_code = false;return std::string();}std::cout << "Deserialize success" << std::endl;//5.进行计算业务处理shared_ptr<Response> resp = calculte.Cal(req);//6.处理完后,我们要将结果也要序列化,因为我们还要发送回客户端!std::string send_string;resp->Serialize(&send_string); // 序列化为"result code"//7.构建完成的字符串级别的响应报文send_string = Encode(send_string);total_resp_string += send_string;}return total_resp_string;
}int main(int argc,char* argv[])
{if(argc != 2){cout << "Usage : " << argv[0] << " port" << std::endl;return 0;}uint16_t  localport = stoi(argv[1]);unique_ptr<TcpServer> svr (new TcpServer(localport,HandlerRequest));//unique_ptr只能支持移动构造svr->Loop();//server开始不断获取新连接return 0;
}

主要是打开服务端,服务端的具体实现功能被封装在TcpServer.hpp

实现效果

在这里插入图片描述

虽然整体代码来说还不错,但是自己定义序列化和反序列化未免太麻烦了,有没有现成的轮子给我们用呢,接下来我们引入JSON.

👉🏻JSON

🌈概念介绍
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它以易于阅读和编写的文本形式来表示数据。JSON格式常用于Web应用程序之间的数据交换,也经常用于存储和传输结构化数据。

JSON的基本数据类型包括:

  1. 对象(Object):对象是一个无序的键值对集合,键和值之间使用冒号分隔,键值对之间使用逗号分隔,整个对象用花括号包围。例如:{"name": "John", "age": 30}
  2. 数组(Array):数组是一个有序的值的集合,值之间使用逗号分隔,整个数组用方括号包围。例如:["apple", "banana", "orange"]
  3. 字符串(String):字符串是由双引号包围的文本序列。例如:“Hello, World!”
  4. 数字(Number):数字可以是整数或浮点数。例如:423.14
  5. 布尔值(Boolean):表示真或假。例如:truefalse
  6. 空值(Null):表示空值。例如:null

JSON的优点包括易于阅读和编写、跨平台兼容性好、数据结构简单清晰。它也被广泛用于Web开发、API设计和配置文件等领域。 JSON与JavaScript语言密切相关,但由于其简洁和通用性,其他编程语言和技术也广泛采用了JSON格式。
🍲 JSON的数据格式包括以下几种:

  1. 对象(Object):由一对大括号 {} 包裹,内部包含零个或多个键值对,键和值之间使用冒号 : 分隔,键值对之间使用逗号 , 分隔。
{"name": "John","age": 30,"city": "New York"
}
  1. 数组(Array):由一对方括号 [] 包裹,内部包含零个或多个值,值之间使用逗号 , 分隔。
["apple", "banana", "orange"]
  1. 字符串(String):由双引号 "" 包裹的文本序列。
"Hello, World!"
  1. 数字(Number):整数或浮点数。
42
3.14
  1. 布尔值(Boolean):表示真或假。
true
false
  1. 空值(Null):表示空值。
null

这些数据格式可以嵌套组合使用,构成复杂的数据结构。 JSON的灵活性和简洁性使其成为了一种流行的数据交换格式。

Centos环境下载JSON第三方库供c++编译

下载命令

sudo yum install jsoncpp-devel

JsonCpp库中几个常用类

在C++中,常用的JSON库之一是JsonCpp,它提供了一组用于解析和生成JSON数据的类和函数。下面是JsonCpp库中几个常用类的介绍:

  1. Json::Value
    Json::Value是JsonCpp库中最基本的类之一,它表示JSON数据的值。Json::Value可以表示JSON中的各种数据类型,包括对象、数组、字符串、数字、布尔值和空值。它提供了一组成员函数来访问和操作JSON数据。

  2. Json::FastWriter
    Json::FastWriter是JsonCpp库中的一个类,用于快速地将Json::Value对象序列化为JSON字符串。它提供了一个成员函数write(),可以将Json::Value对象转换为字符串形式的JSON数据,但是不保证生成的JSON字符串的格式化。

  3. Json::StyledWriter
    Json::StyledWriter也是JsonCpp库中的一个类,用于将Json::Value对象序列化为JSON字符串,并且保留了可读性良好的格式化。与Json::FastWriter相比,Json::StyledWriter生成的JSON字符串会更加易读,每个值都会单独占据一行,并且适当地缩进。

  4. Json::Reader
    Json::Reader是JsonCpp库中用于解析JSON字符串的类。它提供了一个成员函数parse(),可以将JSON字符串解析为Json::Value对象。Json::Reader会尝试解析输入的JSON字符串,并将解析结果存储在给定的Json::Value对象中。如果解析失败,Json::Reader会返回false,并且可以通过调用getFormattedErrorMessages()方法获取解析错误的详细信息。

Json接口函数用法

当使用JsonCpp库时,你会频繁使用以下几个常用的Json接口函数来解析和生成JSON数据:

🚗 解析JSON数据:

  1. Json::Reader::parse()
    • 函数原型:bool parse(const std::string& document, Json::Value& root, bool collectComments = true)
    • 用法:将JSON格式的字符串解析为Json::Value对象。
    • 参数:
      • document:待解析的JSON字符串。
      • root:用于存储解析结果的Json::Value对象。
      • collectComments:可选参数,指定是否收集注释,默认为true。
#include <json/json.h>std::string jsonString = "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}";
Json::Value root;
Json::Reader reader;
if (reader.parse(jsonString, root)) {// 解析成功,root 中存储了 JSON 数据
} else {// 解析失败,可以通过 reader.getFormattedErrorMessages() 获取详细错误信息
}

🚗 生成JSON数据:

  1. Json::FastWriter::write()
    • 函数原型:std::string write(const Json::Value& root)
    • 用法:将Json::Value对象转换为JSON格式的字符串,快速但不带格式。
    • 参数:root:待转换的Json::Value对象。
#include <json/json.h>Json::Value root;
root["name"] = "John";
root["age"] = 30;
root["city"] = "New York";Json::FastWriter writer;
std::string jsonString = writer.write(root);
  1. Json::StyledWriter::write()
    • 函数原型:std::string write(const Json::Value& root)
    • 用法:将Json::Value对象转换为JSON格式的字符串,保留可读性良好的格式化。
    • 参数:root:待转换的Json::Value对象。
#include <json/json.h>Json::Value root;
root["name"] = "John";
root["age"] = 30;
root["city"] = "New York";Json::StyledWriter writer;
std::string jsonString = writer.write(root);

定义预处理器宏

在C和C++中,编译器选项 -D 用于定义预处理器宏(Preprocessor Macros)。这个选项告诉编译器在编译过程中,预先定义一个宏,使得在源代码中可以使用该宏。

例如,在使用命令行编译器编译程序时,可以使用 -D 选项来定义宏,语法如下:

-D<macro_name>[=<value>]

其中:

  • <macro_name> 是要定义的宏的名称。
  • <value>(可选)是要为宏定义的值。如果省略了 <value>,则预处理器会将该宏定义为 1。

例如,假设有一个名为 DEBUG 的宏,可以通过以下方式在编译时定义它:

gcc -DDEBUG main.c -o main

这样,在 main.c 文件中就可以使用 #ifdef DEBUG#ifndef DEBUG 来根据宏的定义情况进行条件编译。

总之,-D 选项用于在编译过程中定义预处理器宏,使得在源代码中可以使用这些宏进行条件编译或其他预处理操作。

使用JSON序列化和反序列化

Protocol.hpp

#pragma once#include<iostream>
#include<string>
#include<memory>
#include <jsoncpp/json/json.h>
using namespace std;namespace Protocol
{const string ProtSep = " ";//分隔符const string LineBreakSep = "\n";//换行符//封装报文:"len\nx op y\n"string Encode(const string& message){string len = to_string(message.size());string package = len+LineBreakSep+message+LineBreakSep;return package;}//解析报文bool Decode(string& package,string* message){//1.第一步我们要检查该报文是否有边界\n处理auto pos = package.find(LineBreakSep);if(pos==string::npos) return false;//2.第二步我们要判断有效载荷的长度是否如报头len中所说一样长string len = package.substr(0,pos);int messagelen = stoi(len);int totallen = len.size()+messagelen+2*LineBreakSep.size();//这里是整个报文的长度(报头加边界符加有效载荷)if(package.size()<totallen) return false;//3.如果上述没有问题,说明此时package内部一定有一个完整的报文了//我们现在就能对其解析,得到有效载荷 *message = package.substr(pos+LineBreakSep.size(),messagelen);package.erase(0,totallen);//package已经利用完,清空return true;}class Request
{
public:Request():_data_x(0),_data_y(0),_oper(0){}Request(int x,int y,char op):_data_x(x),_data_y(y),_oper(op){}void Inc(){_data_x++;_data_y++;}void Debug(){cout<<"_data_x: "<<_data_x<<endl;cout<<"_data_y: "<<_data_y<<endl;cout<<"_oper: "<<_oper<<endl;}bool Serialize(string* out)//序列化{#ifdef SelfDefine //条件编译*out = to_string(_data_x)+ProtSep+_oper+ProtSep+to_string(_data_y);return true;#else Json::Value root;root["datax"] = _data_x;root["datay"] = _data_y;root["oper"] = _oper;Json::FastWriter writer;*out = writer.write(root);return true;#endif}bool Deserialize(string& in)//反序列化{#ifdef SelfDefine //1.先找到操作符两边的分隔符auto left = in.find(ProtSep);if(left==string::npos) return false;//找不到则说明不是完整报文,失败auto right = in.rfind(ProtSep);if(right==string::npos) return false;//2.将报文中的x和y以及操作符都提取出来赋值 _data_x = stoi(in.substr(0,left));_data_y = stoi(in.substr(right+ProtSep.size()));string oper = in.substr(left+ProtSep.size(),right-(left+ProtSep.size()));if(oper.size()!=1) return false;//操作数长度只能为1_oper = oper[0];return true;#elseJson::Value root;Json::Reader reader;bool res = reader.parse(in,root);if(res){_data_x = root["datax"].asInt();_data_y = root["datay"].asInt();_oper = root["oper"].asInt();}return res;#endif}//获取_data_x、_data_y、_oper的外部接口int GetX(){return _data_x;}int GetY(){return _data_y;}char GetOper() {return _oper;}private:// _data_x _oper _data_y// 报文的自描述字段// "len\nx op y\n" : \n不属于报文的一部分,约定// 很多工作都是在做字符串处理!int _data_x;int _data_y;char _oper;//操作数
};class Response
{
public:Response():_result(0),_code(0){}Response(int result,int code):_result(result),_code(code){}bool Serialize(std::string *out){#ifdef SelfDefine*out = std::to_string(_result) + ProtSep + std::to_string(_code);return true;#elseJson::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);return true;#endif}bool Deserialize(std::string &in) // "_result _code" [){#ifdef SelfDefineauto pos = in.find(ProtSep);if (pos == std::string::npos)return false;_result = std::stoi(in.substr(0, pos));_code = std::stoi(in.substr(pos + ProtSep.size()));return true;#elseJson::Value root;Json::Reader reader;bool res = reader.parse(in, root);if(res){_result = root["result"].asInt();_code = root["code"].asInt();}return res;#endif}//设置result和code的值void SetResult(int res) { _result = res; }void SetCode(int code) {_code = code; }//提供result和code的外部接口int GetResult() { return _result; }int GetCode() { return _code; }
private:
// "_result _code"  序列int _result;int _code;
};//工厂模式,建造类设计模式,直接返回指针对象
class Factory
{
public:shared_ptr<Request> BuildRequest(){shared_ptr<Request> req = make_shared<Request>();return req;}shared_ptr<Request> BuildRequest(int x,int y,char op){shared_ptr<Request> req = make_shared<Request>(x,y,op);return req;}shared_ptr<Response> BuildResponse(){shared_ptr<Response> resp = make_shared<Response>();return resp;}shared_ptr<Response> BuildResponse(int result,int code){shared_ptr<Response> resp = make_shared<Response>(result,code);return resp;}};
}

Makefile

.PHONY:all
all:tcpclient tcpserverLDFLAG=#-DSelfDefine=1
tcpclient:TcpClientMain.ccg++ -o $@ $^ $(LDFLAG) -ljsoncpp -std=c++14
tcpserver:TcpServerMain.ccg++ -o $@ $^ $(LDFLAG) -ljsoncpp -std=c++14 -lpthread.PHONY:clean
clean:rm -f tcpclient tcpserver

效果展示

在这里插入图片描述


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/3158.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言-联合体基本概念

联合体的外在形式跟结构体非常类似&#xff0c;但它们有一个本质的区别&#xff1a;结构体中的各个成员是各自独立的&#xff0c;而联合体中的各个成员却共用同一块内存&#xff0c;因此联合体也称为共用体。 联合体内部成员的这种特殊的“堆叠”效果&#xff0c;使得联合体有如…

Ollama完成本地模型的运行

Ollama完成本地模型的运行 llama 3 8b很多pc都可以run起来,可以用这个练练手 简介 Ollama 是一个开源的大型语言模型(LLM)服务工具,它允许用户在本地运行和使用各种大型语言模型。Ollama 提供了一个命令行界面,支持多种流行的模型,如 Llama 3、Qwen 1.5、Mixtral、Gemma…

MapMagic 2 Biomes and Functions

MapMagic 2(免费)世界生成器官方模块。支持基于遮罩混合几个图形,从而可以在无限地形上混合不同的生物群落。也随附函数节点,从而可以在子图中执行复杂的生成过程。将它们视作含有输入和输出连接器的生物群落。请注意,必须使用 MapMagic 2 的现有安装才能使用该模块。 下…

(一)JVM实战——jvm的组成部分详解

前言 本节内容是关于java虚拟机JVM组成部分的介绍&#xff0c;通过其组成架构图了解JVM的主要组成部分。 正文 ClassFile&#xff1a;字节码文件 - javac&#xff1a;javac前端编译器将源代码编译成符合jvm规范的.class文件&#xff0c;即字节码文件 - class文件的结构组成&a…

数据变更捕获 (CDC):PostgreSQL 与 ClickHouse - 第一部分

本文字数&#xff1a;13442&#xff1b;估计阅读时间&#xff1a;34 分钟 审校&#xff1a;庄晓东&#xff08;魏庄&#xff09; 本文在公众号【ClickHouseInc】首发 简介 在之前的文章中&#xff0c;我们已经讨论了OLTP数据库&#xff08;例如Postgres&#xff09;和OLAP数据…

【go零基础】go-zero从零基础学习到实战教程 - 1项目表设计

既然是0基础&#xff0c;现在来写下设计思路&#xff0c;因为go-zero是个微服务架构&#xff0c;所以&#xff0c;哪怕是0基础&#xff0c;也从两个服务模块开始写起。 我们的目标是&#xff1a;最小可用微服务架构最佳实践&#xff01; 好了&#xff0c;饼画完了。 第0部分写到…

504网关超时可能是哪些原因导致

当前随时互联网的发展普及&#xff0c;我们经常会使用到网站服务&#xff0c;许多网站为了提高打开速度&#xff0c;都会接入使用CDN。当我们在浏览网页或使用网络服务时&#xff0c;有时候可能有遇到网站打不开的情况&#xff0c;出现各式各样的错误代码&#xff0c;其中504网…

【注解和反射】通过反射动态创建对象、调用普通方法、操作属性

继上一篇博客【注解和反射】获取类运行时结构-CSDN博客 目录 八、通过反射动态创建对象 测试&#xff1a;通过反射动态创建对象 思考&#xff1a;难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后&#xff0c;才可以实…

三维图形程序员必学-CGAL几何算法

GCAL几何算法库,涵盖了很多数学几何算法,矩阵运算、平面拟合、曲线拟合、曲面重建、网格优化、网格剖分、面线相交、布尔运算等等各种图形学几何相关的算法。 文章最后放了一个CGAL求点集拟合平面,投影求线的例子代码。 CGAL是一个开源代码库,官网连接GitHub - CGAL/cgal…

网络安全之弱口令与命令爆破(上篇)(技术进阶)

目录 一&#xff0c;什么是弱口令&#xff1f; 二&#xff0c;为什么会产生弱口令呢&#xff1f; 三&#xff0c;字典的生成 四&#xff0c;使用Burpsuite工具弱口令爆破 总结 一&#xff0c;什么是弱口令&#xff1f; 弱口令就是容易被人们所能猜到的密码呗&#xff0c;…

Linux动态追踪——eBPF

目录 摘要 1 什么是 eBPF 2 eBPF 支持的功能 3 BCC 4 编写脚本 5 总结 6 附 摘要 ftrace 和 perf 与 ebpf 同为 linux 内核提供的动态追踪工具&#xff0c;其中 ftrace 侧重于事件跟踪和内核行为的实时分析&#xff0c;perf 更侧重于性能分析和事件统计&#xff0c;与…

vim+xxd 编辑16进制

1. vim -b mib 2. 在vim 中执行 %!xxd, 这样就可以输入16进制&#xff1a; 3. 输入完成后&#xff0c;在vim中 执行 %!xxd -r 切换至原模式&#xff1b; 4. 保存退出即可 5. 重新打开mib文件&#xff1a;vim -b mib 6. 在vim 中执行 %!xxd, 查看是否符合预期&#xff1a;…

如何理解GDP、国民总收入(GNI)的区别和联系

国内生产总值和国民总收入是衡量一个国家&#xff08;地区&#xff09;经济状况和发展水平的两个重要总量指标。两者既有密切的联系&#xff0c;又有一定区别&#xff0c;用途都非常广泛。 一、GDP与GNI的基本概念 国内生产总值(Gross Domestic Product&#xff0c;GDP)&…

一个联合均值与方差模型的R包——dglm

目录 一、引言二、包的安装与载入三、模拟例子3.1 数据生成3.2 数据查看3.3 模型估计参数 一、引言 在 R 语言中&#xff0c;dglm 包是用于拟合双参数广义线性模型&#xff08;Double Generalized Linear Models&#xff0c;简称 DGLMs&#xff09;的一个工具。这类模型允许同…

模块三:二分——162.寻找峰值

文章目录 题目描述算法原理解法一&#xff1a;暴力查找解法二&#xff1a;二分查找 代码实现解法一&#xff1a;暴力查找解法二&#xff1a;CJava 题目描述 题目链接&#xff1a;162.寻找峰值 根据题意&#xff0c;需要使用O(log N)的时间复杂度来解决&#xff0c;得出本道题…

在美国站群服务器部署时如何保障从253个IP到1000个IP的无缝扩展?

在美国站群服务器部署时如何保障从253个IP到1000个IP的无缝扩展? 在当今企业的数字化转型中&#xff0c;服务器的部署和管理成为了保证业务连续性和拓展性的关键。尤其对于站群服务器来说&#xff0c;随着企业业务的增长和市场的扩展&#xff0c;需要从较小规模的253个IP地址…

AWTK 异形进度条控件发布

异形进度条控件。通过多边形来定义进度条的形状。 代码地址&#xff1a;https://gitee.com/zlgopen/awtk-widget-progress-polygon 特性 通过多边形定义进度条的形状支持通过图片来定义进度条的背景支持通过图片来定义进度条的前景 使用图片填充比使用颜色填充消耗更多的内…

数据结构练习-线性表定义与基本操作

----------------------------------------------------------------------------------------------------------------------------- 1. 线性表是( )。 A.一个有限序列&#xff0c;可以为空 B. 一个有限序列&#xff0c;不可以为空 C. 一个无限序列&#xff0c;可以为空…

第三次国土调查数据库字母缩写代表含义

CCWJQ拆除未尽区&#xff0c;CJDCQ村界调查区&#xff0c;CJDCQJX村界调查区界线&#xff0c;CLKZD测量控制点&#xff0c;CSKFBJ城市开发边界&#xff0c;CZCDYD城镇村等用地&#xff0c;DGX等高线&#xff0c;DLTB地类图斑&#xff0c;DZGY地质公园&#xff0c;FJMSQ风景名胜…

【一般排查思路】针对银河麒麟高级服务器操作系统磁盘空间已满

1. 本身磁盘空间已满 有时候我们会看到服务器上有提示“设备上没有空间”&#xff0c;如图1。 图 1 如果是磁盘本身空间已满&#xff0c;我们可以借助du工具来排查&#xff0c;比如首先cd / 切换到根目录&#xff0c;然后 du -sh * | sort -rh | head -n 3查看空间占用最大的…