我们程序员写的一个一个程序,都是在应用层
我们发送的数据都是结构化的数据,这种结构话的数据就很好看也很好使用
struct message
{
我的昵称:会跳的鹅
我的头像:唐老鸭.png
我的消息:在吗?
消息时间:2022-07-10 11:31:59
}
这就是一个结构化的消息,我们要把这个结构化的数据转化成一个从“字符串”,我们要把这个结构化的数据
“{昵称:xxx,头像:xxx,消息:xxx}”,把它传送到网络里面,到对端之后,再把它解析出来,读取到对应的结构体里面
序列化:把结构化的数据转化为字符串的过程就是序列化的过程,
反序列化:把字符串转化为结构体信息就是反序列化的过程,因为结构化的数据,再网络里面不方便传输,字符串便于网络传输
为什么要进行序列化和反序列化
而我们之前使用的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)
//这就是一个反序列化的过程,接收到了一个字符串,再把它弄成结构体
//最后还需要再序列化
这个方案,我们是把数据分开了
我们发现这个过程实际上是非常麻烦的 ,都由我们自己做就很麻烦
约定方案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));//这样的话就缺少了序列化的过程,不太推荐
测试json
t e s t . c p p test.cpp test.cpp
#include
#include
#include
using namespace std;
//仅仅是了解一下序列化和反序列化的过程
struct request_t
{
int x; // 10
int y; // 0
char 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<
//这样就读取到里面的内容了
return 0;
}
S o c k . h p p Sock.hpp Sock.hpp
#include
#include
using namespace std;
#include
#include
#include
#include
#include
#include
#include
//这些静态的套接字都是属于类而不属于对象
class Sock
{
public:
static int Socket()
{
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);
}
}
};
p r o t o c o l . h p p protocol.hpp protocol.hpp
//我们在通信的时候要自己定制协议
//客户端和服务器要进行计算器的功能,我们要有请求有响应
//这个本质上是一个应用层网络服务
#pragma once
#include
#include
#include
#include
using namespace std;
//定制协议的过程,目前就是定制结构化数据的过程
//请求格式
//但是向这种,如果面对的是老客户端,一旦有一个字节没有办法发送过来,就出现了错误
//我们需要序列化这个东西
struct request_t
{
int x; // 10
int y; // 0
char opt; //我们协议上是支持 //
request_t() = default;
}; //请求协议
//这里我们写一个响应格式
struct response_t
{
int code; //程序运算完毕的计算状态,code=0(success),code=-1(:\0),code=-2(%0),先检测code,得到result才有意义
int result; //计算结果,能否区分是正常的计算结果,还是异常退出结果
};
//这里的话我们实现一个序列化请求的函数
request_t ---->string
string 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_t
void ReqReSerialize(const string &jsonstring, request_t &req)
{
Json::Reader reader;
Json::Value root;
reader.parse(jsonstring, root); //解析进行反序列化
req.x = root["one"].asInt();
req.y = root["two"].asInt();
req.opt =(char) root["operator"].asUInt();
}
//序列化响应的函数
response_t ----->string
string 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_t
void RespReSerialize(const string &jsonstring, response_t &resp)
{
Json::Reader reader;
Json::Value root;
reader.parse(jsonstring, root); //解析进行反序列化
resp.code = root["code"].asInt();
resp.result = root["result"].asInt();
}
c l i e n t . c c client.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<
//对响应进行反序列化完成
RespReSerialize(msg, resp);
cout << "code[0:success]: " << resp.code;
cout << "result " << resp.result << endl;
}
else
{
exit(1);
}
return 0;
}
s e r v e r . c c server.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;
else
resp.result = req.x / req.y;
break;
case '%':
if (req.y == 0)
resp.code = -2;
else
resp.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模式的在线版本计算器,本质上是一个应用层网络服务
- 基本通信代码是我们自己写的————————(会话层:进行通信管理)
- 序列化和反序列化时我们使用组件完成的————(表示层:设备固有数据格式和网络标准数据格式的转换)
- 请求,结果格式,code含义,等约定是我们自己写的————(针对特定应用的协议)
- 业务逻辑(计算也是我们自己写的)
HTTP协议,本质上,在定位上和我们刚刚写的网络计算机,没有区别,都是应用层协议