自定义协议、序列化与反序列化

news2025/7/10 20:24:37

在编写TCP和UDP程序的时候,我们很自然的就使用了读取的函数对数据进行获取,对于UDP来说提供的是无连接的以数据报的形式进行传输,对于TCP来说是面向数据流的,在之前的程序中我们只是进行了读取的操作,但是并没有对读取的内容进行分析。那如果我们要传输一些结构化的数据的话,那么就需要引入"协议"这个概念。

网络版计算器

在本文中将实现一个服务器版本的加法器,需要客户端把要计算的两个加数发过去,然后由服务器进行计算, 最后再把结果返回给客户端。

协议的约定

我们在这里约定信息,让协议能够更好的进行实现:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 “±*/%”;
  • 数字和运算符之间没有空格;

序列化与反序列化

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 同时通过使用Jsoncpp的库进行协议的序列化与反序列化

TcpServer.hpp

namespace tcpserver_ns{
    using namespace protocol_ns; // 使用自定义协议的工作空间
    class TcpServer;
    using func_t = std::function<Response (const Request&)>;    
    class ThreadData{
    public:
        ThreadData(int sock, std::string ip, uint16_t port, TcpServer *tsvrp)
            : _sock(sock) , _ip(ip) ,_port(port), _tsvrp(tsvrp)
        {}
        ~ThreadData()
        {}
    public:
        int _sock;
        std::string _ip;
        uint16_t _port;
        TcpServer* _tsvrp;
    };
    class TcpServer{
    public:
        TcpServer(func_t func, uint16_t port) : _func(func), _port(port){}
        void InitServer(){ // 初始化相关的套接字信息
            _listensock.Socket();
            _listensock.Bind(_port);
            _listensock.Listen();
            logMessage(Info, "init server done, listensock: %d", _listensock.Fd());
        }
        static void* ThreadRoutine(void* args){
            pthread_detach(pthread_self());
            logMessage(Debug, "thread running ...");
            ThreadData* td = static_cast<ThreadData*>(args);
            td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port); // 调用ServiceIO进行数据的读取与写入
            logMessage(Debug, "thread quit, client quit ... ");
            delete td;
            return nullptr;
        }
        void Start(){ // Accept 建立连接
            for(;;){
                std::string clientip;
                uint16_t clientport;
                int sock = _listensock.Accept(&clientip, &clientport);
                if (sock < 0) continue;
                logMessage(Debug, "get a new client, client info : [%s:%d]", clientip.c_str(), clientport);
                pthread_t tid;
                ThreadData *td = new ThreadData(sock, clientip, clientport, this); // 构建线程的数据信息
                pthread_create(&tid, nullptr, ThreadRoutine, td); // 创建线程运行ThreadRoutine
            }

        } 
        // 这个函数是被多线程调用的
        // 这里如果我们直接使用前面的文章中使用的read函数进行读取就无法保证获取的数据是我们想要的与计算相关的信息,
        // 因此此时需要我们自己根据自定义的协议来处理数据 -- 在这里我们规定每次我们需要的完整的报文是 "7"\r\n"10 + 20"\r\n 这样的形式,前面的数字表示有效报文的长度,报文长度与有效载荷之间通过"\r\n"来隔开
        void ServiceIO(int sock, const std::string &ip, const uint16_t &port){
            std::string inbuffer; // 放在外面,防止每次循环被释放
            while(true){
                // 0. 怎么保证读到的是一个完整的字符串报文? "7"\r\n""10 + 20"\r\n
                std::string package;
                int n = ReadPackage(sock, inbuffer, &package); // 对获取的数据流进行处理,分出协议所需要的报文
                if (n == -1)
                    break;
                else if (n == 0)
                    continue;
                else{
                    // 一定得到了一个 "7"\r\n""10 + 20"\r\n
                    // 1. 你需要的只是有效载荷 "10 + 20"
                    package = RemoveHeader(package, n); // 对报文的有效载荷进行分离,获取有效载荷
                    // decode
                    // 2. 假设已经读到了一个完整的string "10 + 20"
                    Request req;
                    req.Deserialize(package); // 对读到的request字符串要进行反序列化
                    // 3. 直接提取用户的请求数据啦
                    Response resp = _func(req); // 业务逻辑!处理响应的计算业务
                    // 4. 给用户返回响应 - 序列化
                    std::string send_string;
                    resp.Serialize(&send_string); // 对计算完毕的response结构要进行序列化,形成可发送字符串
                    // 5. 添加报头
                    send_string = AddHeader(send_string); // 给需要返回的结果添加报头
                    //encode
                    // 6. 发送到网络 -- 弱化
                    send(sock, send_string.c_str(), send_string.size(), 0); // 简易版本的发送
                }
            }
            close(sock);
        }
        ~TcpServer(){
            _listensock.Close();
        }
    private:
        uint16_t _port;
        Sock _listensock;
        func_t _func;
    };
}

Protocol.hpp

自定义协议类以及Jsoncpp库使用:

#include <jsoncpp/json/json.h>
#include "Util.hpp"
#define MYSELF 1
namespace protocol_ns{
	#define SEP " "
	#define SEP_LEN strlen(SEP)
	#define HEADER_SEP "\r\n"
	#define HEADER_SEP_LEN strlen(HEADER_SEP)

    // "长度"\r\n""_x _op _y"\r\n
    // "10 + 20" => "7"\r\n""10 + 20"\r\n => 报头 + 有效载荷
    // 请求/响应 = 报头\r\n有效载荷\r\n
    std::string AddHeader(const std::string &str){ // 添加报头
        std::cout << "AddHeader 之前:\n" << str << std::endl;
        std::string s = std::to_string(str.size());
        s += HEADER_SEP;
        s += str;
        s += HEADER_SEP;
        std::cout << "AddHeader 之后:\n" << s << std::endl;
        return s;
    }

    // "7"\r\n""10 + 20"\r\n => "10 + 20"
    std::string RemoveHeader(const std::string &str, const int &len){ // 移除报头
        std::cout << "RemoveHeader 之前:\n" << str << std::endl;
        std::string res = str.substr(str.size() - HEADER_SEP_LEN - len, len);
        std::cout << "RemoveHeader 之后:\n" << res << std::endl;
        return res;
    }
    int ReadPackage(int sock, std::string &inbuffer, std::string *package){ // 正确读取需要的报文
        std::cout << "ReadPackage inbuffer 之前:\n" << inbuffer << std::endl;
        // 边读取
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (s <= 0)
            return -1; // 读取出错
        buffer[s] = 0;
        inbuffer += buffer;
        std::cout << "ReadPackage inbuffer 之中:\n" << inbuffer << std::endl;
        // 边分析
        auto pos = inbuffer.find(HEADER_SEP);
        if (pos == std::string::npos)
            return 0;                                                    // 读取的不完善
        std::string lenStr = inbuffer.substr(0, pos);                    // 获取了头部字符串
        int len = Util::toInt(lenStr);                                   // "123" -> 123
        int targetpackageLen = lenStr.size() + len + 2 * HEADER_SEP_LEN; // 添加了报头的目标字符串长度
        if (inbuffer.size() < targetpackageLen)
            return 0;                                    // 读取的不完善
        *package = inbuffer.substr(0, targetpackageLen); // 提取报文
        // 到此为止,inbuffer 什么都没动
        inbuffer.erase(0, targetpackageLen); // 从inbuffer中直接移除整个报文
        std::cout << "ReadPackage inbuffer 之后:\n" << inbuffer << std::endl;
        return len; // 读取成功
    }
    // Request && Response都要提供序列化和反序列化功能
    class Request{
    public:
        Request()
        {}
        Request(int x, int y, char op) : _x(x), _y(y), _op(op)
        {}
        // 目前 "_x _op _y"
        bool Serialize(std::string *outStr){
            *outStr = "";
#ifdef MYSELF
            std::string x_string = std::to_string(_x);
            std::string y_string = std::to_string(_y);
            *outStr = x_string + SEP + _op + SEP + y_string;
            std::cout << "Request Serialize:\n" << *outStr << std::endl;
#else
            Json::Value root; // Value:一种万能对象,接受任意的kv对象
            root["x"] = _x;
            root["y"] = _y;
            root["op"] = _op; // 填充字段
            // Json::FastWriter writer; // Writer:是用来进行序列化的 struct -> string
            Json::StyledWriter writer; // 将Json转成好看一点的字符串
            // {
            //     "op" : 43,
            //     "x" : 1,
            //     "y" : 1
            // }
            *outStr = writer.write(root);
#endif 
            return true;
        }
        bool Deserialize(const std::string &inStr){
#ifdef MYSELF
            // inStr : 10 + 20 => [0]=>10, [1]=>+, [2]=>20
            // string -> vector
            std::vector<std::string> result;
            Util::StringSplit(inStr, SEP, &result);
            if (result.size() != 3)
                return false;
            if (result[1].size() != 1)
                return false;
            _x = Util::toInt(result[0]);
            _y = Util::toInt(result[2]);
            _op = result[1][0];
#else
            Json::Value root;
            Json::Reader reader; // Reader:用来进行反序列化的
            reader.parse(inStr, root);
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _op = root["op"].asInt();
#endif
            return true;
        }
        ~Request()
        {}
    public:
        // _x + _op + _y
        int _x;
        int _y;
        char _op;
    };

    class Response{
    public:
        Response()
        {}
        Response(int result, int code) : _result(result), _code(code)
        {}
        bool Serialize(std::string *outStr){
            *outStr = "";
#ifdef MYSELF
            std::string res_string = std::to_string(_result);
            std::string code_string = std::to_string(_code);
            *outStr = res_string + SEP + code_string;
            std::cout << "Response Serialize:\n" << *outStr << std::endl;
#else
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;
            // Json::FastWriter writer;
            Json::StyledWriter writer;
            *outStr = writer.write(root);
#endif
            return true;
        }

        bool Deserialize(const std::string &inStr){
#ifdef MYSELF
            std::vector<std::string> result;
            Util::StringSplit(inStr, SEP, &result);
            if (result.size() != 2)
                return false;
            _result = Util::toInt(result[0]);
            _code = Util::toInt(result[1]);
#else
            Json::Value root;
            Json::Reader reader; // Reader:用来进行反序列化的
            reader.parse(inStr, root);
            _result = root["result"].asInt();
            _code = root["code"].asInt();
#endif
            return true;
        }
        ~Response()
        {}
    public:
        int _result;
        int _code; // 0->success 1,2,3,4代表不同的错误
    };
}

// Util.hpp 工具类的实现
class Util
{
public:
    // 输入: const &
    // 输出: *
    // 输入输出: &
    static bool StringSplit(const string &str, const string &sep, vector<string> *result){
        size_t start = 0;
        // + 20
        // "abcd efg" -> for(int i = 0; i < 10; i++) !=  for(int i = 0; i <= 9; i++)
        while (start < str.size()){
            auto pos = str.find(sep, start);
            if (pos == string::npos) break;
            result->push_back(str.substr(start, pos-start));
            // 位置的重新reload
            start = pos + sep.size();
        }
        if(start < str.size())  result->push_back(str.substr(start));
        return true;
    }
    static int toInt(const std::string &s){
        return atoi(s.c_str());
    }

CalculatorServer.cc

Response calculate(const Request &req){ // 主体的计算处理模块
    Response resp(0, 0);
    switch (req._op){
    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;
    }

    return resp;
}

int main(){
    uint16_t port = 8081;
    std::unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port)); // TODO
    tsvr->InitServer();
    tsvr->Start();
    return 0;
}

CalculatorClient.cc

using namespace protocol_ns;
static void usage(std::string proc){
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}
enum{
    LEFT,
    OPER,
    RIGHT
};
// 10+20
Request ParseLine(const std::string &line){ // 将输入的数据通过ParseLine函数进行分割并添加到请求所需要的变量中
    std::string left, right;
    char op;
    int status = LEFT;
    int i = 0;
    while(i < line.size()){
        // if(isdigit(e)) left.push_back;

        switch (status){
        case LEFT:
            if (isdigit(line[i]))
                left.push_back(line[i++]);
            else
                status = OPER;
            break;
        case OPER:
            op = line[i++];
            status = RIGHT;
            break;
        case RIGHT:
            if (isdigit(line[i]))
                right.push_back(line[i++]);
            break;
        }
    }
    Request req;
    std::cout << "left: " << left << std::endl;
    std::cout << "right: " << right << std::endl;
    std::cout << "op: " << op << std::endl;
    req._x = std::stoi(left);
    req._y = std::stoi(right);
    req._op = op;
    return req;
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[]){
    if (argc != 3){
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    Sock sock;
    sock.Socket();
    int n = sock.Connect(serverip, serverport);
    if (n != 0)
        return 1;
    std::string buffer;
    while (true){
        std::cout << "Enter# "; // 1+1,2*9 // 这里的目标是要将输入的数据构建成前面的形式
        std::string line;
        std::getline(std::cin, line);
        Request req = ParseLine(line);
        std::cout << "test: " << req._x << req._op << req._y << std::endl;
        // 1. 序列化
        std::string sendString;
        req.Serialize(&sendString);
        // 2. 添加报头
        sendString = AddHeader(sendString);
        // 3. send
        send(sock.Fd(), sendString.c_str(), sendString.size(), 0);
        // 4. 获取响应
        std::string package;
        int n = 0;
    START:
        n = ReadPackage(sock.Fd(), buffer, &package);
        if (n == 0)
            goto START;
        else if (n < 0)
            break;
        else
        {}
        // 5. 去掉报头
        package = RemoveHeader(package, n);
        // 6. 反序列化
        Response resp;
        resp.Deserialize(package);
        std::cout << "result: " << resp._result << "[code: " << resp._code << "]" << std::endl;
    }
    sock.Close();
    return 0;
}

在这里插入图片描述

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

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

相关文章

通讯网关软件008——利用CommGate X2Mysql实现OPC数据转储Mysql

本文介绍利用CommGate X2MYSQL实现从OPC Server读取数据并转储至MYSQL数据库。CommGate X2MYSQL是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现从OPC Server读取数据并转储至MYSQL数据…

Go语言进化之路:泛型的崛起与复用的新篇章

一、引言 泛型编程在许多编程语言中都是一项非常强大的特性&#xff0c;它可以使程序更加通用、具有更高的重用性。然而&#xff0c;Go语言在很长一段时间内一直没有提供泛型功能。在过去的一些版本中&#xff0c;Go语言开发者试图引入泛型&#xff0c;但最终都因为各种原因被…

分布式系统——分布式系统知识脑图

摘要 本博文主要介绍分布式系统知识脑图&#xff0c;帮助大家更好的快速的了解分布式系统相关知识。同时也是为大家在工作中应对分布式系统设计提供相关参考。 一、分布式系统知识脑图 博文参考

origin自定义颜色

点击图例&#xff0c;然后点击菜单栏的颜色右边的三角形。选择自定义颜色 在红绿蓝里输入自己想要的颜色配比。点击确定 如图&#xff0c;定义好五组颜色。我这里用的是比较经典的五色配图 蓝&#xff1a;1,86,153 黄&#xff1a;250,192,15 橙&#xff1a;243,118,74 浅蓝…

mysql workbench常见问题

1、No database selected Select the default DB to be used by double-clicking its name in the SCHEMAS list in the sidebar 方法一&#xff1a;双击你要使用的库 方法二&#xff1a;USE 数据库名 2、复制表名&#xff0c;字段名 3、保存链接

【PostgreSQL内核学习(十一)—— (CreatePortal)】

CreatePortal 概述CreatePortal 函数GetPortalByName 函数PortalHashTableLookup 函数 MemoryContextAllocZero 函数 AllocSetContextCreate 函数ResourceOwnerCreatePortalHashTableInsert总结 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们…

AI大模型服务应用场景

大模型是指模型具有庞大的参数规模和复杂程度的机器学习模型。在深度学习领域&#xff0c;大模型通常是指具有数百万到数十亿参数的神经网络模型。这些模型通常在各种领域&#xff0c;例如自然语言处理、图像识别和语音识别等&#xff0c;表现出高度准确和广泛的泛化能力。伴随…

2023-9

内核向应用层发送netlink单播消息&#xff1a; nlmsg_unicast -> netlink_unicast -> netlink_sendskb -> __netlink_sendskb -> 把skb链入struct sock 的 sk_receive_queue 链表中&#xff0c;再调用sk->sk_data_ready(sk); -> sock_def_readable -> wak…

排序算法:归并排序(递归和非递归)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关排序算法的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

关于使用API接口获取商品数据的那些事(使用API接口获取商品数据的步骤和注意事项。)

随着电商行业的不断发展&#xff0c;越来越多的企业和个人需要获取各大电商平台上的商品数据。而最常用的方法是使用API接口获取商品数据。本文将为您介绍使用API接口获取商品数据的步骤和注意事项。 一、选择API接口 首先需要了解各大电商平台提供的API接口&#xff0c;目前…

算法通关村第14关【黄金】| 数据流的中位数

思路&#xff1a;使用一个小根堆一个大根堆来找中位数 小根堆保存较大的一半数字&#xff0c;大根堆保存较小的一半数字 奇数queMin的队头即为中位数&#xff0c;偶数queMin和queMax队头相加/2为中位数 初始状态&#xff1a; queMin: [] queMax: [] 添加数字 1&#xff1a; …

【Java 基础篇】Java 进程详解:从基础到实践

Java 是一种广泛应用于各种类型的软件开发的编程语言&#xff0c;而与 Java 紧密相关的一个概念就是进程。本篇博客将从基础开始&#xff0c;详细介绍 Java 进程的概念、创建、管理以及一些实际应用场景。无论您是初学者还是有一定经验的开发者&#xff0c;都能从本文中获取有关…

如何高效批量查询快递单号,提高工作效率?

在日常生活中&#xff0c;快递单号的查询是一项常规任务。过去&#xff0c;这项任务需要通过人工一个一个地在快递平台上查询&#xff0c;既耗时又费力。然而&#xff0c;随着科技的发展&#xff0c;我们有了更多的工具可以帮助我们高效地完成这项任务。本文将介绍如何使用固乔…

【List篇】LinkedList 详解

目录 成员变量属性构造方法add(), 插入节点方法remove(), 删除元素方法set(), 修改节点元素方法get(), 取元素方法ArrayList 与 LinkedList的区别Java中的LinkedList是一种实现了List接口的 双向链表数据结构。链表是由一系列 节点(Node)组成的,每个节点包含了指向 上一个…

Java“牵手”1688商品评论数据采集+1688商品评价接口,1688商品追评数据接口,行业商品质检接口,1688API接口申请指南

1688商品评论平台是阿里巴巴集团旗下的一个在线服务市场平台&#xff0c;为卖家提供商品评价服务。平台上有多种评价工具和数据支持&#xff0c;可以帮助卖家更好地了解商品的质量和特点&#xff0c;从而做出更明智的采购决策。 1688商品评论平台支持多种评价方式&#xff0c;…

R语言画多变量间的两两相关性图

语言代码&#xff1a; setwd("D:/Desktop/0000/R") #更改路径df<-read.csv("kaggle/Seed_Data.csv") head(df) df$target<-factor(df$target) # 因为目标是数字&#xff0c;所以加他&#xff0c;不加会报错 cols<-c("steelblue","…

《动手学深度学习 Pytorch版》 7.1 深度卷积神经网络(LeNet)

7.1.1 学习表征 深度卷积神经网络的突破出现在2012年。突破可归因于以下两个关键因素&#xff1a; 缺少的成分&#xff1a;数据 数据集紧缺的情况在 2010 年前后兴起的大数据浪潮中得到改善。ImageNet 挑战赛中&#xff0c;ImageNet数据集由斯坦福大学教授李飞飞小组的研究人…

OpenCV中的HoughLines函数和HoughLinesP函数到底有什么区别?

一、简述 基于OpenCV进行直线检测可以使用HoughLines和HoughLinesP函数完成的。这两个函数之间的唯一区别在于,第一个函数使用标准霍夫变换,第二个函数使用概率霍夫变换(因此名称为 P)。概率版本之所以如此,是因为它仅分析点的子集并估计这些点都属于同一条线的概率。此实…

威胁的数量、复杂程度和扩散程度不断上升

Integrity360 宣布了针对所面临的网络安全威胁、数量以及事件响应挑战的独立研究结果。 数据盗窃、网络钓鱼、勒索软件和 APT 是最令人担忧的问题 这项调查于 2023 年 8 月 9 日至 14 日期间对 205 名 IT 安全决策者进行了调查&#xff0c;强调了他们的主要网络安全威胁和担忧…

评价指标分类

声明 本文是学习GB-T 42874-2023 城市公共设施服务 城市家具 系统建设实施评价规范. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件确立了城市家具系统建设实施的评价原则、评价流程&#xff0c;给出了评价指标&#xff0c;描述了 方…