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

这篇具有很好参考价值的文章主要介绍了【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”:

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

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头文件

客户端头文件,通过输入数据生产请求体,将服务器返回的响应体序列化和反序列化文章来源地址https://www.toymoban.com/news/detail-602861.html

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

到了这里,关于【Linux后端服务器开发】协议定制(序列化与反序列化)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux后端服务器开发】基础IO与文件系统

    目录 一、基础IO 1. C语言文件读写 2. 标志位传参 3. C语言与系统调用关系 二、文件系统 1. 文件描述符 2. 输入输出重定向 文件调用 库函数接口: fopen、fclose、fwrite、fread、fseek 系统调用接口:open、close、write、read、lseek r/w/a :读/写/追加 若打开的文件不存在,“r”报错,“

    2024年02月15日
    浏览(66)
  • 【Linux后端服务器开发】封装线程池实现TCP多线程通信

    目录 一、线程池模块 Thread.h LockGuard.h ThreadPool.h 二、任务模块模块 Task.h 三、日志模块 Log.h 四、守护进程模块 Deamon.h  五、TCP通信模块 Server.h Client.h server.cpp client.cpp 关于TCP通信协议的封装,此篇博客有详述: 【Linux后端服务器开发】TCP通信设计_命运on-9的博客-CSDN博客 线程池

    2024年02月16日
    浏览(45)
  • 强推Linux高性能服务器编程, 真的是后端开发技术提升, 沉淀自身不容错过的一本经典书籍

    目录 第1章 TCP/IP协议 1.1 TCP/IP协议族体系结构以及主要协议 1.1.1 数据链路层 1.1.2 网络层 1.1.3 传输层 1.1.4 应用层 1.2 封装 1.3 分用 1.5 ARP协议工作原理 1.5.1 以太网ARP请求/应答报文详解 1.5.2 ARP高速缓存的查看和修改 1.5.3 使用tcpdump观察ARP通信过程所得结果如下 本篇核心关键所在

    2024年02月07日
    浏览(50)
  • 【Linux】TCP网络套接字编程+协议定制+序列化和反序列化

    悟已往之不谏,知来者之可追。抓不住的就放手,属于你的都在路上…… 1. 为了让我们的代码更规范化,所以搞出了日志等级分类,常见的日志输出等级有DEBUG NORMAL WARNING ERROR FATAL等,再配合上程序运行的时间,输出的内容等,公司中就是使用日志分类的方式来记录程序的输

    2024年02月08日
    浏览(70)
  • 谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事

    由于socket api的接口,在读写数据的时候是以字符串的方式发送接收的,如果需要传输 结构化的数据 ,就需要制定一个协议 结构化数据在发送到网络中之前需要完成序列化 接收方收到的是序列字节流,需要完成反序列化才能使用(如ChatInfo._name) 当我们进行网络通信的的时

    2024年02月06日
    浏览(61)
  • 【音视频开发】:RTSP服务器协议内容

    RTSP是一个 实时传输流协议 ,是一个 应用层 的协议。通常说的RTSP包括RTSP协议、RTP协议、RTCP协议。 RTSP协议:负责服务器与客户端之间的请求与相应 RTP协议 :负责服务器与客户端之间传输媒体数据 RTCP协议:负责提供有关RTP传输指令的反馈,就是确保RTP传输的质量 吧 三者关

    2024年04月26日
    浏览(49)
  • Flask框架小程序后端分离开发学习笔记《2》构建基础的HTTP服务器

    Flask是使用python的后端,由于小程序需要后端开发,遂学习一下后端开发。本节提供一个构建简单的本地服务器的代码,仔细看注释,学习每一步的流程,理解服务器接收请求,回复响应的基本原理。 代码效果,运行之后,在浏览器输入:localhost:2000 总结 1.导入socket库:这个库

    2024年01月18日
    浏览(44)
  • Flask框架小程序后端分离开发学习笔记《4》向服务器端发送模拟请求-爬虫

    Flask是使用python的后端,由于小程序需要后端开发,遂学习一下后端开发。 下面代码,是一个比较老的版本了,可以借鉴一下。 最后尝试请求豆瓣的网页,并未得到,我怀疑是有反爬手段,我们的请求数据还有很多东西没加进去,所以看起来不像是浏览器发送的请求,后续会

    2024年01月20日
    浏览(52)
  • 如何在linux服务器上用Nginx部署Vue项目,以及如何部署springboot后端项目

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:这里可以添加本文要记录的大概内容: 本文内容记录如何在Linux(Ubuntu)系统上安装Nginx,并部署打包好的Vue前端项目,最后通过浏览器访问。 提示:以下是本篇文章正文内容,下面案例可供参考

    2024年04月16日
    浏览(48)
  • 【网络】协议定制+序列化/反序列化

    如果光看定义很难理解序列化的意义,那么我们可以从另一个角度来推导出什么是序列化, 那么究竟序列化的目的是什么? 其实序列化最终的目的是为了对象可以 跨平台存储,和进行网络传输 。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是

    2024年02月08日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包