【Linux后端服务器开发】协议定制(序列化与反序列化)

news2024/11/25 2:27:43

目录

一、应用层协议概述

二、序列化与反序列化

Protocal.h头文件

Server.h头文件

Client.h头文件

server.cpp源文件

client.cpp源文件


一、应用层协议概述

什么是应用层?我们通过编写程序解决一个个实际问题、满足我们日常需求的网络程序,都是应用层程序。

协议是一种“约定”,socket的api接口,在读写数据时,都是按“字符串”的方式发送数据,那么如果我们要传输一些“结构化的数据”怎么办?

这时就需要用到应用层协议,一端将结构化数据转化成字符串格式,另一端通过协议的“约定”格式,将其解析成结构化数据。

应用层协议的本质,就是对传输层的字符串数据进行序列化和反序列化,使结构化数据可以进行网络通信。

虽然应用层协议是程序员根据不同的程序进行定制的,但实际上,已经有大佬定义了很多现成又非常好用的应用层协议,供我们直接参考使用,例如 HTTP / HTTPS(超文本传输协议)

URL:我们俗称的“网址”,其实就是URL,例如https://blog.csdn.net/phoenixFlyzzz?type=blog

这是我的博客主页网址的url,每个url都是有固定格式的,通过特殊符号分隔:

  • https:// —— 协议方案名
  • blog.csdn.net —— 服务器域名
  • /phoenixFlyzzz —— 带层次的文件路径
  • ?type=blog —— 参数

在这个URL中,并没有完全展示一个网址的全部结构,但一个URL也不是一定有全部结构的,有些结构可以省略,有些结构不写会有默认值。

urlencodeurldecode:url的编码和解码,像 / ? : 这些字符,已经被URL当作特殊字符处理了,用于区分一个URL中不同的结构字段,因此这些字符不能随意出现。如果某个参数中需要用到这种特殊字符,比必须先对特殊字符进行转义。

转义的规则:将需要转码的字符转为16进制数,然后从左到右,取4位(不足4位直接处理)每2位做一位,前面加上%,编码成%XY格式。

例如,“+”被转义成“%2B”:

urldecode就是urlencode的逆过程,将转义过的字符进行解码。

二、序列化与反序列化

设计:通过TCP协议定制一个网络计算器,客户端发送算式,服务器返回结果

算法:复杂算式的运算规则、字符串数据序列化与反序列化、Json数据解析

由于用到了Json库,需要在编译的时候加上 -ljsoncpp

Protocal.h头文件

网络计算器的协议定制(序列化与反序列化),定义请求体和响应体的对象,请求体与响应体对象在数据传输过程中都需要进行序列化和反序列化操作

  • 序列化:将输入的数据转换成规定格式的字符串
  • 反序列化:将规定格式的字符串转化成结构化数据
#pragma once

#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>

using namespace std;

#define SEP " "
#define LINE_SEP "\r\n"

enum
{
    OK = 0,
    DIV_ZERO,
    MOD_ZERO,
    OP_ERR
};

// "x op y" -> "content_len"\r\n"x op y"\r\n
string En_Length(const string& text)
{
    string send_str = to_string(text.size());
    send_str += LINE_SEP;
    send_str += text;
    send_str += LINE_SEP;

    return send_str;
}

// "content_len"\r\n"x op y"\r\n
bool De_Length(const string& package, string* text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == string::npos)
        return false;
    string text_len_str = package.substr(0, pos);
    int text_len = stoi(text_len_str);
    *text = package.substr(pos + strlen(LINE_SEP), text_len);
    return true;
}

// 通信协议不止一种,需要将协议进行编号,以供os分辨
// "content_len"\r\n"协议编号"\r\n"x op y"\r\n

class Request
{
public:
    Request(int x = 0, int y = 0, char op = 0)
        : _x(x), _y(y), _op(op)
    {}

    // 序列化
    bool Serialize(string* out)
    {
    #ifdef MYSELF
        *out = "";

        // 结构化 -> "x op y"
        string x_str = to_string(_x);
        string y_str = to_string(_y);

        *out = x_str;
        *out += SEP;
        *out += _op;
        *out += SEP;
        *out += y_str;
    #else
        Json::Value root;
        root["first"] = _x;
        root["second"] = _y;
        root["oper"] = _op;

        Json::FastWriter write;
        *out = write.write(root);
    #endif
        return true;
    }

    // 反序列化
    bool Deserialiaze(const string& in)
    {
    #ifdef MYSELF
        // "x op y" -> 结构化
        auto left = in.find(SEP);
        auto right = in.rfind(SEP);

        if (left == string::npos || right == string::npos)
            return false;
        if (left == right)
            return false;
        if (right - (left + strlen(SEP)) != 1)
            return false;
        
        string x_str = in.substr(0, left);
        string y_str = in.substr(right + strlen(SEP));

        if (x_str.empty() || y_str.empty())
            return false;
        
        _x = stoi(x_str);
        _y = stoi(y_str);
        _op = in[left + strlen(SEP)];
    #else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);

        _x = root["first"].asInt();
        _y = root["second"].asInt();
        _op = root["oper"].asInt();
    #endif
        return true;
    }

public:
    int _x, _y;
    char _op;
};

class Response
{
public:
    Response(int exitcode = 0, int res = 0)
        : _exitcode(exitcode), _res(res)
    {}

    bool Serialize(string* out)
    {
    #ifdef MYSELF
        *out = "";
        string ec_str = to_string(_exitcode);
        string res_str = to_string(_res);

        *out = ec_str;
        *out += SEP;
        *out += res_str;
    #else
        Json::Value root;
        root["exitcode"] = _exitcode;
        root["result"] = _res;

        Json::FastWriter writer;
        *out = writer.write(root);
    #endif
        return true;
    }

    bool Deserialize(const string& in)
    {
    #ifdef MYSELF
        auto mid = in.find(SEP);
        if (mid == string::npos)
            return false;

        string ec_str = in.substr(0, mid);
        string res_str = in.substr(mid + strlen(SEP));
        if (ec_str.empty() || res_str.empty())
            return false;

        _exitcode = stoi(ec_str);
        _res = stoi(res_str);
    #else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);

        _exitcode = root["exitcode"].asInt();
        _res = root["result"].asInt();
    #endif
        return true;
    }

public:
    int _exitcode;
    int _res;
};

// 读取数据包
// "content_len"\r\n"x op y"\r\n
bool Recv_Package(int sock, string& inbuf, string* text)
{
    char buf[1024];
    while (true)
    {
        ssize_t n = recv(sock, buf, sizeof(buf) - 1, 0);
        if (n > 0)
        {
            buf[n] = 0;
            inbuf += buf;

            auto pos = inbuf.find(LINE_SEP);
            if (pos == string::npos)
                continue;
            string text_len_str = inbuf.substr(0, pos);
            int text_len = stoi(text_len_str);
            int total_len = text_len_str.size() + 2 * strlen(LINE_SEP) + text_len;
            cout << "处理前#inbuf:\n" << inbuf << endl;

            if (inbuf.size() < total_len)
            {
                cout << "输入不符合协议规定" << endl;
                continue;
            }

            *text = inbuf.substr(0, total_len);
            inbuf.erase(0, total_len);
            cout << "处理后#inbuf:\n" << inbuf << endl;

            break;
        }
        else
        {
            return false;
        }
    }

    return true;
}

// 计算任务
bool Cal(const Request& req, Response& resp)
{
    resp._exitcode = OK;
    resp._res = 0;

    if (req._op == '/' && req._y == 0)
    {
        resp._exitcode = DIV_ZERO;
        return false;
    }
    if (req._op == '%' && req._y == 0)
    {
        resp._exitcode = MOD_ZERO;
        return false;
    }

    switch (req._op)
    {
    case '+':
        resp._res = req._x + req._y;
        break;
    case '-':
        resp._res = req._x - req._y;
        break;
    case '*':
        resp._res = req._x * req._y;
        break;
    case '/':
        resp._res = req._x / req._y;
        break;
    case '%':
        resp._res = req._x % req._y;
        break;
    default:
        resp._exitcode = OP_ERR;
        break;
    }

    return true;
}

Server.h头文件

网络计算器的服务器,读取请求体报文,执行计算任务,生成响应体报文

#pragma once

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <functional>
#include <sys/wait.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "Protocal.h"

using namespace std;
using func_t = function<bool(const Request& req, Response& resp)>;

const static uint16_t g_port = 8080;
const static int g_backlog = 5;

void Handler_Entry(int sock, func_t func)
{
    string inbuf;
    while (true)
    {
        // 1. 读取报文: "content_len"\r\n"x op y"\r\n
        string req_text, req_str;
        if (!Recv_Package(sock, inbuf, &req_text))
            return;
        cout << "带报头的请求:\n" << req_text << endl;
        if (!De_Length(req_text, &req_str))
            return;
        cout << "去报头的正文:\n" << req_str << endl;

        // 2. 对请求Request反序列化,得到结构化请求对象
        Request req;
        if (!req.Deserialiaze(req_str))
            return;
        
        // 3. 业务逻辑, 生成结构化响应
        Response resp;
        func(req, resp);    // 处理req,生成resp

        // 4. 对相应的Response序列化
        string resp_str;
        resp.Serialize(&resp_str);
        cout << "计算完成,序列化响应:\n" << resp_str << endl;

        // 5. 构建完整报文,发送响应
        string send_str = En_Length(resp_str);
        cout << "构建完整的响应报文:\n" << send_str << endl;
        send(sock, send_str.c_str(), send_str.size(), 0);
    }
}

class Server
{
public:
    Server(const int port)
        : _port(port), _listenfd(-1)
    {}

    void Init()
    {
        _listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenfd < 0)
            exit(1);

        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        local.sin_port = htons(_port);

        if (bind(_listenfd, (struct sockaddr*)&local, sizeof(local)) < 0)
            exit(1);

        if (listen(_listenfd, g_backlog) < 0)
            exit(1);
    }

    void Start(func_t func)
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t peer_len = sizeof(peer);
            int sock = accept(_listenfd, (struct sockaddr*)&peer, &peer_len);
            if (sock < 0)
                exit(1);
            
            pid_t id = fork();
            if (id == 0)
            {
                close(_listenfd);
                Handler_Entry(sock, func);
                close(sock);
                exit(0);
            }
            pid_t ret = waitpid(id, nullptr, 0);
        }
    }

private:
    int _listenfd;
    uint16_t _port;
};

Client.h头文件

客户端头文件,通过输入数据生产请求体,将服务器返回的响应体序列化和反序列化

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "Protocal.h"

using namespace std;

class Client
{
public:
    Client(const std::string& server_ip, const uint16_t& server_port)
        : _sock(-1), _server_ip(server_ip), _server_port(server_port)
    {}

    void Init()
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            std::cerr << "socket error" << std::endl;
            exit(1);
        }
    }

    void Run()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_server_port);
        server.sin_addr.s_addr = inet_addr(_server_ip.c_str());

        if (connect(_sock, (struct sockaddr*)&server, sizeof(server)) < 0)
        {
            std::cerr << "connect error" << std::endl;
            exit(1);
        }
        else
        {
            string line;
            string inbuf;
            while (true)
            {
                cout << "mycal>>> ";
                getline(cin, line);
                Request req = Parse_Line(line);     // 输入字符串,生成Request对象

                string content;
                req.Serialize(&content);                // Request对象序列化
                string send_str = En_Length(content);   // 序列化字符串编码 -> "content_len"\r\n"x op y"\r\n
                send(_sock, send_str.c_str(), send_str.size(), 0);

                // 将服务器的返回结果序列化与反序列化
                string package, text;
                if (!Recv_Package(_sock, inbuf, &package))
                    continue;
                if (!De_Length(package, &text))
                    continue;
                
                Response resp;
                resp.Deserialize(text);
                cout << "exitcode: " << resp._exitcode << endl;
                cout << "result: " << resp._res << endl << endl;
            }
        }
    }

    // 将输入转化为Request结构
    Request Parse_Line(const string& line)
    {
        int status = 0;     // 0:操作符之前    1:遇到操作符    2:操作符之后
        int cnt = line.size();
        string left, right;
        char op;
        int i = 0;
        while (i < cnt)
        {
            switch (status)
            {
            case 0:
                if (!isdigit(line[i]))
                {
                    if (line[i] == ' ')
                    {
                        i++;
                        break;
                    }
                    op = line[i];
                    status = 1;
                }
                else
                {
                    left.push_back(line[i++]);
                }
                break;
            case 1:
                i++;
                if (line[i] == ' ')
                    break;
                status = 2;
                break;
            case 2:
                right.push_back(line[i++]);
                break;
            }
        }
        cout << left << ' ' << op << ' ' << right << endl;
        return Request(stoi(left), stoi(right), op);
    }

    ~Client()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    int _sock;
    string _server_ip;
    uint16_t _server_port;
};

server.cpp源文件

#include "Server.h"
#include <memory>

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " local_port\n\t";
    exit(1);
}

// ./server port
int main(int argc, char* argv[])
{
    if (argc != 2)
        Usage(argv[0]);

    uint16_t port = atoi(argv[1]);

    std::unique_ptr<Server> tsvr(new Server(port));
    tsvr->Init();
    tsvr->Start(Cal);

    return 0;
}

client.cpp源文件

#include "Client.h"
#include <memory>

using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
    exit(1);
}

int main(int argc, char* argv[])
{
    if (argc != 3)
        Usage(argv[0]);

    string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    unique_ptr<Client> tcli(new Client(server_ip, server_port));
    tcli->Init();
    tcli->Run();

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/783887.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CMU15-445 2022 Fall 通关记录 —— Project 3:Query Execution(上篇)

前言 我在初次实现的时候并没有做 三个“选做”的排行榜任务&#xff0c;所以这只是上篇内容&#xff0c;等完成 Pro4 后再完成下篇。 Project 3: Query Execution Project #3 - Query Execution | CMU 15-445/645 :: Intro to Database Systems (Fall 2022) — 项目 #3 - 查…

R语言机器学习之影像组学分析的原理详解

概要 影像组学从常规医学图像中高通量提取大量的放射学定量数据&#xff0c;并以非侵入性方式探索它们与临床结果的相关性&#xff0c;在医学研究中得到广泛的应用。 01 影像组学&#xff08;Radiomics&#xff09;的概念&#xff1a; 影像组学&#xff08;Radiomics&#xff…

JVM堆内存介绍

一&#xff1a;JVM中内存 JVM中内存通常划分为两个部分&#xff0c;分别为堆内存与栈内存&#xff0c;栈内存主要用运行线程方法 存放本地暂时变量与线程中方法运行时候须要的引用对象地址。 JVM全部的对象信息都 存放在堆内存中。相比栈内存&#xff0c;堆内存能够所大的多&am…

图为科技应邀出席第38届中国计算机应用大会

第38届中国计算机应用大会&#xff08;CCF NCCA 2023&#xff09;暨2023年人工智能应用学术会议于7月16日-19日在苏州召开。 本次会议由中国计算机学会(CCF)主办&#xff0c;CCF计算机应用专业委员会承办&#xff0c;苏州大学、苏州科技大学、南京理工大学等单位协办&#xff0…

数字孪生搭高台,温控节能唱新戏

“孪生”的基本思想最早起源于1969年的阿波罗计划&#xff0c;通过留在地球上的航天器对发射到太空的航天器进行工作状态的仿真模拟&#xff0c;进而辅助航天员完成决策&#xff0c;减少各种操作结果的未知性。 从2002年开始&#xff0c;数字孪生的概念和定义在不同领域逐渐被提…

三种数据库架构模式

数据架构设计模式 数据架构主要有三种模式&#xff1a; Shared Everything、Shared Disk、Shared Nothing。 Shared Disk 各处理单元使用本地的私有CPU和Memory&#xff0c;共享磁盘系统&#xff0c;分布式数据库。 典型的代表是Oracle RAC、DB2 PureScale。 例如&#xf…

Navicat远程连接服务器失败 2002 - Can‘t connect to server on ...(10060)

报错如下&#xff1a; 2002 - Can’t connect to server on ‘192.168.33.59’(10060) 解决方案&#xff1a; 下面列举可能出现的几种情况&#xff1a; 1.防火墙原因&#xff0c;需要关闭防火墙 systemctl stop firewalld systemctl disable firewalld2.数据库未开启&#x…

基于机器学习的情绪识别算法matlab仿真,对比SVM,LDA以及决策树

目录 1.算法理论概述 2.部分核心程序 3.算法运行软件版本 4.算法运行效果图预览 5.算法完整程序工程 1.算法理论概述 情绪识别是一种重要的情感分析任务&#xff0c;旨在从文本、语音或图像等数据中识别出人的情绪状态&#xff0c;如高兴、悲伤、愤怒等。本文介绍一种基于…

Linux软件/系统看门狗嵌入式独立看门狗

updating linux看门狗的使用 freertos看门狗的使用 一、看门狗简介 看门狗,又叫Watchdog timer(看门狗定时器)是一种电子计时器,其用于检测和恢复计算机故障。一般有一个输入和一个输出,其中的输入叫做喂狗( kickingthe dog or service the dog)。输出一般连接到另外一个部…

HP暗影精灵9 Plus OMEN 17.3英寸游戏本17-ck2000出厂Win11系统原厂预装OEM系统

惠普暗影9笔记本电脑原装Windows11系统ISO镜像包 适用机型17-ck2000TX,17-ck2001TX,17-ck2002TX,17-ck2003TX 自带所有驱动、出厂主题壁纸LOGO、Office办公软件、惠普电脑管家、OMEN Command Center等预装程序 所需要工具&#xff1a;32G或以上的U盘 文件格式&#xff1a;IS…

软件外包开发可行性调研

软件开发的可行性调研对软件工程来说是必要的&#xff0c;也是开启软件工程建设的第一步。在进行软件外包开发的可行性调研时&#xff0c;需要调查的内容比较多&#xff0c;同时调查研究并不是一次性完成的任务&#xff0c;而是一个持续过程&#xff0c;应随着项目进行进行调整…

FPGA开发:按键消抖

按键是FPGA开发板上的重要交互元件&#xff0c;因为按键的内部的结构设计&#xff0c;在按下和松开按键时&#xff0c;按键会无法避免地产生机械抖动&#xff0c;因此要对按键输入进行特殊处理&#xff0c;否则可能会因为机械抖动产生意外的重复触发。 按键消抖有很多方法&…

Git-分布式版本控制工具

Git仓库&#xff1a;本地和远程 获取git仓库&#xff1a; 本地初始化Git仓库&#xff08;创建空目录&#xff0c;右键git bansh&#xff0c;执行git init&#xff09;远程仓库克隆&#xff0c;git clone 远程仓库地址 版本库&#xff1a;.git隐藏文件夹&#xff0c;储存配置信…

【C++初阶】:优先队列(仿函数)

优先队列 一.基本使用二.模拟实现三.仿函数1.优先队列里的使用2.概念3.模拟 一.基本使用 优先队列的底层默认是使用vector构造的&#xff0c;也就是使用数组模拟&#xff08;二叉树&#xff09;堆。并且默认是按大堆存放数据&#xff08;也就是父节点>子节点&#xff0c;左节…

51单片机串口

该部分的笔记来自视频教程链接https://www.bilibili.com/video/BV1bt4y197NR/?spm_id_from333.788&vd_sourceb91967c499b23106586d7aa35af46413 一、51单片机串口基础介绍 一般的应用层的协议中采用和校验或CRC校验&#xff0c;而奇偶校验还是解决基本通信中的帧格式中的…

练习时长两年半的网络安全防御“second”

目录 1.防火墙的安全区域 Trust区域 DMZ区域 Untrust区域 Local区域 安全区域的受信任程度与优先级 2. 安全策略 ​编辑 安全域间、安全策略与报文流动方向 安全域间是用来描述流量的传输通道&#xff0c;它是两个“区域”之间的唯一“道路”。如果希望对经过这条通 …

C++初阶 - 4.类和对象(下)

目录 1.再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 ---- 是构造函数的一部分 1.3 explicit 关键字 2.static成员 2.1概念 2.2 特性 3.友元 3.1友元函数 3.2 友元类 4.内部类 5.再次理解类和对象 1.再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xff0…

Hadoop中HDFS的架构

一、Switch语句 语法规则&#xff1a; ①语句中的变量类型可以是byte、short、int或者char;从javaSE5开始支持枚举类型&#xff1b; javaSE7开始&#xff0c;switch支持String。 ②没有break时&#xff0c;后续case的语句都会执行 二、修饰符 访问修饰符 Java中&#xff0c…

机器人导航(2):导航实现

文章目录 SLAM建图gmapping简介gmapping节点说明gmapping使用 地图服务map_server简介map_server使用之地图保存节点(map_saver)map_server使用之地图服务(map_server)map_server节点说明地图读取地图显示 定位amcl简介amcl节点说明订阅的Topic发布的Topic服务调用的服务参数坐…

prometheus直方图实践

目录 1.简介 2.方案 1.简介 Prometheus提供了Counter、Gauge、Histogram、Summary四类指标&#xff08;详见Metric types | Prometheus&#xff09;&#xff0c;可以通过"github.com/prometheus/client_golang/prometheus"自定义采集指标、注册、采集数据、发布UR…