构建应用层(TCP)自定义协议:深入理解序列化与反序列化技术

🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

    • `网络版计算器`
    • `序列化 和 反序列化`
        • `重新理解 read、write、recv、send 和 tcp 为什么支持全双工`
        • `自定义协议期望的报文格式`
    • `模板方法模式实现Socket封装`
          • `下面给出序列化与反序列化的关键代码:`
          • `序列与反序列化的调用逻辑代码`
    • `Jsoncpp`
            • `序列化`
            • `反序列化`

网络版计算器

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.

约定方案:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做 "序列化" 和 "反序列化"

序列化 和 反序列化

在这里插入图片描述
只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是 ok 的. 这种约定, 就是 应用层协议

为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。

  • 我们要引入序列化和反序列化,直接采用现成的方案 – jsoncpp库
  • 我们要对 socket 进行字节流的读取处理
重新理解 read、write、recv、send 和 tcp 为什么支持全双工

在这里插入图片描述

  • 在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工(每一个连接内部都有发送与接收缓冲区)。
  • 这就是为什么一个 tcp sockfd 读写都是它的原因
  • 在实际数据什么时候发,发多少,出错了怎么办,由 TCP 控制,所以 TCP 叫做传输控制协议
  • 当我们调用 write 或 send 等系统调用将数据发送给另一台主机时,这些操作实际上只是将数据内容拷贝到本地系统的发送缓冲区中。至于数据的实际发送时间、发送方式以及错误处理,则完全由传输层协议(如TCP)负责决定和控制,这也是TCP被称为传输控制协议的原因。因此,严格来说,send、write 以及对应的接收函数 recv、read 主要是负责数据在应用程序与内核缓冲区之间的拷贝,而不是直接负责数据的网络传输。
自定义协议期望的报文格式

在这里插入图片描述

  • 我们把tcp中读到的报文可能有半个,或则一个半等叫做TCP粘报问题。

模板方法模式实现Socket封装

模板方法设计模式是一种行为型设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中实现。这种设计模式允许子类在不改变算法结构的情况下,重新定义算法的某些步骤。
模板方法模式定义了一个操作中的算法的框架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

#pragma once#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <functional>#include "Log.hpp"
#include "InetAddr.hpp"class Socket;
using Socket_ptr = std::shared_ptr<Socket>;
const static int g_backlog = 8;enum EixtError
{SOCK_ERROE = 1,BIND_ERROR,LISTEN_ERROR,USAGE_ERROR
};class Socket
{
public:virtual void BuildSocket() = 0;virtual void BindSocket(InetAddr &addr) = 0;virtual void ListenSocket() = 0;virtual Socket_ptr Accepter(InetAddr *addr) = 0;virtual bool ConnectSocket(InetAddr &addr) = 0;virtual int Getsockfd() = 0;virtual int revc(std::string *out) = 0;virtual int send(std::string &in) = 0;public:void CreateTcpSocket(InetAddr &addr){BuildSocket();BindSocket(addr);ListenSocket();}bool ClientConnect(InetAddr &addr){BuildSocket();return ConnectSocket(addr);}
};class TcpSocket : public Socket
{
public:TcpSocket(int fd = -1) : _sockfd(fd){}void BuildSocket() override{// 创建流式套接字_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){LOG(FATAL, "socket create failed...");exit(SOCK_ERROE);}LOG(DEBUG, "socket create success..._sockfd:%d", _sockfd);}void BindSocket(InetAddr &addr) override{// 绑定struct sockaddr_in Seraddr;memset(&Seraddr, 0, sizeof(Seraddr));Seraddr.sin_family = AF_INET;Seraddr.sin_port = htons(addr.Getport());Seraddr.sin_addr.s_addr = inet_addr(addr.GetIP().c_str());int n = bind(_sockfd, (struct sockaddr *)&Seraddr, sizeof(Seraddr));if (n < 0){LOG(FATAL, "socket bind failed...");exit(BIND_ERROR);}LOG(DEBUG, "socket bind success...");}void ListenSocket() override{// listenint n = listen(_sockfd, g_backlog);if (n < 0){LOG(FATAL, "socket listen failed...");exit(LISTEN_ERROR);}}Socket_ptr Accepter(InetAddr *addr) override{struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int sockfd = accept(_sockfd, (struct sockaddr *)&clientaddr, &len);if (sockfd < 0){LOG(WARNING, "accept failed...");return nullptr;}LOG(DEBUG, "link success... sockfd:%d", sockfd);*addr = clientaddr;Socket_ptr sockptr = std::make_shared<TcpSocket>(sockfd);return sockptr;}bool ConnectSocket(InetAddr &addr) override{struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(addr.Getport());seraddr.sin_addr.s_addr = inet_addr(addr.GetIP().c_str());// 不需要显示的bind,系统自动bind// 客户端连接服务端int n = connect(_sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (n < 0){std::cout << "connet fail..." << std::endl;return false;}return true;}int revc(std::string *out) override{char inbuffer[1024];ssize_t n = recv(_sockfd, inbuffer, sizeof(inbuffer), 0);if (n > 0){inbuffer[n] = 0;*out += inbuffer; //}return n;}int send(std::string &in) override{int n = ::send(_sockfd, in.c_str(), in.size(), 0);return n;}int Getsockfd() override{return _sockfd;}private:int _sockfd;
};
下面给出序列化与反序列化的关键代码:
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>const std::string SEP = "\r\n";
std::string AddHeader(const std::string &Jsonstr)        // 将Json字符串加报头
{std::string lenstr = std::to_string(Jsonstr.size());string str = lenstr;str += SEP;str += Jsonstr;str += SEP;return str;
}
std::string Decode(std::string &inbuffer) // 判断收到的请求是否完整,比如有些请求是分多次收到的,所以需要判断是否完整
{auto pos = inbuffer.find(SEP);                   //找到第一个\r\n的位置std::string len_str = inbuffer.substr(0,pos);     //将有效载荷的长度计算出来并转为字符串if(len_str.empty()) return string();              //判断有效载荷的长度是否为空   int len = stoi(len_str);                          //将有效载荷的长度转为int类型int total = len+len_str.size()+2*SEP.size();      //计算一个完整的报头的长度if(inbuffer.size()<total) return string();        //如果请求的总大小小于一个报头的长度,说明请求不完整,返回空字符串std::string package = inbuffer.substr(pos+SEP.size(),len);    //将完整的请求取出来inbuffer.erase(0,total);                            //将处理过的请求从缓冲区中删除return package;                         //返回完整的请求
}class Request
{
public:Request(){}Request(int x, int y, char op) : _x(x), _y(y), _op(op){}bool Serialize(std::string *out) // 序列化,将结构体数据转为字符串{Json::Value root;            root["_x"] = _x;    // 给Json::Value对象赋值root["_y"] = _y;    root["_op"] = _op;Json::FastWriter writer;   // 构造一个Json::FastWriter对象std::string str = writer.write(root);    // 调用write方法将Json::Value对象转为字符串*out = str;return true;}bool Deserialize(const std::string &in) // 反序列化,将字符串转为结构体数据{Json::Value root;Json::Reader reader;   // 构造一个Json::Reader对象bool ret = reader.parse(in, root);   // 调用parse方法将字符串转为Json::Value对象if (ret == false){return false;}_x = root["_x"].asInt();   // 调用asInt方法将Json::Value对象转为int类型_y = root["_y"].asInt();_op = root["_op"].asInt();return true;}~Request(){}public:int _x;  //_x _op _yint _y;char _op;
};class Response
{
public:Response() {}Response(int result, int code) : _result(result), _code(code){}bool Serialize(std::string *out)   // 序列化{Json::Value root;root["_result"] = _result;root["_code"] = _code;Json::FastWriter writer;std::string str = writer.write(root);*out = str;return true;}bool Deserialize(const std::string &in)   // 反序列化{Json::Value root;Json::Reader reader;bool ret = reader.parse(in, root);if (ret == false){return false;}_result = root["result"].asInt();_code = root["code"].asInt();return true;}~Response(){}
public:int _result;int _code;
};
序列与反序列化的调用逻辑代码

服务端

 		while (true){// 1.接受到请求int n = sockfd->revc(&inbuffer);if (n < 0){LOG(DEBUG, "client %s quit...", clientMessage);break;}// 2.检查是否是完整的请求std::string package = Decode(inbuffer);   //检查是否是一个完整请求,如果有多个完整请求,提取出来if (package.empty()) continue;// 3.将请求反序列化req.Deserialize(inbuffer);// 4. 请求处理Response resp = _ct(req);// 5. 将处理结果序列化std::string send_str;resp.Serialize(&send_str);// 6. 添加报头send_str = AddHeader(send_str);// 7. 发送响应sockfd->send(send_str);}

客户端

        // 1. 构建一个请求// 2. 对请求进行序列化std::string send_str;req->Serialize(&send_str);// 3. 添加长度报头send_str = AddHeader(send_str);// 4. "len"\r\n"{}"\r\n//  发送请求cli->Send(send_str);// 5. 读取应答int n = cli->Recv(&inbuffer);if (n <= 0)break;std::string package = Decode(inbuffer);if (package.empty())continue;// 6. 我能保证package一定是一个完整的应答!// 6.1 构建一个应答auto resp = factory.BuildResponse();// 6.1 应答传入将其反序列化resp->Deserialize(package);// 7. 得到了结构化应答数据

Jsoncpp

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。

当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍:

安装:

ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
序列化

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化:

使用 Json::FastWriter:

 bool Serialize(std::string *out) // 序列化,将结构体数据转为字符串{Json::Value root;            root["_x"] = _x;    // 给Json::Value对象赋值root["_y"] = _y;    root["_op"] = _op;Json::FastWriter writer;   // 构造一个Json::FastWriter对象std::string str = writer.write(root);    // 调用write方法将Json::Value对象转为字符串*out = str;return true;}
反序列化

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化:

使用 Json::Reader:

  bool Deserialize(const std::string &in) // 反序列化,将字符串转为结构体数据{Json::Value root;Json::Reader reader;   // 构造一个Json::Reader对象bool ret = reader.parse(in, root);   // 调用parse方法将字符串转为Json::Value对象if (ret == false){return false;}_x = root["_x"].asInt();   // 调用asInt方法将Json::Value对象转为int类型_y = root["_y"].asInt();_op = root["_op"].asInt();return true;}

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

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

相关文章

开源大数据框架-Ambari+Bigtop如何写metainfo.xml文件

1.如何一键编译&#xff1f;一键安装&#xff1f;你没看错。 &#x1f449;&#x1f449;&#x1f449; https://gitee.com/tt-bigdata/ambari-env 你以为跟你闹着玩&#xff1f;人狠话不多&#x1f64d;‍♂️&#x1f64d;‍♂️&#x1f64d;‍♂️&#xff0c;直接上图&a…

国庆普及模拟2总结

目录 题目链接&#xff1a; 官方题解&#xff1a; 概述&#xff1a; 总结反思&#xff1a; 题目 T1: 题目分析&#xff1a; 错误代码&#xff1a; 错因&#xff1a; &#xff21;&#xff23;代码&#xff1a; T2&#xff1a; 题目分析&#xff1a; 赛时代码&#xf…

Centos Stream 9备份与恢复、实体小主机安装PVE系统、PVE安装Centos Stream 9

最近折腾小主机&#xff0c;搭建项目环境&#xff0c;记录相关步骤 数据无价&#xff0c;丢失难复 1. Centos Stream 9备份与恢复 1.1 系统备份 root权限用户执行进入根目录&#xff1a; cd /第一种方式备份命令&#xff1a; tar cvpzf backup.tgz / --exclude/proc --exclu…

CSS基础-常见属性

6、CSS三大特性 6.1 层叠性 如果样式发生冲突&#xff0c;则按照优先级进行覆盖。 6.2 继承性 元素自动继承其父元素、祖先元素所设置的某些元素&#xff0c;优先继承较近的元素。 6.3 优先级 6.3.1 简单分级 1、内联样式2、ID选择器3、类选择器/属性选择器4、标签名选择器/…

若无向图G(V,E)中含7个顶点,为保证图G在任何情况下都是连通的,则需要的边数最少是多少?

这乍一看是不是可抽象&#xff08;迷糊&#xff09;了&#xff0c;butttt待我小翻译一下。 先举少一点的例子&#xff0c;假如我们有三个点&#xff0c;我给你两条边&#xff0c;那是不是不管咋连都一定一定是连通的。 那我们再进一步&#xff0c;假如四个点呢&#xff1f;我给…

大厂进阶之CSS死磕牢记的7大知识点

本文主要讨论7大CSS知识点&#xff0c;个个都是金刚附体&#xff0c;干货满满&#xff1a; 1、移动端样式适配 2、回流和重绘 3、flex布局 4、BFC 5、CSS垂直居中方法 6、CSS两栏、三栏自适应布局 7、CSS单行、多行文本溢出省略号格式 一、如何做到移动端样式适配 1、媒体查询…

CloudCompare插件编写

预置环境&#xff1a;Windows10GitCMake3.23.3VS2019Qt5.14.2 编译CloudCompare工程 首先克隆CloudCompare工程&#xff0c;注意必须加上--recursive否则无法下载完整代码编译会失败&#xff1a; git clone --recursive https://github.com/CloudCompare/CloudCompare.git这…

鸢尾花书实践和知识记录[编程1-11二维和三维可视化]

作者空间 文章目录 思维导图函数使用 二维可视化方案平面散点图散点图的示例代码1&#xff1a;绘制鸢尾花的散点图代码2Plotly绘制散点图 数据类型和绘图工具的对应 平面等高线代码3生成等高线网格数据 plotly.express关键的绘图函数 Plotly的另一个模块代码4 Plotly生成的 热图…

李宏毅深度学习-梯度下降和Normalization归一化

Gradient Descent梯度下降 ▽ -> 梯度gradient -> vector向量 -> 下图中的红色箭头&#xff08;loss等高线的法线方向&#xff09; Tip1: Tuning your learning rates Adaptive Learning Rates自适应 通常lr会越来越小 Adaptive Learning Rates中每个参数都给它不同…

如何使用MethodChannel通信

文章目录 1 概念介绍2 实现方法3 经验总结我们在上一章回中介绍了Visibility组件相关的内容,本章回中将介绍Flutter与原生平台通信相关的内容.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 在移动开发领域以Android和IOS SDK开发出的应用程序叫原生开发,开发同一个程序…

Redis: Sentinel工作原理和故障迁移流程

Sentinel 哨兵几个核心概念 1 ) 定时任务 Sentinel 它是如何工作的&#xff0c;是如何感知到其他的 Sentinel 节点以及 Master/Slave节点的就是通过它的一系列定时任务来做到的&#xff0c;它内部有三个定时任务 第一个就是每一秒每个 Sentinel 对其他 Sentinel 和 Redis 节点…

【Canvas与徽章】金圈蓝底国庆75周年徽章

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>金边黑盾75周年</title><style type"text/css"&g…

万知:告别繁琐,轻松办公

零一万物这位科技创新的弄潮儿&#xff0c;带着它的最新杰作——万知&#xff0c;闪亮登场。这不仅仅是一个产品&#xff0c;它是对传统工作方式的一次轻松挑战。作为一款一站式AI问答、阅读和创作平台&#xff0c;万知旨在为用户提供高效、便捷的工作体验。万知通过集成多种智…

Suricata:开源网络分析和威胁检测

Suricata 是一款高性能、开源网络分析和威胁检测软件&#xff0c;被大多数私人和公共组织使用&#xff0c;并被主要供应商嵌入以保护他们的资产。 Suricata 功能 Suricata 提供全面的网络安全监控 (NSM) 功能&#xff0c;包括记录 HTTP 请求、捕获和存储 TLS 证书以及从网络流…

关于Vben Admin多标签页面缓存不生效的问题

情况说明 笔者在接手一个基于Vben Admin框架改造的vue3后台管理项目&#xff0c;客户要求在切换头部Tab页面时&#xff0c;不要刷新清空已经填写的表单页面或者表格。 然而&#xff0c;笔者根据Vben Admin的官方文档来配置多标签页面缓存后&#xff0c;页面每次切换后&#x…

光通信——FTTx

目录 FTTH模式 FTTO模式 FTTR模式 FTTB/CLAN/xDSL模式 FTTCabxDSL模式 根据接入光纤到用户的距离分类&#xff0c;PON可应用于光纤到交接箱&#xff08;FTTCab&#xff09;、光纤到大楼/路边&#xff08;FTTB/C&#xff09;、光纤到办公室&#xff08;FTTO&#xff0…

Tiny-universe手戳大模型TinyRAG--task4

TinyRAG 这个模型是基于RAG的一个简化版本&#xff0c;我们称之为Tiny-RAG。Tiny-RAG是一个基于RAG的简化版本&#xff0c;它只包含了RAG的核心功能&#xff0c;即Retrieval和Generation。Tiny-RAG的目的是为了帮助大家更好的理解RAG模型的原理和实现。 1. RAG 介绍 LLM会产…

一文彻底搞懂多模态 - 多模态理解+视觉大模型+多模态检索

文章目录 技术交流多模态理解一、图像描述1. 基于编码器-解码器的方法2. 基于注意力机制的方法3. 基于生成对抗网络的方法 二、视频描述三、视觉问答 视觉大模型一、通用图像理解模型二、通用图像生成模型 多模态检索一、单模态检索二、多模态检索三、跨模态检索 最近这一两周看…

自闭症寄宿学校:为孩子发掘多重才能

在教育的广阔天地里&#xff0c;每一片土壤都孕育着不同的生命&#xff0c;每一颗种子都蕴含着无限的可能。对于自闭症儿童而言&#xff0c;他们的世界或许更加独特与复杂&#xff0c;但同样充满了未被发掘的潜能与才华。在广州&#xff0c;星贝育园自闭症儿童寄宿制学校正以满…

OpenGL笔记十九之相机系统

OpenGL笔记十九之相机系统 —— 2024-10-02 晚上 文章目录 OpenGL笔记十九之相机系统1.运行1.1.游戏相机1.2.轨迹球相机 2.游戏相机与轨迹球相机切换3.博主笔记本要运行需要更改的文件更改1:28_OpenGL_CameraSystem/application/Application.cpp更改2:28_OpenGL_CameraSystem/a…