网络计算器—————序列化与反序列化

本文探讨了如何在应用层网络通信中使用自定义协议和jsoncpp组件进行序列化与反序列化,通过实例展示了结构化数据在网络计算器中的传输与处理过程,包括协议定制、jsoncpp的使用及其在简化数据转换中的作用。
摘要由CSDN通过智能技术生成


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

序列化和反序列化

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

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

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

t e s t . c p p test.cpp test.cpp


#include<iostream>
#include<string>
#include<jsoncpp/json/json.h>

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<<jsontostruct<<endl;
    //这样就读取到里面的内容了

    return 0;
}

在这里插入图片描述

网络在线计算器

S o c k . h p p Sock.hpp Sock.hpp

#include <iostream>
#include <unistd.h>
using namespace std;
#include<sys/socket.h>
#include<sys/types.h>
#include<cstdlib>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstring>
#include<string>
//这些静态的套接字都是属于类而不属于对象
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 <iostream>
#include <string>
#include <pthread.h>
#include <jsoncpp/json/json.h>

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<<msg<<endl;
        //对响应进行反序列化完成
        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模式的在线版本计算器,本质上是一个应用层网络服务

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

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

  1. 网络通信
  2. 序列化和反序列化
  3. 协议细节
    http协议把这3点都实现了
  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevin~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值