iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >模拟实现应用层协议
  • 128
分享到

模拟实现应用层协议

网络 2023-09-12 10:09:42 128人浏览 独家记忆
摘要

模拟实现应用层协议 文章目录 模拟实现应用层协议应用层再谈协议 序列化和反序列化 网络版计算器自定义协议利用Json进行序列化和反序列化json库的安装条件编译

模拟实现应用层协议

文章目录

应用层

image-20230829191153866

应用层(Application layer)是OSI模型的第七层。应用层直接和应用程序接口并提供常见的网络应用服务。应用层也向表示层发出请求。应用层是开放系统的最高层,是直接为应用进程提供服务的。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层。

再谈协议

运输层为应用进程提供了端到端的通信服务,但不同的网络应用的应用进程之间,还需要有不同的通信规则,因此在运输层协议之上,还需要有应用层协议。协议作为一种”约定“,那么必须遵守一些准则。对应应用层协议一般要遵守:

  1. 应用进程交换的报文类型,如请求报文和响应报文。
  2. 各种报文类型的语法,如报文中的各个字段及其详细描述。
  3. 字段的语义,即包含在字段中的信息的含义。
  4. 进程何时,如何发送报文,以及对报文进行响应的规则

序列化和反序列化

官方定义:序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

序列化有两个用途:

  • 把对象的字节序列永久保存到硬盘上,通常存放在一个文件中(序列化对象)
  • 在网络上传送对象的字节序列(网络传输对象)

实际上就是将数据持久化,防止一直存储在内存当中,消耗内存资源。而且序列化后也能更好的便于网络运输何传播。

image-20230829223851219

例如在微信里你給对方发送一条消息,实际上会将头像、昵称、消息内容、发送时间等构建一个结构,然后将该结构进行序列化,即将该结构形成一个字节流报文,通过网络将该报文发送給对方,对方进行反序列化,将该报文转化为结构,然后重新拆解为头像、昵称、消息内容、发送时间等。

而序列化还解决了网络传输结构体由于大小端、内存对齐导致数据出错等问题

网络版计算器

现通过指定简单的协议,实现一个服务器版的计算器。我们需要在客户端把要计算的两个数和运算符发过去, 然后由服务器进行计算, 最 后再把结果返回给客户端。

对应网络计算器约定协议:

  • 客户端发送一个形如"1+1"的字符串
  • 这个字符串中有两个操作数都是整形
  • 两个操作数只间有一个运算符,操作数和运算符之间不能有空格

对应网络的序列化和反序列化:

  • 定义一个结构体来表示我们需要交互的信息
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体。这个过程就称为序列化和反序列化

在TCP协议中,如何保证接收方收到的是完整的报文呢?

image-20230829231938344

  • 我们调用的发送或接收函数,本质上是拷贝函数。

  • 应用层调用的发送或接收函数,并不是直接从网络中读取或发送数据。例如客户端在应用层调用发送函数发送数据时,是将数据从应用层的发送缓冲区拷贝一份到传输层的发送缓冲区。然后由传输层自主决定何时将数据发送至网络中,服务器的传输层再通过网络将数据读取到接收缓冲区,然后将数据拷贝一份到应用层的接收缓冲区。因此tcp协议是一种传输控制协议

  • 由于使用在TCP协议的双方都有发送缓冲区和接收缓冲区,即读取数据和发送数据不会互相干扰,可以同时双向传输数据,因此TCP协议是一种全双工的通信协议。

  • 由于TCP协议是一种全双工的通信协议,因此也会产生客户端发送数据的速度远远大于服务器读取数据的速度,此时会造成服务器的接收缓冲区内积攒大量的报文,这些报文是线性连接在一起的,那么如何将一条条报文完整的读取上来呢?**通过指定的协议,按照协议规定的方式读取上来。**协议定制的方式有:

    • 定长:规定该报文的长度
    • 间隔符号:规定报文之间存在间隔符号
    • 自描述方式:自定义协议

该网络版计算器对应的协议如下:

image-20230829235118910

  • 由于UDP协议发送和接收数据都是以数据报的形式,在传输过程中数据是完整的,因此并不需要通过序列和反序列化、对数据添加特殊内容的方式去界定报文边界;而TCP协议发送和接收数据是以字节流的方式,就必须使用相关协议手段去标识、保护报文。

自定义协议

protocol.hpp

#pragma once#include#include#include #include #include using namespace std;#define SEP " "#define SEP_LEN strlen(SEP)//strlen统计'\0'之前的字符个数,而sizeof统计的是所占内存的空间大小,使用sizeof会越界出问题#define LINE_SEP "\r\n"#define LINE_SEP_LEN strlen(LINE_SEP)enum {    NONE=0,    DIV_ZERO,    MOD_ZERO,    OP_ERR};//"x op y"->"text_len"\r\n"x op y"\r\n---給内容加上报头std::string enLength(const std::string& text)//协议定制{    std::string send_str=to_string(text.size());    send_str+=LINE_SEP;    send_str+=text;    send_str+=LINE_SEP;    return send_str;}//"text_len"\r\n"x op y"\r\n -> "x op y"---去掉报头,取出里面的内容bool deLength(const std::string& str,string* ret)//协议定制{    auto it=str.find(LINE_SEP);//找到报头    if(it==std::string::npos) return false;//如果没找到则直接返回    int len=stoi(str.substr(0,it));//取出字符串的长度    *ret=str.substr(it+LINE_SEP_LEN,len);//取出数据    return true;}class Request{public:Request():_x(0),_y(0),_op(0){}Request(int x,int y,int op):_x(x),_y(y),_op(op){}bool Serialize(std::string* out)//序列化,将传入的x op y转化为字符串"x op y"{    *out="";    *out+=to_string(_x);    *out+=SEP;    *out+=to_string(_op);    *out+=SEP;    *out+=to_string(_y);    return true;}bool Deserialize( const string& origin)//反序列化,将传过来的字符串拆出来传参給_x _op _y{//"_xSEP_opSEP_y"-> _x,_op,_y    auto leftit=origin.find(SEP);    cout<<"Deserialize找到了leftSEP: "<<leftit<<endl;    auto rightit=origin.rfind(SEP);    cout<<"Deserialize找到了rightSEP: "<<rightit<<endl;    if(leftit==string::npos|| rightit==string::npos) return false;    if(leftit==rightit) return false;    int opsize=rightit-leftit-1;    cout<<"opsize: "<<opsize<<endl;//1 43 1--leftit=1,rightit=4,opsize=rightit-leftit-1=4-1-1=2;//1 3 1--leftit=1,right=3,opsize=rightit-leftit-1=3-1-1=1   // if(rightit-(leftit+SEP_LEN)!=1) return false;    if(rightit-(leftit+SEP_LEN)!=opsize) return false;    //+号ASCII码是43,从char转int被解析成43即stringlen为两位,这里的运算rightit-(leftit+SEP_LEN)!=1就出问题//4-(1+1)==2;3-(1+1)=1    std::string origin_x=origin.substr(0,leftit);    std::string origin_y=origin.substr(rightit+SEP_LEN);    if(origin_x.empty()) return false;    if(origin_y.empty()) return false;    cout<<"origin_x: "<<origin_x<<" origin_y: "<<origin_y<<endl;    _x=stoi(origin_x);    int opf=stoi(origin.substr(leftit,rightit));    _op=opf;    cout<<"opf: "<<opf<<"_op: "<<_op<<endl;    _y=stoi(origin_y);    return true;}    public:    int _x;    int _y;    char _op;};class Response{public:Response():_exitcode(0),_result(0){}Response(int exitcode,int result):_exitcode(exitcode),_result(result){}bool Serialize(string*out)//序列化{//_exitcode _result ->"_exitcodeSEP_result"*out="";*out+=to_string(_exitcode);*out+=SEP;*out+=to_string(_result);return true;}bool Deserialize(const string& in)//反序列化{//_exitcodeSEP_result"->_exitcode _resultauto pos=in.find(SEP);if(pos==string::npos) return false;string excstr=in.substr(0,pos);string resstr=in.substr(pos+SEP_LEN);if(excstr.empty()||resstr.empty()) return false;_exitcode=stoi(excstr);_result=stoi(resstr);return true;}public:int _exitcode;//退出码int _result;//结果};//"text_len"\r\n"x op y"\r\nbool recvPackage(int sock,string& inbuffer,string*out){char buffer[1024];while(true){ssize_t  s=recv(sock,buffer,sizeof(buffer)-1,0);if(s>0){    buffer[s]=0;    inbuffer+=buffer;    auto pos=inbuffer.find(LINE_SEP);    if(pos==string::npos)continue;//没找到报头和有效载荷之间的分隔符---如果字节流式的报文没读全就继续读    string text_len=inbuffer.substr(0,pos);//报头是有效载荷的长度    int len=stoi(text_len);        int totallen=text_len.size()+LINE_SEP_LEN*2+len;//整个报文的长度    if(inbuffer.size()<totallen)     {        cout<<"输入的消息不完整,请继续输入.continue..."<<endl;        continue;//报文没读完继续读    }        cout<<"处理前的inbuffer: \n"<<inbuffer<<endl;    *out=inbuffer.substr(0,totallen);    inbuffer.erase(0,totallen);    cout<<"处理后的inbuffer: \n"<<inbuffer<<endl;    break;}else return false;}return true;}

介绍一下:

  • 在Request类内:
  1. 外部传参两个操作数和一个运算符进来构造Request对象,调用Serialize序列化函数构造字符串"_xSEP_opSEP_y",其中SEP是空格, _x是左操作数, _op是运算符, _y是右操作数
  2. 外部传参数字符串"_xSEP_opSEP_y"进来,调用Deserialize反序列化通过该字符串构造内嵌参数 _x, _op, _y。注意的是,运算符 _op在传入时会将符号转化成其对应的ASCII码。例如"+“会被转化成"43”,此时需要将字符串里的"43"取出来再转化成整形43,方便后续转化回"+"
  • 在Response类内:
  1. 外部传字符串"_exitcodeSEP_result"进来,调用Deserialize反序列化函数构造内嵌参数 _exitcode和 _result。其中 exitcode是退出码,当计算结果正确时退出码为NONE。计算时出现除零错误退出码为DIV_ZERO。计算时出现商零错误退出码为MOD_ZERO。传入参数时运算符传入错误,退出码为OP_ERR; _result是计算结果。
  2. 调用Serialize序列化函数利用内嵌参数 _exitcode和 _result构造字符串" _exitcodeSEP_result"
  • enLength协议定制函数,給传入的字符串加上"报头",传入字符串"xSEPopSEPy",加上"报头后字符串为"text_len"\r\n"xSEPopSEPy"\r\n。其中"xSEPopSEPy"为有效载荷,text_len为有效载荷的长度,SEP是空格。

  • deLength协议定制函数。函数作用是給传入的字符串str去"报头",并把有效载荷通过ret传出去。传入的字符串是"text_len"\r\n"xSEPopSEPy"\r\n,去掉"报头"后,取出字符串 “xSEPopSEPy”。其中SEP是空格

  • recvPackage函数是供服务器调用接收数据包的函数。服务器接收客户端发送来的报文,若报文不符合定义的协议形式或者没读上来的报文不是完整的则阻塞式读取,直到读上来完整的报文。然后通过输出型参数out将报文传出去。

calserver.cc

#include"calserver.hpp"#include"log.hpp"#include#include#includeusing namespace Server;using namespace std;static void Usage(string proc){    cout<<"\nUsage:\n\t"<<proc<<" local_port\n\n"<<endl;}//req是一个已经处理的完整的对象bool cal(const Request& req,Response& rep)//根据req填充rep {switch(req._op){    case '+':    rep._result=req._x+req._y;    break;    case '-':        rep._result=req._x-req._y;    break;        case '*':        rep._result=req._x*req._y;    break;        case '/':    {        if(req._y==0) rep._exitcode=DIV_ZERO;        else        rep._result=req._x/req._y;    }       break;        case '%':    {        if(req._y==0) rep._exitcode=MOD_ZERO;        else        rep._result=req._x%req._y;    }    break;    default:    rep._exitcode=OP_ERR;    break;}return true;}int main(int arGC,char* argv[]){if(argc!=2){    Usage(argv[0]);    exit(USAGE_ERR);}uint16_t port=atoi(argv[1]);//将字符串转化为整数unique_ptr<calserver> ts(new calserver(port));ts->initserver();ts->start(cal);return 0;}
  • cal是计算函数,传入Request对象和Response对象。Request对象中参数有两个操作数 _ x、_ y 和一个运算符 _ op,计算出结果放到Response对象的参数_result中,退出码放到 _exitcode。

calserver.hpp

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include#include"log.hpp"#include"protocol.hpp"#define NUM 1024using namespace std;static const int gbacklog = 5;namespace Server{    enum    {        USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR    };typedef function<bool(const Request&req,Response& res)> func_t;void handlerentry(int sock,func_t func){     string inbuffer;    while(true)    {    //1. 获取客户端发送来的数据报,确定数据报是带报头的数据报    //"text_len"\r\n"x op y"\r\n    string req_text,req_str;    if(!recvPackage(sock,inbuffer,&req_text)) return;    cout<<"带报头的请求(数据报): "<<req_text<<endl;    //2.对数据报进行反序列化    //"text_len"\r\n"x op y"\r\n -> "x op y"---去掉报头,取出里面的内容    if(!deLength(req_text,&req_str)) return;    cout<<"去掉报头的请求(数据报):"<<req_str<<endl;    //走到这里再往下就卡主了,只打印到上面那条日志后面都没打印到!!!    //3.获得一个结构化的请求对象    Request req;   if(!req.Deserialize(req_str)) return;//如果反序列化失败直接返回    //4.对对象进行操作---进行服务器业务    //4.1.获得一个结构化响应     Response rep;    func(req,rep);    //5.对对象进行序列化    //_exitcode _result ->"_exitcodeSEP_result"    string rep_str;    rep.Serialize(&rep_str);    cout<<"计算完成后的响应: "<<rep_str<<endl;    //6.給有效载荷加上报头     //"exitcode result" -> "content_len"\r\n"exitcode result"\r\n    string rep_text=enLength(rep_str);    cout<<"加上报头的完整响应(报文): "<<rep_text<<endl;    //7.把报文发送回給客户端    send(sock,rep_text.c_str(),rep_text.size(),0);        }    }typedef function<bool(const Request&req,Response& res)> func_t;class calserver{ public:calserver(const uint16_t& port):_port(port),_listensock(-1){}void initserver(){//1.创建套接字_listensock=Socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){    logMessage(FATAL,"create listensocket error");    exit(SOCK_ERR);} logMessage(NORMAL, "create socket success: %d", _listensock);//2.bind ip和portstruct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败{    logMessage(FATAL,"bind error");    exit(BIND_ERR);} logMessage(NORMAL,"bind success");//3.将套接字设置为监听模式if(listen(_listensock,gbacklog)<0){    logMessage(FATAL,"listen error");    exit(LISTEN_ERR);}logMessage(NORMAL,"listen success");}void start(func_t fun){    while(true)    {        struct sockaddr_in cli;        socklen_t len=sizeof(cli);        bzero(&cli,len);        int sock=accept(_listensock,(struct sockaddr*)&cli,&len);        if(sock<0)        {            logMessage(FATAL,"accept client error");            continue;        }        logMessage(NORMAL,"accept client success");        cout<<"accept sock: "<<sock<<endl;        //多进程版---        //一个客户端占用一个文件描述符,原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的,而每次客户端进来都要占用新的文件描述符        //因此若接收多个客户端不退出的话文件描述符会越来越少。         pid_t id=fork();//创建子进程         if(id==0)//子进程进入         {             close(_listensock);//子进程不需要用于监听因此关闭该文件描述符             handlerentry(sock,fun);             close(sock);             exit(0);        }        //父进程        close(sock);        pid_t ret=waitpid(id,nullptr,0);        if(ret<0)        {            cout << "waitsuccess: " << ret << endl;        }    }}~calserver(){}private:int _listensock;//用于监听服务器的sock文件描述符uint16_t _port;//端口号};}
  • handlerentry函数是供服务器调用的接收发送函数。传入参数为用于通信的文件描述符sock,调用的计算函数cal(cal在calserver.cc文件中)。服务器调用handlerentry函数,接收到客户端发送来的报文,形式形如"text_len"\r\n"x op y"\r\n;调用deLength函数去"报头",转化后的字符串形如"x op y";调用Request对象的反序列函数Deserialize,通过字符串"x op y"填充Request对象的 _ x、_ op、 _ y参数;调用func即cal函数利用Request对象内的参数计算得出结果并构造Response对象;调用Response对象的序列化函数将 参数_exitcode _result 转化为字符串" _exitcodeSEP_result";调用enLength函数加上报头,转化后的字符串形如"content_len"\r\n"exitcode result"\r\n;然后通过send函数将字符串发送給客户端。
  • 注意的是服务器是多进程版,即能够与多个客户端进行并行通信。

calclient.cc

#include#include#include#include"calclient.hpp"using namespace std;using namespace client;static void Usage(string proc){    cout<<"\nUsage :\n\t"<<proc<<" serverip serverport\n"<<endl;}int main(int argc, char* argv[]){    if(argc!=3)    {        Usage(argv[0]);        exit(1);    }string serverip=argv[1];uint16_t serverport=atoi(argv[2]);unique_ptr<calclient> tc(new calclient(serverip,serverport));tc->initclient();tc->start();    return 0;}

calclient.hpp

#pragma once#include #include #include #include #include #include #include #include #include #include"protocol.hpp"using namespace std;#define NUM 1024namespace client{    class calclient{public:calclient(const string& ip,const uint16_t& port):_sock(-1),_port(port),_ip(ip){}void initclient(){//1.创建sockfd_sock=socket(AF_INET,SOCK_STREAM,0);if(_sock<0){   cerr<<"socket create error"<<endl;   exit(2);}//2.绑定 ip port,不显示绑定,OS自动绑定}void start(){struct sockaddr_in ser;bzero(&ser,sizeof(ser));socklen_t len=sizeof(ser);ser.sin_family=AF_INET;ser.sin_port=htons(_port);ser.sin_addr.s_addr=inet_addr(_ip.c_str());if(connect(_sock,(struct sockaddr *)&ser,len)!=0){    cerr<<"connect error"<<endl;}else{    string line;    string inbuffer;    while(true)    {        cout<<"mycal>>: ";//输入"xopy"        getline(cin,line);        Request req=ParseLine(line);//用"xopy"取出x op y构造Request对象        string context;        req.Serialize(&context);//序列化,用x op y构造字符串"xSEPopSEPy"        string send_str=enLength(context);//定制协议---"x op y"->"text_len"\r\n"x op y"\r\n---給内容加上报头        cout<<"calclient send str: "<<send_str<<endl;        send(_sock,send_str.c_str(),send_str.size(),0);//客户端把报文发送給服务器        string package;        if(!recvPackage(_sock,inbuffer,&package)) continue;//服务器处理完数据,客户端接收服务器发送来的报文        //  "content_len"\r\n"exitcode result"\r\n        string reser_len;        if(!deLength(package,&reser_len)) continue;//去报头         //  "content_len"\r\n"exitcode result"\r\n -> "exitcode result"        Response rep;        rep.Deserialize(reser_len);//反序列化://_exitcodeSEP_result"->_exitcode _result        cout<<"_exitcode: "<<rep._exitcode<<endl;        cout<<"_result: "<<rep._result<<endl;    }}}~calclient(){    if(_sock>=0) close(_sock);}Request ParseLine(const string& line){//"xopy"->取出来到x op y 上int i=0;int status=0;int num=line.size();string left,right;char op;while(i<num){switch(status){    case 0:    {        if(!isdigit(line[i]))    {        op=line[i];//取出运算符**        status=1;    }else    left.push_back(line[i++]);//取出左操作数    }    break;    case 1:    i++;    status=2;    break;    case 2:    right.push_back(line[i++]);    break;}}cout<<"left: "<<stoi(left)<<" op: "<<op<<" right: "<<stoi(right)<<endl;return Request(stoi(left),stoi(right),op);//返回Request对象}private:int _sock;uint16_t _port;string _ip;};}
  • ParseLine函数接收形如"1+1"的字符串,解析字符串后构造Request对象
  • 客户端调用start函数,接收命令行发送来的形如"1+1"的字符串,然后调用ParseLine函数构造Request对象。调用Request对象的序列化函数Serialize构造字符串"xSEPopSEPy"。调用协议定制函数enLength給字符串加上"报头",转化后的字符串为"text_len"\r\n"x op y"\r\n;调用send函数将字符串发送給服务器;服务器计算完成后将结果发送回来,调用recvPackage函数接收服务器发送回来的字符串,字符串形如 “content_len”\r\n"exitcode result"\r\n;调用deLength函数去报头,转化后的字符串形如"exitcode result";调用Response对象的反序列化函数Deserialize通过字符串获取参数_exitcode _result,并进行打印。

makefile

.PHONY:allall:calclient calservercalclient:calclient.ccg++ -o $@ $^ -std=c++11calserver:calserver.ccg++ -o $@ $^ -std=c++11 .PHONY:cleanclean:rm -rf calserver calclient

image-20230830121135167

利用JSON进行序列化和反序列化

jsON(javascript Object Notation)是一种轻量级的数据交换格式,它以易于阅读和写作的文本形式表示结构化数据。JSON由两种结构构成:键值对(键值对集合)和值的有序列表。在这里以键值对的方式使用。

json库的安装

输入以下指令安装

sudo yum install -y jsoncpp-devel

安装完后可通过ls查询

image-20230830151558485

makefile

cc=g++LD=-DMYPRO.PHONY:allall:calclient calservercalclient:calclient.cc$(cc) -o $@ $^ -std=c++11 -ljsoncpp #${LD}calserver:calserver.cc$(cc) -o $@ $^ -std=c++11 -ljsoncpp #${LD}.PHONY:cleanclean:rm -rf calserver calclient
  • 如果不使用Json序列化就注释掉第二行LD=-DMYPRO

protocol.hpp

#pragma once#include#include#include #include #include #include using namespace std;#define SEP " "#define SEP_LEN strlen(SEP)//strlen统计'\0'之前的字符个数,而sizeof统计的是所占内存的空间大小,使用sizeof会越界出问题#define LINE_SEP "\r\n"#define LINE_SEP_LEN strlen(LINE_SEP)enum {    NONE=0,    DIV_ZERO,    MOD_ZERO,    OP_ERR};//"x op y"->"text_len"\r\n"x op y"\r\n---給内容加上报头std::string enLength(const std::string& text)//协议定制{    std::string send_str=to_string(text.size());    send_str+=LINE_SEP;    send_str+=text;    send_str+=LINE_SEP;    return send_str;}//"text_len"\r\n"x op y"\r\n -> "x op y"---去掉报头,取出里面的内容bool deLength(const std::string& str,string* ret)//协议定制{    auto it=str.find(LINE_SEP);//找到报头    if(it==std::string::npos) return false;//如果没找到则直接返回    int len=stoi(str.substr(0,it));//取出字符串的长度    *ret=str.substr(it+LINE_SEP_LEN,len);//取出数据    return true;}class Request{public:Request():_x(0),_y(0),_op(0){}Request(int x,int y,int op):_x(x),_y(y),_op(op){}bool Serialize(std::string* out)//序列化,将传入的x op y转化为字符串"x op y"{#ifdef MYPRO    *out="";    *out+=to_string(_x);    *out+=SEP;    *out+=to_string(_op);    *out+=SEP;    *out+=to_string(_y);#elseJson::Value root;//json的对象是键值对[key,value]root["first"]=_x;//int类型被设置进json的键值对时自动转换为string类型root["second"]=_y;root["oper"]=_op;Json::FastWriter writer;*out=writer.write(root);//调用接口序列化返回值为字符串#endif    return true;}bool Deserialize( const string& origin)//反序列化,将传过来的字符串拆出来传参給_x _op _y{//"_xSEP_opSEP_y"-> _x,_op,_y#ifdef MYPRO    auto leftit=origin.find(SEP);    cout<<"Deserialize找到了leftSEP: "<<leftit<<endl;    auto rightit=origin.rfind(SEP);    cout<<"Deserialize找到了rightSEP: "<<rightit<<endl;    if(leftit==string::npos|| rightit==string::npos) return false;    if(leftit==rightit) return false;    int opsize=rightit-leftit-1;    cout<<"opsize: "<<opsize<<endl;//1 43 1--leftit=1,rightit=4,opsize=rightit-leftit-1=4-1-1=2;//1 3 1--leftit=1,right=3,opsize=rightit-leftit-1=3-1-1=1   // if(rightit-(leftit+SEP_LEN)!=1) return false;    if(rightit-(leftit+SEP_LEN)!=opsize) return false;    //+号ASCII码是43,从char转int被解析成43即stringlen为两位,这里的运算rightit-(leftit+SEP_LEN)!=1就出问题//4-(1+1)==2;3-(1+1)=1    std::string origin_x=origin.substr(0,leftit);    std::string origin_y=origin.substr(rightit+SEP_LEN);    if(origin_x.empty()) return false;    if(origin_y.empty()) return false;    cout<<"origin_x: "<<origin_x<<" origin_y: "<<origin_y<<endl;    _x=stoi(origin_x);    int opf=stoi(origin.substr(leftit,rightit));    _op=opf;    cout<<"opf: "<<opf<<"_op: "<<_op<<endl;    _y=stoi(origin_y);#elseJson::Value root;Json::Reader reader;reader.parse(origin,root);//反序列化,将字符串中的协议字符串填进对象对应的元素中_x=root["first"].asInt();_y=root["second"].asInt();_op=root["oper"].asInt();#endif    return true;}    public:    int _x;    int _y;    char _op;};class Response{public:Response():_exitcode(0),_result(0){}Response(int exitcode,int result):_exitcode(exitcode),_result(result){}bool Serialize(string*out)//序列化{//_exitcode _result ->"_exitcodeSEP_result"#ifdef MYPRO*out="";*out+=to_string(_exitcode);*out+=SEP;*out+=to_string(_result);#elseJson::Value root;root["exitcode"]=_exitcode;root["result"]=_result;Json::FastWriter writer;*out= writer.write(root);#endifreturn true;}bool Deserialize(const string& in)//反序列化{//_exitcodeSEP_result"->_exitcode _result#ifdef MYPROauto pos=in.find(SEP);if(pos==string::npos) return false;string excstr=in.substr(0,pos);string resstr=in.substr(pos+SEP_LEN);if(excstr.empty()||resstr.empty()) return false;_exitcode=stoi(excstr);_result=stoi(resstr);#elseJson::Value root;Json::Reader reader;reader.parse(in,root);_exitcode=root["exitcode"].asInt();_result=root["result"].asInt();#endifreturn true;}public:int _exitcode;//退出码int _result;//结果};//"text_len"\r\n"x op y"\r\nbool recvPackage(int sock,string& inbuffer,string*out){char buffer[1024];while(true){ssize_t  s=recv(sock,buffer,sizeof(buffer)-1,0);if(s>0){    buffer[s]=0;    inbuffer+=buffer;    auto pos=inbuffer.find(LINE_SEP);    if(pos==string::npos)continue;//没找到报头和有效载荷之间的分隔符---如果字节流式的报文没读全就继续读    string text_len=inbuffer.substr(0,pos);//报头是有效载荷的长度    int len=stoi(text_len);        int totallen=text_len.size()+LINE_SEP_LEN*2+len;//整个报文的长度    if(inbuffer.size()<totallen)     {        cout<<"输入的消息不完整,请继续输入.continue..."<<endl;        continue;//报文没读完继续读    }        cout<<"处理前的inbuffer: \n"<<inbuffer<<endl;    *out=inbuffer.substr(0,totallen);    inbuffer.erase(0,totallen);    cout<<"处理后的inbuffer: \n"<<inbuffer<<endl;    break;}else return false;}return true;}
  • Json序列化在Request类和Response类中使用。

条件编译

  • #ifdef指令说明,如果预处理已经定义了后面的标识符(DEBUG),即DEBUG为真,则执行 #ifdef 与 #else 之间的所有所有代码,不执行#else之后的代码。若DEBUFG为未定义,即DEBUG为假,则执行#else与#endif之间的代码。#endif 用于结束该条件编译指令。#ifdef和#endif搭配使用。

格式

#ifdef DEBUG//......#else//......#endif
//Request类内的Json序列化片段-SerializeJson::Value root;//json的对象是键值对[key,value]root["first"]=_x;//int类型被设置进json的键值对时自动转换为string类型root["second"]=_y;root["oper"]=_op;Json::FastWriter writer;*out=writer.write(root);//调用接口序列化返回值为字符串
  • 创建一个Json的Value对象,键为"first"对应的值为_x,将操作数和运算符设置进Value对象里,然后通过Json的FastWriter对象调用write进行序列化。
Request类内的Json反序列化片段-DeserializeJson::Value root;Json::Reader reader;reader.parse(origin,root);//反序列化,将字符串中的协议字符串填进对象对应的元素中_x=root["first"].asInt();_y=root["second"].asInt();_op=root["oper"].asInt();
  • 创建一个Json的Value对象,然后再创建一个Json的Reader对象,调用Reader对象的parse把携带协议的字符串填进Value对象对应的元素里。然后再通过键值对的方式把元素取出。

image-20230830150840323

来源地址:https://blog.csdn.net/m0_71841506/article/details/132583387

--结束END--

本文标题: 模拟实现应用层协议

本文链接: https://www.lsjlt.com/news/404329.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 模拟实现应用层协议
    模拟实现应用层协议 文章目录 模拟实现应用层协议应用层再谈协议 序列化和反序列化 网络版计算器自定义协议利用Json进行序列化和反序列化json库的安装条件编译 ...
    99+
    2023-09-12
    网络
  • 【网络原理】应用层协议 与 传输层协议
    ✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 目 录 🏉一. 应用层协议⚾️二. 传输层协议👒1. UDP 协议...
    99+
    2023-08-18
    网络 服务器 网络协议
  • OSI七层协议模型及其协议
    文章目录 一、OSI七层模型是什么?其协议有哪些?二、TCP/IP四层协议,TCP/IP五层协议,OSI七层协议是什么? 一、OSI七层模型是什么?其协议有哪些? 二、TCP/IP四层协议...
    99+
    2023-09-03
    网络 服务器 tcp/ip
  • Java应用层协议WebSocket实现消息推送
    目录前言浏览器端服务器端前言   大部分的web开发者,开发的业务都是基于Http协议的:前端请求后端接口,携带参数,后端执行业务代码,再返回结果给前端。作者参与...
    99+
    2023-02-22
    Java WebSocket Java WebSocket消息推送
  • Java应用层协议WebSocket如何实现消息推送
    这篇“Java应用层协议WebSocket如何实现消息推送”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java应用层协议W...
    99+
    2023-07-05
  • java支持哪些应用层协议
    java中支持的应用层协议有:1.HTTP协议,超文本传输协议;2.DNS协议,将域名转换为IP地址;3.FTP协议,高精度时间同步协议;4.SMTP协议,电子邮件传输协议;java中支持的应用层协议有以下几种HTTP协议HTTP协议是一种...
    99+
    2024-04-02
  • java 中如何实现模拟TCP协议进行传输数据
    java 中如何实现模拟TCP协议进行传输数据?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、创建TCP传输的客户端建立TCP客户端的Socket服务,使用的是Socke...
    99+
    2023-05-31
    java tcp ava
  • 学习网络编程No.8【应用层协议之HTTP】
    引言: 北京时间:2023/10/9/13:03,一晃好多天过去了,9月14号的文章终于在昨天发出去了,也是许久没有更文了,国庆放假期间由于各种原因,在王者峡谷和铲子世界遨游的不亦乐乎,有待改善!目前...
    99+
    2023-10-26
    1024程序员节 网络 http
  • java实现mqtt协议
    1.简介 MQTT(message queuing telemetry transport)是IBM开发的即时通讯协议,是一种发布/订阅极其轻量级的消息传输协议,专门为网络受限设备、低宽带以及高延迟和...
    99+
    2023-09-04
    java
  • golang实现ptp协议
    PTP(Precision Time Protocol)是一种时间同步协议,它能够在分布式系统中实现子微秒级别的时间同步。在工业自动化、网络通讯等领域,时间同步是非常重要的。PTP协议的实现一直是个热门的话题,而Golang是一种高效的编程...
    99+
    2023-05-15
  • golang 实现redis协议
    随着互联网的快速发展,现代应用程序对高效、可伸缩的数据存储的需求越来越大。Redis,一个开源的键值对数据库,经常被用作软件架构中的缓存、消息队列、分布式锁等多种用途。Redis文档中有介绍它的通信协议,而这种协议也赋予了开发一种实现Red...
    99+
    2023-05-15
  • opc协议golang实现
    随着工业自动化的不断发展,OPC协议越来越成为工业领域中数据通讯的主流协议。而Golang作为一种高性能、可靠性强的编程语言,也越来越受到工业自动化领域的关注。本文将介绍如何使用Golang语言实现OPC协议的数据通讯,以帮助读者更好地理解...
    99+
    2023-05-14
  • tcp/ip协议采用了几层的层级结构
    这篇文章给大家分享的是有关tcp/ip协议采用了几层的层级结构的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。tcp/ip协议采用4层的层级结构:1、网络接口层(主机-网络层);2、网际层,提供简单灵活的、无连接的...
    99+
    2023-06-15
  • 【应用】OPC 通讯协议
    OPC 通讯协议 OPC 通讯协议基础OPC 简介OPC 与 OPC UAOPC 逻辑对象模型OPC 通信方式Java 实现 OPC 的方式 Java 实现 OPC-clientOPC-D...
    99+
    2023-10-24
    网络 java 网络协议
  • Python底层技术揭秘:如何实现TCP/IP协议栈
    Python底层技术揭秘:如何实现TCP/IP协议栈,需要具体代码示例引言:随着互联网的快速发展,TCP/IP协议成为了现代互联网中最重要的协议之一。对于想要深入了解网络通信底层原理的开发者来说,了解TCP/IP协议栈的实现原理将是一个非常...
    99+
    2023-11-08
    实现 TCP/IP 底层技术
  • 使用 Java 类 实现Http协议
    目录Java实现Http协议一、协议请求的定义二、响应协议的定义三、编码常量定义四、客户端的实现五、服务端的实现六、ProtocolUtils工具类的实现七、ByteUtils类的实...
    99+
    2024-04-02
  • python实现onvif协议-2
    from suds.client import Client from suds.wsse import Security from suds_pa...
    99+
    2023-01-31
    协议 python onvif
  • 计算机网络中常用的应用层协议包括哪些
    这篇文章主要为大家展示了“计算机网络中常用的应用层协议包括哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“计算机网络中常用的应用层协议包括哪些”这篇文章吧。网络协议指的是计算机网络中互相通信的...
    99+
    2023-06-15
  • 深入探究Python底层技术:如何实现网络协议
    Python语言是一门高级编程语言,开发者通常不需要过于关注其底层技术实现。然而,当涉及到实现网络协议时,我们需要深入了解其底层技术,以便正确实现和优化网络应用程序。本文将深入探究Python底层技术,以实现一个简单的网络协议为例,提供具体...
    99+
    2023-11-08
    实现 网络协议 Python底层技术
  • Vue模拟响应式原理底层代码实现的示例
    目录1.Vue.js功能:2.Observer.js功能(数据劫持):3.Compiler.js功能:4.Dep.js功能:5.Watcher.js功能:整体分析Vue的基本结构如下...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作