需要云服務(wù)器等云產(chǎn)品來學(xué)習(xí)Linux的同學(xué)可以移步/-->騰訊云<--/-->阿里云<--/-->華為云<--/官網(wǎng),輕量型云服務(wù)器低至112元/年,新用戶首次下單享超低折扣。
?目錄
一、序列化與反序列化的概念
二、自定義協(xié)議設(shè)計(jì)一個(gè)網(wǎng)絡(luò)計(jì)算器
2.1TCP協(xié)議,如何保證接收方收到了完整的報(bào)文呢?
2.2自定義協(xié)議的實(shí)現(xiàn)
2.3自定義協(xié)議在客戶端與服務(wù)器中的實(shí)現(xiàn)
三、使用Json進(jìn)行序列化和反序列化
3.1jsoncpp庫的安裝
3.2改造自定義協(xié)議
3.3自定義協(xié)議的命名區(qū)分
網(wǎng)絡(luò)版計(jì)算器代碼可參考博主gitee。
一、序列化與反序列化的概念
????????序列化是指將對(duì)象轉(zhuǎn)換為字節(jié)流或其他可存儲(chǔ)或傳輸?shù)母袷剑员銓⑵浯鎯?chǔ)在文件中或通過網(wǎng)絡(luò)發(fā)送到另一個(gè)系統(tǒng)。反序列化是指將序列化的數(shù)據(jù)重新轉(zhuǎn)換為對(duì)象。在序列化和反序列化過程中,對(duì)象的狀態(tài)信息被保存和恢復(fù),以保證數(shù)據(jù)的完整性和正確性。在分布式系統(tǒng)中,序列化和反序列化是實(shí)現(xiàn)遠(yuǎn)程方法調(diào)用和消息傳遞的重要技術(shù)。
????????例如微信發(fā)送一條消息,會(huì)將頭像、昵稱、消息內(nèi)容、發(fā)送時(shí)間等“結(jié)構(gòu)化”數(shù)據(jù)進(jìn)行序列化,形成一個(gè)字節(jié)流報(bào)文,通過網(wǎng)絡(luò)將該報(bào)文發(fā)送給接收方,接收方進(jìn)行反序列化將報(bào)文重新拆解為頭像、昵稱、消息內(nèi)容、發(fā)送時(shí)間等“結(jié)構(gòu)化”數(shù)據(jù)。
二、自定義協(xié)議設(shè)計(jì)一個(gè)網(wǎng)絡(luò)計(jì)算器
2.1TCP協(xié)議,如何保證接收方收到了完整的報(bào)文呢?
1、我們調(diào)用的所有發(fā)送/接收函數(shù),并不是直接從網(wǎng)絡(luò)中發(fā)送/接收數(shù)據(jù),應(yīng)用層調(diào)用的發(fā)送/接收函數(shù),本質(zhì)是一個(gè)拷貝函數(shù)。
例如客戶端發(fā)送數(shù)據(jù)時(shí),應(yīng)用層調(diào)用發(fā)送函數(shù)將會(huì)把應(yīng)用層的發(fā)送緩沖區(qū)數(shù)據(jù)拷貝至傳輸層的發(fā)送緩沖區(qū)。傳輸層自主決定何時(shí)將發(fā)送緩沖區(qū)的數(shù)據(jù)發(fā)送至網(wǎng)絡(luò)里,再通過網(wǎng)絡(luò)發(fā)送至服務(wù)器的接收緩沖區(qū)中,所以TCP協(xié)議是一種傳輸控制協(xié)議。2、TCP協(xié)議的通信雙方的發(fā)送緩沖區(qū)和接收緩沖區(qū)互不干擾,可以雙向同時(shí)進(jìn)行通信。TCP是一種全雙工的通信協(xié)議。
3、如果TCP服務(wù)器的讀取速度跟不上客戶端的發(fā)送速度,將會(huì)導(dǎo)致服務(wù)器接收緩沖區(qū)積攢大量的報(bào)文,這些報(bào)文數(shù)據(jù)可是一連串的粘連在一起的,如何一條一條的將完整的報(bào)文提取出來呢?使用協(xié)議!協(xié)議設(shè)計(jì)方式:
- 定長(例如規(guī)定該報(bào)文定長為1024字節(jié))
- 特殊符號(hào)(在報(bào)文和報(bào)文之間增加特殊符號(hào))
- 自描述方式(自己設(shè)計(jì)協(xié)議)
本文代碼協(xié)議設(shè)計(jì)如下圖所示:使用該協(xié)議設(shè)計(jì)一個(gè)網(wǎng)絡(luò)計(jì)算器。
????????如果是UDP協(xié)議,UDP客戶端,發(fā)送報(bào)文時(shí)只需創(chuàng)建請(qǐng)求,對(duì)請(qǐng)求進(jìn)行序列化后即可發(fā)送;接收?qǐng)?bào)文時(shí)只需將接收的數(shù)據(jù)進(jìn)行反序列化即可。無需進(jìn)行協(xié)議內(nèi)容的添加與解析。這是因?yàn)閁DP每次發(fā)送與接收都是以數(shù)據(jù)報(bào)的形式,數(shù)據(jù)是完整的,不像TCP是面向字節(jié)流,需要使用相關(guān)的協(xié)議進(jìn)行界定報(bào)文邊界。
2.2自定義協(xié)議的實(shí)現(xiàn)
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
enum
{
OK=0,
DIV_ZERO_ERR,
MOD_ZERO_ERR,
OP_ZERO_ERR,
};
#define SEP " "
#define SEP_LEN strlen(SEP)//不能使用sizeof,用sizeof會(huì)統(tǒng)計(jì)到'\0'
#define LINE_SEP "\r\n"
#define LINE_SEP_LINE strlen(LINE_SEP)
//"_exitcode result" -> "content_len"\r\n"_exitcode result"\r\n
//"_x _op _y" -> "content_len"\r\n"_x _op _y"\r\n
std::string enLength(const std::string& text)//text:_x _op _y。添加協(xié)議規(guī)則,用于構(gòu)建一個(gè)完整的報(bào)文(類似"打包")
{
std::string send_string=std::to_string(text.size());//計(jì)算有效載荷的長度"_x _op _y"
send_string+=LINE_SEP;
send_string+=text;
send_string+=LINE_SEP;
return send_string;
}
//_exitcode result
bool deLength(const std::string& package,std::string* text)//獲取報(bào)文中的有效載荷(類似"解包")
{
auto pos=package.find(LINE_SEP);
if(pos==std::string::npos){return false;}
int textLen=std::stoi(package.substr(0,pos));//計(jì)算有效載荷的長度
*text=package.substr(pos+LINE_SEP_LINE,textLen);
return true;
}
class Request//請(qǐng)求類
{
public:
Request(int x,int y,char op)
:_x(x)
,_y(y)
,_op(op)
{}
Request()
:_x(0)
,_y(0)
,_op(0)
{}
bool serialize(std::string* out)//序列化,將成員變量轉(zhuǎn)字符串
{
//結(jié)構(gòu)化->"_x _op _y"
*out="";//清空string對(duì)象
std::string x_tostring=std::to_string(_x);
std::string y_tostring=std::to_string(_y);
*out=x_tostring+SEP+_op+SEP+y_tostring;//_x _op _y
return true;
}
bool deserialize(const std::string& in)//反序列化
{
//"_x _op _y"->結(jié)構(gòu)化
auto leftSpace=in.find(SEP);//左邊的空格
auto rightSpace=in.rfind(SEP);//右邊的空格
if(leftSpace==std::string::npos||rightSpace==std::string::npos){return false;}
if(leftSpace==rightSpace){return false;}
//子串提取
std::string x_tostring=in.substr(0,leftSpace);
if(rightSpace-(leftSpace+SEP_LEN)!=1){return false;}//表示操作符一定只占1位
_op=in.substr(leftSpace+SEP_LEN,rightSpace-(leftSpace+SEP_LEN))[0];
std::string y_tostring=in.substr(rightSpace+SEP_LEN);
//對(duì)x,y進(jìn)行轉(zhuǎn)換
_x=std::stoi(x_tostring);
_y=std::stoi(y_tostring);
return true;
}
public:
//_x _op _y
int _x;//左操作數(shù)
int _y;//右操作數(shù)
char _op;//操作符
};
class Response//響應(yīng)類
{
public:
Response()
:_exitCode(0)
,_result(0)
{}
Response(int exitCode,int result)
:_exitCode(exitCode)
,_result(result)
{}
bool serialize(std::string* out)//序列化,將成員變量轉(zhuǎn)string對(duì)象
{
*out="";//清空string對(duì)象
std::string outString=std::to_string(_exitCode)+SEP+std::to_string(_result);
*out=outString;
return true;
}
bool deserialize(const std::string& in)//反序列化
{
auto space=in.find(SEP);//找空格
if(space==std::string::npos){return false;}
std::string exitString=in.substr(0,space);
std::string resString=in.substr(space+SEP_LEN);
if(exitString.empty()||resString.empty()){return false;}//一個(gè)字符串為空就false
_exitCode=std::stoi(exitString);
_result=std::stoi(resString);
return true;
}
public:
int _exitCode;//0表示計(jì)算成功,非零代表除零等錯(cuò)誤
int _result;//運(yùn)算結(jié)果
};
bool recvPackage(int sock,std::string& inbuffer,std::string* text)//服務(wù)器/客戶端讀取報(bào)文
{
//將緩沖區(qū)數(shù)據(jù)拆分成一個(gè)個(gè)報(bào)文"content_len"\r\n"_x _op _y"\r\n
char buffer[1024];
while(1)
{
ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);//阻塞式讀取,等價(jià)于read接口
if(n>0)
{
buffer[n]=0;//字符串末尾添加'\0'
inbuffer+=buffer;
//拆分成一個(gè)個(gè)報(bào)文
auto pos=inbuffer.find(LINE_SEP);//找\r\n的起始位置
if(pos==std::string::npos)//沒找到說明暫時(shí)還沒找到\r\n分隔符,跳過本次循環(huán),等待下次讀取
{
continue;
}
std::string textLenString=inbuffer.substr(0,pos);
int textLen=std::stoi(textLenString);//拿出有效載荷的長度
int totalLen=textLenString.size()+2*LINE_SEP_LINE+textLen;//單個(gè)報(bào)文總長度
if(inbuffer.size()<totalLen)//說明緩沖區(qū)長度還不到一個(gè)報(bào)文大小,需要跳過本次循環(huán)繼續(xù)讀取
{
continue;
}
std::cout<<"截取報(bào)文前inbuffer中的內(nèi)容:\n"<<inbuffer<<std::endl;
//走到這里,一定有一個(gè)完整的報(bào)文
*text=inbuffer.substr(0,totalLen);//取出一個(gè)報(bào)文
inbuffer.erase(0,totalLen);//刪掉緩沖區(qū)中剛剛被提取走的報(bào)文數(shù)據(jù)
std::cout<<"截取報(bào)文后inbuffer中的內(nèi)容:\n"<<inbuffer<<std::endl;
break;
}
else
{
return false;
}
}
return true;
}
代碼解釋:
本協(xié)議設(shè)計(jì)了一個(gè)Request請(qǐng)求類和一個(gè)Response響應(yīng)類,請(qǐng)求類存儲(chǔ)計(jì)算器的操作數(shù)和操作符,響應(yīng)類存儲(chǔ)計(jì)算結(jié)果和退出碼。這兩個(gè)類中各自實(shí)現(xiàn)了對(duì)有效載荷的序列化與反序列化的接口。
enLength用于給有效載荷添加報(bào)頭,即自定義協(xié)議的規(guī)則。deLength則用于解析收到的報(bào)文,剔除報(bào)文中的協(xié)議報(bào)頭,提取出其中的有效載荷。協(xié)議是明確緩沖區(qū)中報(bào)文與報(bào)文之間邊界的一種特殊格式,而enLength和deLength用于添加報(bào)頭和去掉報(bào)頭。
2.3自定義協(xié)議在客戶端與服務(wù)器中的實(shí)現(xiàn)
三、使用Json進(jìn)行序列化和反序列化
3.1jsoncpp庫的安裝
????????從上方代碼可以看到,使用string對(duì)象手動(dòng)進(jìn)行序列化與反序列化非常麻煩??梢允褂肑son進(jìn)行序列化與反序列化操作。
????????Json(JavaScript Object Notation)是一種輕量級(jí)的數(shù)據(jù)交換格式,常用于Web應(yīng)用程序中的數(shù)據(jù)傳輸。它是一種基于文本的格式,易于讀寫和解析。Json格式的數(shù)據(jù)可以被多種編程語言支持,包括JavaScript、Python、Java、C#、C++等。Json數(shù)據(jù)由鍵值對(duì)組成,使用大括號(hào)表示對(duì)象,使用方括號(hào)表示數(shù)組。
????????C++使用Json需要包含Jsoncpp庫,yum安裝Jsoncpp庫指令:先執(zhí)行第二句,如果報(bào)錯(cuò)再執(zhí)行第一句!
sudo mv /var/lib/rpm/__db.00* /tmp/&&yum clean all
sudo yum install -y jsoncpp-devel
3.2改造自定義協(xié)議
makefile:使用jsoncpp庫記得在編譯時(shí)加上-ljsoncpp
cc=g++#將cc變量設(shè)置為g++編譯器
LD=#-DMYSELF
.PHONY:all
all:calClient calServer
calClient:calClient.cc
$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
calServer:calServer.cc
$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
.PHONY:clean
clean:
rm -f calClient calServer
改造的協(xié)議:?
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <jsoncpp/json/json.h>
#include <sys/types.h>
#include <sys/socket.h>
enum
{
OK=0,
DIV_ZERO_ERR,
MOD_ZERO_ERR,
OP_ZERO_ERR,
};
#define SEP " "
#define SEP_LEN strlen(SEP)//不能使用sizeof,用sizeof會(huì)統(tǒng)計(jì)到'\0'
#define LINE_SEP "\r\n"
#define LINE_SEP_LINE strlen(LINE_SEP)
//"_exitcode result" -> "content_len"\r\n"_exitcode result"\r\n
//"_x _op _y" -> "content_len"\r\n"_x _op _y"\r\n
std::string enLength(const std::string& text)//text:_x _op _y。添加協(xié)議規(guī)則,用于構(gòu)建一個(gè)完整的報(bào)文(類似"打包")
{
std::string send_string=std::to_string(text.size());//計(jì)算有效載荷的長度"_x _op _y"
send_string+=LINE_SEP;
send_string+=text;
send_string+=LINE_SEP;
return send_string;
}
//_exitcode result
bool deLength(const std::string& package,std::string* text)//獲取報(bào)文中的有效載荷(類似"解包")
{
auto pos=package.find(LINE_SEP);
if(pos==std::string::npos){return false;}
int textLen=std::stoi(package.substr(0,pos));//計(jì)算有效載荷的長度
*text=package.substr(pos+LINE_SEP_LINE,textLen);
return true;
}
class Request//請(qǐng)求類
{
public:
Request(int x,int y,char op)
:_x(x)
,_y(y)
,_op(op)
{}
Request()
:_x(0)
,_y(0)
,_op(0)
{}
bool serialize(std::string* out)//序列化,將成員變量轉(zhuǎn)字符串
{
#ifdef MYSELF
//結(jié)構(gòu)化->"_x _op _y"
*out="";//清空string對(duì)象
std::string x_tostring=std::to_string(_x);
std::string y_tostring=std::to_string(_y);
*out=x_tostring+SEP+_op+SEP+y_tostring;//_x _op _y
#else
//Json序列化
Json::Value root;//Json::Value萬能對(duì)象,可接收任何對(duì)象
root["first"]=_x;//自動(dòng)將_x轉(zhuǎn)換為字符串
root["second"]=_y;
root["oper"]=_op;
//序列化
Json::FastWriter writer;//Json::StyledWriter write;等價(jià)
*out=writer.write(root);//將root進(jìn)行序列化,返回值為string對(duì)象,接收即可
#endif
return true;
}
bool deserialize(const std::string& in)//反序列化
{
#ifdef MYSELF
//"_x _op _y"->結(jié)構(gòu)化
auto leftSpace=in.find(SEP);//左邊的空格
auto rightSpace=in.rfind(SEP);//右邊的空格
if(leftSpace==std::string::npos||rightSpace==std::string::npos){return false;}
if(leftSpace==rightSpace){return false;}
//子串提取
std::string x_tostring=in.substr(0,leftSpace);
if(rightSpace-(leftSpace+SEP_LEN)!=1){return false;}//表示操作符一定只占1位
_op=in.substr(leftSpace+SEP_LEN,rightSpace-(leftSpace+SEP_LEN))[0];
std::string y_tostring=in.substr(rightSpace+SEP_LEN);
//對(duì)x,y進(jìn)行轉(zhuǎn)換
_x=std::stoi(x_tostring);
_y=std::stoi(y_tostring);
#else
//Json反序列化
Json::Value root;//Json::Value萬能對(duì)象,可接收任何對(duì)象
Json::Reader reader;
reader.parse(in,root);//第一個(gè)參數(shù):解析哪個(gè)流;第二個(gè)參數(shù):將解析的數(shù)據(jù)存放到對(duì)象中
//反序列化
_x=root["first"].asInt();//默認(rèn)是字符串,轉(zhuǎn)換為整型
_y=root["second"].asInt();
_op=root["oper"].asInt();//轉(zhuǎn)換為整型,整型可以給char類型。
#endif
return true;
}
public:
//_x _op _y
int _x;//左操作數(shù)
int _y;//右操作數(shù)
char _op;//操作符
};
class Response//響應(yīng)類
{
public:
Response()
:_exitCode(0)
,_result(0)
{}
Response(int exitCode,int result)
:_exitCode(exitCode)
,_result(result)
{}
bool serialize(std::string* out)//序列化,將成員變量轉(zhuǎn)string對(duì)象
{
#ifdef MYSELF
*out="";//清空string對(duì)象
std::string outString=std::to_string(_exitCode)+SEP+std::to_string(_result);
*out=outString;
#else
//Json序列化
Json::Value root;//Json::Value萬能對(duì)象,可接收任何對(duì)象
root["exitCode"]=_exitCode;//自動(dòng)將_exitCode轉(zhuǎn)換為字符串
root["result"]=_result;
//序列化
Json::FastWriter writer;//Json::StyledWriter write;等價(jià)
*out=writer.write(root);//將root進(jìn)行序列化,返回值為string對(duì)象,接收即可
#endif
return true;
}
bool deserialize(const std::string& in)//反序列化
{
#ifdef MYSELF
auto space=in.find(SEP);//找空格
if(space==std::string::npos){return false;}
std::string exitString=in.substr(0,space);
std::string resString=in.substr(space+SEP_LEN);
if(exitString.empty()||resString.empty()){return false;}//一個(gè)字符串為空就false
_exitCode=std::stoi(exitString);
_result=std::stoi(resString);
#else
//Json反序列化
Json::Value root;//Json::Value萬能對(duì)象,可接收任何對(duì)象
Json::Reader reader;
reader.parse(in,root);//第一個(gè)參數(shù):解析哪個(gè)流;第二個(gè)參數(shù):將解析的數(shù)據(jù)存放到對(duì)象中
//反序列化
_exitCode=root["exitCode"].asInt();//默認(rèn)是字符串,轉(zhuǎn)換為整型
_result=root["result"].asInt();
#endif
return true;
}
public:
int _exitCode;//0表示計(jì)算成功,非零代表除零等錯(cuò)誤
int _result;//運(yùn)算結(jié)果
};
bool recvPackage(int sock,std::string& inbuffer,std::string* text)//服務(wù)器/客戶端讀取報(bào)文
{
//將緩沖區(qū)數(shù)據(jù)拆分成一個(gè)個(gè)報(bào)文"content_len"\r\n"_x _op _y"\r\n
char buffer[1024];
while(1)
{
ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);//阻塞式讀取,等價(jià)于read接口
if(n>0)
{
buffer[n]=0;//字符串末尾添加'\0'
inbuffer+=buffer;
//拆分成一個(gè)個(gè)報(bào)文
auto pos=inbuffer.find(LINE_SEP);//找\r\n的起始位置
if(pos==std::string::npos)//沒找到說明暫時(shí)還沒找到\r\n分隔符,跳過本次循環(huán),等待下次讀取
{
continue;
}
std::string textLenString=inbuffer.substr(0,pos);
int textLen=std::stoi(textLenString);//拿出有效載荷的長度
int totalLen=textLenString.size()+2*LINE_SEP_LINE+textLen;//單個(gè)報(bào)文總長度
if(inbuffer.size()<totalLen)//說明緩沖區(qū)長度還不到一個(gè)報(bào)文大小,需要跳過本次循環(huán)繼續(xù)讀取
{
continue;
}
std::cout<<"截取報(bào)文前inbuffer中的內(nèi)容:\n"<<inbuffer<<std::endl;
//走到這里,一定有一個(gè)完整的報(bào)文
*text=inbuffer.substr(0,totalLen);//取出一個(gè)報(bào)文
inbuffer.erase(0,totalLen);//刪掉緩沖區(qū)中剛剛被提取走的報(bào)文數(shù)據(jù)
std::cout<<"截取報(bào)文后inbuffer中的內(nèi)容:\n"<<inbuffer<<std::endl;
break;
}
else
{
return false;
}
}
return true;
}
bool recvPackageAll(int sock,std::string& inbuffer,std::vector<std::string>* out)
{
std::string line;
while(recvPackage(sock,inbuffer,&line))
{
out->push_back(line);
}
}
3.3自定義協(xié)議的命名區(qū)分
????????未來在一套系統(tǒng)中可以自定義多種協(xié)議,為了區(qū)分不同的自定義協(xié)議,可以參照如下格式設(shè)計(jì)協(xié)議的格式:
當(dāng)然前人已經(jīng)設(shè)計(jì)好了常見的網(wǎng)絡(luò)協(xié)議,例如http/https。文章來源:http://www.zghlxwxcb.cn/news/detail-461929.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-461929.html
到了這里,關(guān)于【網(wǎng)絡(luò)編程】協(xié)議定制+Json序列化與反序列化的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!