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

目录

一、应用层协议概述

二、序列化与反序列化

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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

0基础学习VR全景平台篇 第69篇:VR直播-如何设置广告

直播间可以插入轮播广告&#xff0c;并且支持外链跳转&#xff0c;能够有效地提升VR直播活动的转化率。 1、点击&#xff0c;添加广告 2、广告图展现形式分为两种&#xff1a;普通广告和全屏广告&#xff0c;普通广告在非全屏播放的直播间显示&#xff0c;全屏广告在全屏播放的…

【Visual Studio】VS调用tensorflow C++API的配置(无需编译)

1. 首先下载并安装visual studio Visual Studio 2015 安装教程&#xff08;附安装包&#xff09;&#xff0c;按照博客中顺序来就可以 如果在安装过程中提示安装包损失或毁坏&#xff0c;参考VS2015安装过程中安装包丢失或损坏解决办法 卡在哪个搜索文件上就找到哪个文件再继…

【100天精通python】Day15:python 第三方模块和包,模块如何以主程序形式执行

目录 1 常用的第三方模块 2. 第三方模块的安装和使用 2.1 安装第三方模块&#xff1a; 2.2 导入第三方模块&#xff1a; 2.3 使用第三方模块&#xff1a; 3 模块以主程序形式执行 4 python 中的包 4.1 python程序的包结构 4.2 创建包 4.3 python中包的导入和使用 5 …

lama cleaner

这里写自定义目录标题 安装参数包含的额外plugins 安装 conda create --name lamacleaner python3.10 pip install -r requirements.txt pip install gfpgan pip install realesrgan pip install rembg pip install .如果安装本package报错&#xff0c;可以尝试改&#xff1…

【安全】web中的常见编码浅析浏览器解析机制

目录 常见编码 一、ASCII码 二、URL编码 三、Unicode编码 四、HTML实体编码 结合编码理解浏览器解析机制 常见编码 一、ASCII码 ASCII (American Standard Code for Information Interchange&#xff0c;美国信息交换标准代码&#xff09; 计算机内部&#xff0…

Ceph简介和特性

Ceph是一个多版本存储系统&#xff0c;它把每一个待管理的数据流(例如一个文件) 切分为一到多个固定大小的对象数据&#xff0c;并以其为原子单元完成数据存取。 对象数据的底层存储服务是由多个主机 (host) 组成的存储集群&#xff0c;该集群也被称之为 RADOS (ReliableAutoma…

测试覆盖率 JVM 字节码测试运用 - 远程调试、测试覆盖、影子数据库

目录 前言&#xff1a; 简介 基础使用方式介绍 工具特性 前言&#xff1a; 在软件开发中&#xff0c;测试覆盖率是一个非常重要的指标&#xff0c;它表示代码中所有的测试用例是否都已经被覆盖到。JVM 字节码测试是一种比较新的测试方法&#xff0c;它可以对 JVM 字节码进…

php开发实战分析(10):城市区县联动筛选

php开发实战分析系列目录 php开发实战分析(1)&#xff1a;mysql操作字段&#xff08;添加、删除、修改&#xff0c;多数据表中新增多个字段&#xff09;php开发实战分析(2)&#xff1a;cookie的动态使用&#xff08;设置、获取、删除、猜你喜欢原理、购物车调用&#xff09;ph…

DB-GPT:强强联合Langchain-Vicuna的应用实战开源项目,彻底改变与数据库的交互方式

今天看到 蚂蚁科技 Magic 开源的DB-GPT项目&#xff0c;觉得创意很好&#xff0c;集成了当前LLM的主流技术&#xff0c;主要如下 Langchain&#xff1a; 构建在LLM之上的应用开发框架HuggingFace: 模型标准&#xff0c;提供大模型管理功能Vicuna: 一个令GPT-4惊艳的开源聊天机…

❓“如何创业?互联网创业又该如何入手?

&#x1f31f;5大创业建议&#xff0c;让你轻松入门&#xff01; 作为一名互联网创业者&#xff0c;我想分享一下我的创业经验。下面是我的五个建议&#xff0c;希望对你有所帮助&#xff01; &#x1f31f;了解市场需求 在创业之前&#xff0c;了解市场需求非常重要。你需要研…

Docker基本概念+命令

Docker基本概念命令 一、Docker是什么&#xff1f;二、为什么Docker技术受欢迎三、Docker核心概念四、Docker安装五、Docker镜像操作1.搜索镜像2.获取镜像3.镜像加速下载4.查看镜像信息5.查看下载的镜像文件信息6.查看下载到本地的所有镜像7.获取镜像的详细信息8.修改镜像标签9…

STM32MP157驱动开发——按键驱动(异步通知)

文章目录 “异步通知 ”机制&#xff1a;信号的宏定义&#xff1a;信号注册 APP执行过程驱动编程做的事应用编程做的事异步通知方式的按键驱动程序(stm32mp157)button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “异步通知 ”机制&#xff1a; 信号的宏定义&#x…

阿里云容器镜像仓库(ACR)的创建和使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【Flume 01】Flume简介、部署、组件

1 Flume简介 Flume是一个高可用、高可靠、分布式的海量日志采集、聚合和传输的系统 主要特性&#xff1a; 它有一个简单、灵活的基于流的数据流结构&#xff08;使用Event封装&#xff09;具有负载均衡机制和故障转移机制一个简单可扩展的数据模型(Source、Channel、Sink) Sou…

若依vue 多table前端HTML页面导出一张Excel表

前言 导入依赖&#xff0c;具体前端vue配置就不介绍了&#xff0c;直接晒具体细节代码 实现 需要在多table外加div&#xff0c;其他都是基本操作js代码 import FileSaver from file-saver import * as XLSX from "xlsx";const htmlToExcel {getExcelNew(classNam…

Windows Server 2019 中文版、英文版下载 (updated Jul 2023)

Windows Server 2019 中文版、英文版下载 (updated Jul 2023) Windows Server 2019 Version 1809&#xff0c;2023 年 7 月更新 请访问原文链接&#xff1a;https://sysin.org/blog/windows-server-2019/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者…

软件测试测试分类(重点)

目录 按照测试对象划分&#xff08;了解&#xff09; ①界面测试 ②可靠性测试&#xff08;可用性&#xff09; ③容错性测试 容错性和可靠性之间的区别 ④文档测试 ⑤兼容性测试 ⑥易用性测试 ⑦安装、卸载测试 ⑧安全测试 ⑨性能测试 内存泄露测试 按照是否查看…

HDFS基本操作命令

这里写目录标题 HDFS Shell CLI客户端说明常用命令hadoop fs -mkdir [-p] <path>hadoop fs -ls [-h] [-R] [<path>...]上传文件到指定目录下方法一:hadoop fs -put [-f] [-p] <localsrc>.....<dst>方法二&#xff1a;hadoop fs -moveFromLocal <loc…

查看docker容器启动参数

查看docker启动参数 1、查看docker容器的自启动策略2、查看docker容器的日志滚动清理策略 以下配置命令以redis容器为例 1、查看docker容器的自启动策略 docker inspect --format{{json .HostConfig.RestartPolicy}} redis输出的name是always 表示此容器是开机自启动的&#x…

uniapp 即时通讯开发流程详解

今天我将为您详细介绍UniApp开发中的即时通讯流程。本文将向您展示如何在UniApp中实现即时通讯功能&#xff0c;为您的应用程序增添交互性和实时性。 1. 准备工作 在开始开发之前&#xff0c;确保您已完成以下准备工作&#xff1a; 确保您已经安装好UniApp开发环境&#xff…