【计网】从零开始掌握序列化 --- 实现网络计算器项目

在这里插入图片描述

​​​请各位保持头脑清醒,
​​​读些好书,做点有用的事,
​​​快快乐乐地生活。
​​​ --- 斯蒂芬·金 《肖申克的救赎》---

从零开始掌握序列化

  • 1 知识回顾
  • 2 服务器框架
  • 3 客户端框架
  • 4 运行测试

1 知识回顾

前面两篇文章学习中基础知识和三层结构的实现
我们学习了:

序列化与反序列化:

  1. 必要性:协议的本质是双方都认识的结构化数据,为了传输结构化的数据就需要进行序列化,为了从数据流中获取结构化数据就要进行反序列化!
  2. 本质:序列化的本质是将结构化的数据转换成字符串,将字符串发送给客户端。客户端根据协议进行反序列化获取到结构化数据!
  3. 序列化与反序列化的方法有很多种,可以自行编写也可以使用第三方库,比如JSON库

并且重新理解了TCP协议:

TCP协议

  1. 支持全双工通信:传输层会创建两个缓冲区:发送缓冲区和接收缓冲区。发送和接收是分开的,所以天然支持全双工
  2. 通信函数本质:read , write , send , recv本质上都是拷贝函数!他们都是讲数据拷贝到缓冲区中,并不关心缓冲区中的数据何时以何种方式发送给对方,系统负责缓冲区的刷新!
  3. 传输层是属于OS的,传输缓冲区的本质和文件缓冲区一样,在操作系统看起来都是向文件中进行刷新写入,用户不需要考虑!

最重要的是将Socket进行了程序重构,具体的细节在TCP协议中讲解过。这样将通信功能彻底解耦出来:

  1. 将socket系列操作分类封装,设计为基类,派生出Tcp和Udp两种具体的Socket!
  2. 基类都需要进行创建socket文件 、进行绑定、 进入listen 、获取链接、 申请链接…由于两种类的操作方式不一致,所以基类只需要进行一个声明就可以,具体实现在派生类中完成!
  3. 依照基础的方法进行组合就可以实现生成服务器端Socket和客户端Socket!

对应网络计算器的需求,我们设计了Request和Response两个结构体作为通信的协议!并且我们通过JSON库来进行协议内部的序列化与反序列化!为了保证可以获取完整的结构化数据,我们设计了独特的报文结构:
len\r\n{json}\r\n这样可以保证从数据流中获取完整的报文结构!!!

2 服务器框架

在这里插入图片描述
服务器的框架是基于这样的三层结构实现的:

  1. 传输层TcpServer:负责从Socket文件中获取链接,传输层不需要进行IO,获取到连接就让会话层通过连接获取数据!
  2. 会话层Service:根据传输层给的连接,从Sockfd文件中读取数据,解析出报文结构中的数据字符串,然后通过协议分离出结构化数据。该层只负责数据的解析,数据的处理交给应用层进行!
  3. 应用层Process:应用层是具有的业务逻辑,根据会话层解析出的数据,进行数据处理!这里使用的是网络计算器的业务逻辑,也就是执行加减乘除运算!

基于这样的结构我们上层的服务器代码逻辑是很好写的:

#include "TcpServer.hpp"
#include "Service.hpp"
#include "NetCal.hpp"
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);//业务层NetCal cal;Service ser(std::bind(&NetCal::Calculator , &cal , std::placeholders::_1));//IO层std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::bind(&Service::IOExecute, &ser,std::placeholders::_1,std::placeholders::_2),port);//进入通信循环!tsvr->Loop();return 0;
}

可以看到我们只是使用了两次的bind绑定就实现了三层结构的实现,十分非简洁明了。只需等待客户端传入数据即可!

3 客户端框架

客户端的框架和服务端类似:

  1. 首先客户端在执行程序时需要传入服务器的IP地址和端口号!
  2. 然后通过封装的Socket类创建客户端Socket文件!对于IP地址和端口号的处理都封装在了类方法中,使用起来十分简单快捷!
#include <iostream>
#include "Socket.hpp"
#include "Protocol.hpp"using namespace socket_ns;int main(int argc, char *argv[])
{// 根据参数获取服务器IP 与 端口号if (argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);// 工厂建立TcpSocket链接SockSPtr sock = std::make_shared<TcpSocket>();if (!sock->BuildClientSocket(ip, port)){std::cerr << "connect error!" << std::endl;exit(1);}std::string packagestream;//业务逻辑while (true){}return 0;
}

接下来我们来进行客户端数据通信的逻辑:

  1. 基础数据的获取这里为了快捷直接使用随机数进行初始化!
  2. 发送数据:根据协议快速构建Request,然后对其进行序列化,然后加入报头形成完整报文,发送给服务器
  3. 接收数据:从Socket文件中读取数据流,去除报头,检查是否具有完整报文,有完整报文就进行反序列化得到Response,打印结果即可!!!
int main(int argc, char *argv[])
{//...srand(time(nullptr));std::string arr = "+-*/%&^!";std::string packagestream;int cnt = 3;while (cnt--){// 传入数据int x = rand() % 50;usleep(1000);int y = rand() % 50;char oper = arr[y % arr.size()];// 1. 构建requestauto req = Factory::BuildRequestDefault();req->SetValue(x, y, oper);// 2. 进行序列化std::string jsonstr;req->Serialize(&jsonstr);std::cout << "jsonstr: " << jsonstr << std::endl;// 3. 添加报头std::string reqstr = Encode(jsonstr);// 4. 发送数据sock->Send(reqstr);// 5. 接收数据while (true){ssize_t n = sock->Recv(&packagestream);if (n <= 0)break;// 6. 去除报头std::string resstr = Decode(packagestream);std::cout << "resstr: " << resstr << std::endl;if (resstr.empty())continue;auto res = Factory::BuildResponseDefault();// 7. 反序列化std::cout << "----------------"<<std::endl;res->Deserialize(resstr);res->PrintResult();break;}}return 0;
}

这样客户端逻辑就写好了!!!

4 运行测试

我们进行一下简单的测试首先注意因为我们使用JSON库编译时要加入对应的编译动态库选项:

.PHONY:all
all:calserver calclient
calserver:ServerMain.ccg++ -o $@ $^ -std=c++14 -lpthread -ljsoncpp
calclient:ClientMain.ccg++ -o $@ $^ -std=c++14 -ljsoncpp
.PHONY:clean 
clean:rm -rf calserver calclient

编译之后我们来看运行效果:
在这里插入图片描述
参照对应的ASCII码表:

运算符ACSII码
+43
-45
*42
/47
%37
&38
*42
!33

可以验证结果是正确的!!!
这样网络计算机的小项目就完成了!!!

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

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

相关文章

ROS第六梯:ROS+VSCode+C++消息发布和订阅

第一步&#xff1a;创建ROS工作空间&#xff0c;并在工作空间下创建名为srr_pkg的功能包&#xff0c;具体步骤参考第二章。 第二步&#xff1a;在src下创建publisher.cpp作为发布节点代码文件&#xff0c;创建subscriber.cpp作为订阅节点代码文件&#xff1a; 主要步骤是&#…

数字通云平台智慧政务 login 存在登录绕过

0x01 漏洞描述&#xff1a; 数字通云平台智慧政务OA产品是基于云计算、大数据、人工智能等先进技术&#xff0c;为政府部门量身定制的智能化办公系统。该系统旨在提高政府部门的办公效率、协同能力和信息资源共享水平&#xff0c;推动电子政务向更高层次发展。 数字通云平台 智…

【高分系列卫星简介——高分五号卫星(GF-5)】

高分五号卫星&#xff08;GF-5&#xff09; 高分五号&#xff08;GF-5&#xff09;卫星是中国高分辨率对地观测系统重大专项系列中的一颗重要卫星&#xff0c;主要承担着遥感、测绘等任务。以下是对高分五号卫星的详细介绍&#xff1a; 一、基本信息 国籍&#xff1a;中国研…

Android JNI 调用流程

为啥要用JNI&#xff0c;我个人理解是&#xff0c;Java 代码效率不够高&#xff0c;代码调用底层逻辑隔着一层Java 虚拟机&#xff0c;不能直接操控底层硬件&#xff0c;而C/C 可以直接操控硬件设备&#xff0c;对于需要效率更高的操作&#xff0c;就需要通过C/C 完成。。 比如…

GNU链接器(LD):存储命令(MEMORY)用法及实例解析

0 参考资料 GNU-LD-v2.30-中文手册.pdf GNU linker.pdf1 前言 一个完整的编译工具链应该包含以下4个部分&#xff1a; &#xff08;1&#xff09;编译器 &#xff08;2&#xff09;汇编器 &#xff08;3&#xff09;链接器 &#xff08;4&#xff09;lib库 在GNU工具链中&…

最小花费爬楼梯(动态规划)问题

目录 一题目&#xff1a; 二思路&#xff1a; 三代码&#xff1a; 一题目&#xff1a; 最小花费爬楼梯_牛客题霸_牛客网 二思路&#xff1a; 思路&#xff1a;动态规划找前后规律化简题意&#xff1a;此题想要的结果其实就是能上到顶楼也就是&#xff1a; 分为&#xff1…

【华为】用策略路由解决双出口运营商问题

需求描述 不同网段访问互联网资源时&#xff0c;走不同的出口&#xff0c;即PC1走电信出口&#xff0c;PC2走移动出口。 客户在内网接口下应用策略路由后往往出现无法访问内网管理地址的现象&#xff0c;该举例给出解决办法。 拓扑图 基础配置 #sysname R1 # # interface G…

QT 中的信号与槽机制详解

目录 一、引言 二、信号与槽的基本概念 1.信号&#xff08;Signals&#xff09; 2.槽&#xff08;Slots&#xff09; 三、声明信号和槽 1.声明信号和槽 2.发射信号 3.连接信号和槽 四、高级特性 1.多信号连接到一个槽 2.一个信号连接到多个槽 3.断开信号和槽的连…

SpringBoot和JPA初探

目录 SpringBoot和JPA初探0.准备条件1.创建JPA项目2.项目3.总结 SpringBoot和JPA初探 我们使用SpringBootJPA做一个简单的API接口演示&#xff0c;通过一个简单的例子让大家对Spring Data JPA有一个整体的认知。 0.准备条件 IntelliJ IDEAjdk 1.8mysql 8.0maven 3.8.x 1.创…

多智能体笔记本专家系统:集成CrewAI、Ollama和自定义Text-to-SQL工具

在这个项目中&#xff0c;我们的目标是创建一个由多智能体架构和本地大语言模型&#xff08;LLM&#xff09;驱动的个人笔记本电脑专家系统。该系统将使用一个SQL数据库&#xff0c;包含有关笔记本电脑的全面信息&#xff0c;包括价格、重量和规格。用户可以根据自己的特定需求…

前缀和问题

洛谷题面 这个其实可以当模板了。 代码&#xff1a; #include<bits/stdc.h> using namespace std; const int N1e510; int sum[N]; int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int n,m,x;cin>>n;for(int i1;i<n;i){cin>>x;sum[i]sum[i…

【算法】模拟:(leetcode)495.提莫攻击(easy)

目录 题目链接 题目介绍 解法 代码 题目链接 495. 提莫攻击 - 力扣&#xff08;LeetCode&#xff09; 题目介绍 解法 模拟 分情况讨论。 当寒冰再次中毒时&#xff0c;上次「中毒」是否已经结束。 ​ 当上次中毒已经结束了&#xff0c;那么上次「中毒」维持的时间就是 …

【图书管理系统】数据结构、链表、C/C++课程设计

【图书管理系统】数据结构、链表、课程设计、C/C 一、前言二、系统概述2.1 功能简介2.2 系统架构 三、数据结构与算法3.1 单链表的定义与操作3.2 图书信息结构体 四、功能实现详解4.1 主菜单选择系统4.2 信息注册系统4.3 用户登录系统4.4 访问图书系统 五、详细代码分析5.1 全局…

SegFormer网络结构的学习和重构

因为太多的博客并没有深入理解,本文是自己学习后加入自己深入理解的总结记录&#xff0c;方便自己以后查看。 segformer中encoder、decoder的详解。 学习前言 一起来学习Segformer的原理,如果有用的话&#xff0c;请记得点赞关注哦。 一、Segformer的网络结构图 网络结构&…

一道简单的css动态宽度问题?

题目&#xff1a; 如何实现left的宽度随着父级宽度改变而改变&#xff1f; .content {display: flex;height: 300px;}.left {max-width: 200px;min-width: 100px;background: red;}.right {flex: 1;background: yellow;}.left { max-width: 200px; min-width: 100px; backgroun…

【Redis】分布式锁之 Redission

一、基于setnx实现的分布式锁问题 重入问题&#xff1a;获得锁的线程应能再次进入相同锁的代码块&#xff0c;可重入锁能防止死锁。例如在HashTable中&#xff0c;方法用synchronized修饰&#xff0c;若在一个方法内调用另一个方法&#xff0c;不可重入会导致死锁。而synchroni…

IntraWeb开发Web网站时对数据库“增、删、改、查”的操作

delphi源代码&#xff1a;示例两列布局带顶部汉堡菜单&#xff0c;对数据库“增、删、改、查”的操作&#xff08;兼容电脑与手机&#xff09; 功能&#xff1a;交互式网页&#xff0c;两列布局&#xff0c;顶部汉堡菜单&#xff0c;点击汉堡图标关闭左侧栏&#xff0c;这里演示…

大漠yolo-数据集标注

参考 【按键精灵】大漠插件yolo环境配置_哔哩哔哩_bilibili 1. 2. 3.启动

蓝桥杯【物联网】零基础到国奖之路:十一. LORA

蓝桥杯【物联网】零基础到国奖之路:十一. LORA 第一节 LORA理论第二节 Lora的无线收发数据1&#xff0c;硬件解读2&#xff0c;CubeMX配置3&#xff0c;MDK代码 第一节 LORA理论 Lora是一种长距离、低功耗的无线通信技术&#xff0c;专为iot和远程应用设计。Lora技术基于半双工…

Threejs创建胶囊体

上一章节实现了圆环结的绘制&#xff0c;这节来绘制胶囊体&#xff0c;胶囊体就是胶囊的形状&#xff0c;上下是一个半球&#xff0c;中间是一个圆柱体&#xff0c;如上文一样&#xff0c;先要创建出基础的组件&#xff0c;包括场景&#xff0c;相机&#xff0c;灯光&#xff0…