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



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

    序列化和反序列化

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

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

    struct message
    {
    	我的昵称:会跳的鹅
    	我的头像:唐老鸭.png
    	我的消息:在吗?
    	消息时间:2022-07-10 113159
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

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

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

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

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

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

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

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

    sudo yum install -y jsoncpp-devel
    
    • 1

    网络计算器

    我们这里自己定制协议

    没有使用组件

    发送端口
    伪代码

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

    接收端

    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)
    //这就是一个反序列化的过程,接收到了一个字符串,再把它弄成结构体
    //最后还需要再序列化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    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));//这样的话就缺少了序列化的过程,不太推荐
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    jsontest

    测试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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述

    网络在线计算器

    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);
            }
        }
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    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();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

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

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

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

    1. 网络通信
    2. 序列化和反序列化
    3. 协议细节
      http协议把这3点都实现了
  • 相关阅读:
    35_ue4进阶末日生存游戏开发[背包系统准备]
    【tesseract】Linux环境安装tesseract教程
    学习笔记丨Shell
    【每日一题】151. 反转字符串中的单词
    CF1648B Integral Array
    Unity使用VSCode,调试c#、Lua
    [pytorch笔记]04 --进阶训练技巧
    【云原生之kubernetes实战】在kubernetes集群下部署Rainbond平台
    前瞻视野 | 数字化转型领先企业的六大习惯
    【Java】获取手机文件名称补充
  • 原文地址:https://blog.csdn.net/m0_61567378/article/details/125717998