当前位置: 首页 > news >正文

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

在这里插入图片描述

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

从零开始掌握序列化

  • 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

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 细说硫酸钙防静电地板的材质结构和优势特点
  • 产品经理面试整理-常见面试问题
  • 开放原子开源基金会OPENATOM
  • 京准电钟:NTP网络校时服务器助力校园体育场馆
  • 论文 | Reframing Instructional Prompts to GPTk’s Language
  • 等保测评与企业内部安全管理体系的融合
  • 虚幻引擎游戏保存/加载存档功能
  • 23个Python在自然语言处理中的应用实例
  • TS系列(1):TS是什么?如何使用?
  • 基础容器.
  • Elasticsearch:检索增强生成背后的重要思想
  • Facebook对现代社交互动的影响
  • 【30天玩转python】高级面向对象编程
  • 哈希——字符串哈希
  • Postman 发送 JSON 格式数据
  • Java IO学习笔记一
  • Javascript弹出层-初探
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2和SSE,以及如何选择
  • leetcode386. Lexicographical Numbers
  • leetcode讲解--894. All Possible Full Binary Trees
  • Python socket服务器端、客户端传送信息
  • windows下如何用phpstorm同步测试服务器
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 盘点那些不知名却常用的 Git 操作
  • 深入浅出webpack学习(1)--核心概念
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • C# - 为值类型重定义相等性
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • #DBA杂记1
  • #Z0458. 树的中心2
  • $NOIp2018$劝退记
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (C++17) optional的使用
  • (js)循环条件满足时终止循环
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (差分)胡桃爱原石
  • (分布式缓存)Redis哨兵
  • (附源码)基于ssm的模具配件账单管理系统 毕业设计 081848
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)使用VMware vSphere标准交换机设置网络连接
  • (转)为C# Windows服务添加安装程序
  • (转)项目管理杂谈-我所期望的新人
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET和.COM和.CN域名区别
  • .NET上SQLite的连接
  • /usr/bin/python: can't decompress data; zlib not available 的异常处理
  • @Autowired 与@Resource的区别
  • [20150707]外部表与rowid.txt
  • [AI Embedchain] 开始使用 - 全栈
  • [android] 看博客学习hashCode()和equals()
  • [C#基础]说说lock到底锁谁?
  • [C++][opencv]基于opencv实现photoshop算法色阶调整
  • [C++提高编程](三):STL初识