文章目录

  • 序列化和反序列化
  • 网络计算器
    • 没有使用组件
    • jsoncpp组件的使用
      • jsontest
      • 网络在线计算器

自顶向下去写,我们现在每次写的协议都是基于上一层的协议

序列化和反序列化

我们程序员写的一个一个程序,都是在应用层

我们发送的数据都是结构化的数据,这种结构话的数据就很好看也很好使用

struct message{我的昵称:会跳的鹅我的头像:唐老鸭.png我的消息:在吗?消息时间:2022-07-10 113159}

这就是一个结构化的消息,我们要把这个结构化的数据转化成一个从“字符串”,我们要把这个结构化的数据
“{昵称:xxx,头像:xxx,消息:xxx}”,把它传送到网络里面,到对端之后,再把它解析出来,读取到对应的结构体里面

序列化:把结构化的数据转化为字符串的过程就是序列化的过程,

反序列化:把字符串转化为结构体信息就是反序列化的过程,因为结构化的数据,再网络里面不方便传输,字符串便于网络传输

为什么要进行序列化和反序列化

  1. 为了应用层网络通信的方便
  2. 为了方便上层进行使用内部成员,将应用和网络进行解耦,我们只关心使用,不关心发送的过程

而我们之前使用的TCP和UDP是没有序列化的过程,我们必须要有结构化的数据
而这些序列化和反序列化的数据,它实际上就是协议的表现

我们自己写一个实现序列化和反序列化
也可以直接使用别人写好的组件,java里面(json,xml,protobuff)

我们这里就使用jsoncpp
jsoncpp的安装

sudo yum install -y jsoncpp-devel

网络计算器

我们这里自己定制协议

没有使用组件

发送端口
伪代码

string x="123";string opt="+";string y="321";string data=x+opt+y;//send(data);//这个就是序列化的过程,转化成字符串再发送过去

接收端

recv(data);recv="123+321";int opt==recv.find("+");string x=recv.substr(0,opt);//string y=recv.substr(opt);int _x=to_int(x);int _y=to_int(y);int z=x+y;string _z=to_string(z)send(z)//这就是一个反序列化的过程,接收到了一个字符串,再把它弄成结构体//最后还需要再序列化

这个方案,我们是把数据分开了
我们发现这个过程实际上是非常麻烦的 ,都由我们自己做就很麻烦

jsoncpp组件的使用

约定方案2

  • 定义结构体标识我们需要交互的信息
  • 发送数据时将这个结构i体按照一个规则转化成字符串,接收数据的时候再按照相同的规则把字符串转化为结构体
  • 这个过程就叫做“序列化”和“反序列化的过程”
struct request{int x;int y;char op;}struct request req={10,20,"+"};write(sock,&req,sizeof(req));//这样子发送的时候,实际上就是直接把序列化的数据发送过去了//接收struct request req;read(sock,&req,sizeof(req));//这样的话就缺少了序列化的过程,不太推荐

jsontest

测试json

test.cpptest.cpp test.cpp

#include#include#includeusing namespace std;//仅仅是了解一下序列化和反序列化的过程struct request_t{int x;// 10int y;// 0char opt; //我们协议上是支持//request_t() = default;//生成默认的构造函数}; //请求协议int main(){request_t req={10,20,'*'};//现在我们有了这个结构化的数据,所以我们就需要把它进行序列化Json::Value root;//可以承转任意对象,json是一种kv式的序列化方案//要序列化的对象先装到一个value对象里面root["datax"]=req.x;root["datay"]=req.y;root["operator"]=req.opt;//FastWriter StyledWriter 有这两种类型,这是一整行没有分层的// Json::StyledWriter writer;//我们定义了一个json里面Writer类,writer对象,这个writer式一种分层的Json::FastWriter writer;//我们定义了一个json里面Writer类,writer对象,这个writer式一种分层的//而FastWriter 就是一种正常的字符串样子string json_string=writer.write(root);//这里的返回值是一个string类型的对象//现在我们就完成了一个序列化的过程cout<<json_string<<endl;//序列化之后就能发送给对端了//接下来就需要反序列化//假如说对端发送的是这个string jsontostruct=R"({"datax":10,"datay":20,"operator":42})";//R是把()里面的东西当中最原始的东西来看待,避免对里面的""做转义Json::Reader reader;//调用里面的读取Json::Value rooter;//将字符串翻译成结构化的数据reader.parse(jsontostruct,rooter);request_t reqr;reqr.x=rooter["datax"].asInt();//类似于map,我们定义的datax=x,把它当作一个整数来看待reqr.y=rooter["datay"].asInt();reqr.opt=(char)root["operator"].asUInt();//这样就可以获得他的对应的东西了,强转成char类型cout<<reqr.opt<<endl;cout<<reqr.x<<reqr.opt<<reqr.y<<endl;// cout<<jsontostruct<<endl;//这样就读取到里面的内容了return 0;}

网络在线计算器

Sock.hppSock.hpp Sock.hpp

#include #include using namespace std;#include#include#include#include#include#include#include//这些静态的套接字都是属于类而不属于对象class Sock{public:static intSocket(){int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){perror("socket");exit(2);}return sockfd;}static void Listen(int sockfd){if(listen(sockfd,5)<0){perror("listen");exit(4);}}static void Bind(int sockfd,uint16_t port){struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_addr.s_addr=INADDR_ANY;local.sin_port=htons(port);if(bind(sockfd,(struct sockaddr*)&local,sizeof(local))<0){perror("bind");exit(3);}}static int Accept(int sockfd){struct sockaddr_in peer;bzero(&peer,0);socklen_t len=sizeof(peer);int fd=accept(sockfd,(struct sockaddr*)&peer,&len);if(fd<0){perror("accept");exit(5);}return fd;}static void Connect(int sockfd,string ip,int port){struct sockaddr_in svr;memset(&svr,0,sizeof(svr));svr.sin_family=AF_INET;svr.sin_port=htons(port);svr.sin_addr.s_addr=inet_addr(ip.c_str());if(connect(sockfd,(struct sockaddr*)&svr,sizeof(svr))<0){perror("connect");exit(6);}}};

protocol.hppprotocol.hpp protocol.hpp

//我们在通信的时候要自己定制协议//客户端和服务器要进行计算器的功能,我们要有请求有响应//这个本质上是一个应用层网络服务#pragma once#include #include #include #include using namespace std;//定制协议的过程,目前就是定制结构化数据的过程//请求格式//但是向这种,如果面对的是老客户端,一旦有一个字节没有办法发送过来,就出现了错误//我们需要序列化这个东西struct request_t{int x;// 10int y;// 0char opt; //我们协议上是支持//request_t() = default;}; //请求协议//这里我们写一个响应格式struct response_t{int code; //程序运算完毕的计算状态,code=0(success),code=-1(:\0),code=-2(%0),先检测code,得到result才有意义int result; //计算结果,能否区分是正常的计算结果,还是异常退出结果};//这里的话我们实现一个序列化请求的函数request_t ---->stringstring ReqSerialize(const request_t &req){Json::Value root;root["one"] = req.x;root["two"] = req.y;root["operator"] = req.opt;Json::FastWriter writer;string sendwriter = writer.write(root);//调用write之后,就实现了序列化return sendwriter; //返回序列化之后的字符串}//这里实现一个反序列化的函数// string--->request_tvoid ReqReSerialize(const string &jsonstring, request_t &req){Json::Reader reader;Json::Valueroot;reader.parse(jsonstring, root); //解析进行反序列化req.x = root["one"].asInt();req.y = root["two"].asInt();req.opt =(char) root["operator"].asUInt();}//序列化响应的函数response_t ----->stringstring RespSerialize(const response_t &resp){Json::Value root;root["code"]=resp.code;root["result"]=resp.result;Json::FastWriter writer;string sendwriter = writer.write(root);return sendwriter; //返回序列化之后的字符串}//这里实现一个反序列化响应的函数// string--->response_tvoid RespReSerialize(const string &jsonstring, response_t &resp){Json::Reader reader;Json::Valueroot;reader.parse(jsonstring, root); //解析进行反序列化resp.code = root["code"].asInt();resp.result = root["result"].asInt();}

client.ccclient.cc client.cc

#include "protocol.hpp"#include "Sock.hpp"int main(int argc, char *argv[]){if (argc != 3){cout << "ip+port" << endl;exit(1);}uint16_t port = atoi(argv[2]);int sockfd = Sock::Socket();Sock::Connect(sockfd, argv[1], port);request_t req;cout << "Please Enter Data One# ";cin >> req.x;cout << "Please Enter Data Two# ";cin >> req.y;cout << "Please Enter Data Opt# ";cin >> req.opt;string sendwriter=ReqSerialize(req);write(sockfd, sendwriter.c_str(), sendwriter.size());//这样就序列化成功了//读取信息char buf[1024];ssize_t s = read(sockfd, buf, sizeof(buf) - 1);//对resp进行反序列化response_t resp;if (s > 0){buf[s] = 0;string msg = buf;// cout<<msg<<endl;//对响应进行反序列化完成RespReSerialize(msg, resp);cout << "code[0:success]: " << resp.code;cout << "result " << resp.result << endl; }else{exit(1);}return 0;}

server.ccserver.cc server.cc

#include "protocol.hpp"#include "Sock.hpp"void *HandlerRequest(void *args){pthread_detach(pthread_self());int sockfd = *(int *)args;delete args;//业务逻辑,先读先要放序列化,然后计算,判断结果是否正确,正确返回,不正确异常//做一个短服务,request -> 分析处理->构建response->sent(response)--->close(sock)// verson1:没有明显的序列化和反序列化的过程// 1.读取请求,但是这样的操作对于90%的情况是可以满足的,但是对于一些老的服务器就不可以使用了//直接发的话缺少了一个序列化和放序列化的过程char buf[1024];ssize_t s = read(sockfd, buf, sizeof(buf) - 1);if (s < 0){cout << "error" << endl;close(sockfd);}else if (s == 0){cout << "client quit..." << endl;close(sockfd);}else{//只要大于0就认为读取成功了buf[s] = 0;string msg = buf;request_t req;//进行对字符串的反序列化请求ReqReSerialize(msg, req);//读取过来要进行一个反序列化的过程// if (s == sizeof(req)) //因为传送过来的是一个结构体,所以就是==// {//读取到了一个完整的请求,待定// req.x,req.y,req.opt// 2.分析请求// 3.计算结果response_t resp = {0, 0}; //响应,这里的默认响应结果我们都给他设置为0,默认都设置为0// 4.构建响应,并进行返回switch (req.opt){case '+':resp.result = req.x + req.y;break;case '-':resp.result = req.x - req.y;break;case '*':resp.result = req.x * req.y;break;case '/':if (req.y == 0)resp.code = -1;elseresp.result = req.x / req.y;break;case '%':if (req.y == 0)resp.code = -2;elseresp.result = req.x % req.y;break;default:resp.code = -3; //代表我们的请求方法异常break;}//处理完之后就要返回响应cout << "request " << req.x << req.opt << req.y << endl;cout<<"response "<<resp.result<<endl;//这次我们要先对resp进行序列化string send_msg = RespSerialize(resp);cout<<send_msg<<endl;write(sockfd, send_msg.c_str(), send_msg.size()); //序列化之后再发送回去cout << "server finish" << endl;}// 5.关闭链接}int main(int argc, char *argv[]){if (argc != 2){cout << "ip" << endl;exit(1);}uint16_t port = atoi(argv[1]);int sockfd = Sock::Socket();Sock::Bind(sockfd, port);Sock::Listen(sockfd);while (true){int newsockfd = Sock::Accept(sockfd);if (newsockfd < 0){continue;}pthread_t tid;int *pram = new int(newsockfd);pthread_create(&tid, nullptr, HandlerRequest, (void *)pram);}return 0;}

我们刚刚写的cs模式的在线版本计算器,本质上是一个应用层网络服务

  1. 基本通信代码是我们自己写的————————(会话层:进行通信管理)
  2. 序列化和反序列化时我们使用组件完成的————(表示层:设备固有数据格式和网络标准数据格式的转换)
  3. 请求,结果格式,code含义,等约定是我们自己写的————(针对特定应用的协议)
  4. 业务逻辑(计算也是我们自己写的)

HTTP协议,本质上,在定位上和我们刚刚写的网络计算机,没有区别,都是应用层协议

  1. 网络通信
  2. 序列化和反序列化
  3. 协议细节
    http协议把这3点都实现了