【计算机网络】网络版本计算器

此前我们关于TCP协议一直写的都是直接recv或者read,有了字节流的概念后,我们知道这样直接读可能会出错,所以我们如何进行分割完整报文?这就需要报头来解决了!

但当前我们先不谈这个话题,先从头开始。

将会着重理解OSI 7层模型中传输层向上的3层,并编码进行解释。

而恰好tcp/ip模型是4层(或5层),将OSI上三层统一压缩为1层应用层了,这究竟又有什么关系呢?
在这里插入图片描述
我们实际编程中也正是按照这种模式进行编写的。

目录

  • 1. 服务端
    • 1.1 会话层
    • 1.2 表示层
    • 1.3 应用层
  • 2. 客户端
    • 2.1表示层
  • 3. 完整代码:

1. 服务端

1.1 会话层

会话层是一个什么意思?
通俗理解就是建立连接与断开连接,也就是connect与accept

我们都是在server的hpp文件中进行的:

下段代码是一个大概的流程,这也就是编码实现会话层

int fd = accept...IO_service(fd等需要的参数...)close(fd)

1.2 表示层

这段话确实抽象
在这里插入图片描述
通俗理解就是:两个通信的主机按照一定的格式进行传输信息:即按照相同的请求协议与响应协议进行转化(序列化和反序列化)传输数据。

在之前的基于tcp的网络服务程序中,我们表面没有体现出这个,但实际上我们传输的协议就是传输字符串,这也是我们约定好的。

这层也是我们今天的重点。

由于我们当前是基于结构体传输(请求协议与响应协议),但是由于技术原因与业务原因导致直接使用结构体传输会导致各种各样的问题,所以我们序列化为固定格式的字符串进行传输,在反序列为你需要的协议格式进行操作。这是我们在应用层自定义协议就已经说过的了。
但因为是字节流的原因,所以recv或read到的字符串不一定是完整的请求,因此需要添加报头进行解决。

我们先来解决序列化与反序列化的问题。
其中序列化我们可以手写,也可以借助各种各样的库文件进行操作,这里我们选择使用json,最主要的原因还是因为可视化:


关于json我们可以大概的了解一下,熟悉一下接口即可。

class Request
{
public:Request(int x, int y, char oper): _x(x),_y(y),_oper(oper){}Request(){}void Serialization(std::string *out){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::StyledWriter writer;// Json::FastWriter writer;*out = writer.write(root);}void Deserialization(const std::string &in){Json::Reader reader;Json::Value root;reader.parse(in, root);_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();}void Print(){std::cout << _x << std::endl;std::cout << _y << std::endl;std::cout << _oper << std::endl;}~Request(){}public:int _x;int _y;int _oper;
};int main()
{Request req(1, 1, '*');std::string str;req.Serialization(&str);std::cout << str << std::endl;Request req1;req.Deserialization(str);req.Print();return 0;
}

注意由于json是第三方库,记得编译时-ljsoncpp
验证:
在这里插入图片描述


尽管我们现在是在进行序列化与反序列化,但是在序列化与反序列化前总得有请求请求协议与响应协议吧。


class Request
{
public:Request(int x, int y, char oper): _x(x),_y(y),_oper(oper){}Request(){}~Request(){}public:int _x;int _y;int _oper;
};class Response
{
public:Response(): _code(0),_desc("sucess"){}~Response(){}public:int _result;int _code; // 0:sucess, 1:div zero, 2:mod zero, 3:invalid operstd::string _desc;
};

上段代码就是两个协议的基本内容。

因此我们现在即可构建请求协议与响应协议的序列化与反序列化,没错,每种协议都需要构建序列化与反序列化:
当客户端构建数据构需要序列化再传输,服务端接收后再反序列化;
服务端处理完数据后再将响应协议对象序列化传输,客户端再反序列化得到结果。

这张图就很形象的展示了过程。
在这里插入图片描述
但是我们还需要解决如何获得的是一个完整的请求的问题,我们已经说过解决方案了,那就是添加报头,于是我们进一步完善协议。

那么添加的报头是怎样的格式?

"len"\r\n{json串}\r\n

这种形式是非常通用的,我们在HTTP协议中也可以看到这种形式的影子。

现在解释一下参数
len就是json串的长度,\r\n本质上就是换行,这样的健壮性更强(\r是回退到初始行,\n换行,但现在我们的\n基本上都包含了换行到新行的开头的功能了。)

现在解释一下为什么这么做:

// "le --> 残缺报文
// "len"\r\n{json} --> 残缺报文
// "len"\r\n{json}\r\n --> 完整报文
// "len"\r\n{json}\r\n"len"\r\n{jso --> 冗余报文
// "len"\r\n{json}\r\n"len"\r\n{json}\r\n"len"\r\n{json}\r\n --> 冗余报文

因为面向字节流,所以我们有可能得到的数据是以上样子,当我们这样设计报头时,不论何种情况都可以处理。
假设是第一种情况:我们先find \r\n,若是没有则说明当前是不完整的报文,继续recv即可。
若是第二种:我们由于find到了\r\b,所以就可得知json串的具体长度,根据具体长度得到是否为完整的报文。
若是第四/五种:我们直接截取最前方的完整报文即可。

故此时我们即可设计添加报头。

// 添加报头
std::string sep = "\r\n";std::string EnHeader(const std::string &packagestream)
{int len = packagestream.size();std::string ret = std::to_string(len);return ret + sep + packagestream + sep;
}// 注意这里我们传参是非const,原因在于当得到不完整报文返回时,还能续接。(具体可以在完整代码中体现)
std::string DeHeader(std::string &packagestream)
{// 还没有读到lenauto pos = packagestream.find(sep);if (pos == std::string::npos){return {}; }// 检查是否为一个完整的json串int len = std::stoi(packagestream.substr(0, pos));int total = pos + len + 2 * sep.size();if (total > packagestream.size()){return {};}// 至少有一个完整的json串std::string ret = packagestream.substr(pos + sep.size(), len);packagestream = packagestream.erase(0, total);return ret;
}

如此准备工作便都做好了。

可以进行传输与接收了。

while (true)
{int n = socket->Recv(&messagequeue);if (n <= 0){break;}// 2. 去报头std::string ret = DeHeader(messagequeue);if (ret.size() == 0){continue;}// 3. 一个完整的报文,进行反序列化// 只是利用工厂模式造了一个请求协议智能指针,无需重点关注,重点是进行序列化std::shared_ptr<Request> req = Bulider::GetReq();req->Deserialization(ret);// 4. 执行业务std::shared_ptr<Response> resp = _func(req);// 5. 序列化std::string jsonmessage;resp->Serialization(&jsonmessage);// 6. 添加报头jsonmessage = EnHeader(jsonmessage);// 7. 发送数据n = socket->Send(jsonmessage);if (n < 0){break;}
}

1.3 应用层

应用层就是处理我们的业务的,
我们上段代码的第四步就是应用层。

这里根据我们制作的网络计算机设计对应的业务即可。

class Calculator
{
public:Calculator(){}std::shared_ptr<Response> calculate(std::shared_ptr<Request> req){std::shared_ptr<Response> resp = std::make_shared<Response>();std::cout << req->_x << req->_oper << req->_y << std::endl;switch (req->_oper){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;resp->_desc = "div zero";}else{resp->_result = req->_x / req->_y;}}break;case '%':{if (req->_y == 0){resp->_code = 2;resp->_desc = "mod zero";}else{resp->_result = req->_x % req->_y;}}break;default:{resp->_code = 3;resp->_desc = "illegal operation";}break;}return resp;}~Calculator(){}
};

最后将3层整合在一起即可,这些便都是套路了,在完整代码中即可看到。

2. 客户端

本质上与服务端是很相似的,只是那几个步骤变了变顺序而已。

2.1表示层

while (true)
{// 构建数据std::shared_ptr<Request> req = Bulider::GetReq();req->_x = rand() % 10;req->_y = rand() % 10;req->_oper = oper[req->_y % oper.size()];// 序列化数据std::string reqstr;req->Serialization(&reqstr);// 添加报头reqstr = EnHeader(reqstr);// 发送数据int n = sockclient->Send(reqstr);// 接收数据std::string recvstr;while (true){n = sockclient->Recv(&recvmessage);if (n < 0){break;}// 去报头recvstr = DeHeader(recvmessage);if (recvstr.size() == 0){continue;}break;}// 反序列化std::shared_ptr<Response> resp = Bulider::GetResp();resp->Deserialization(recvstr);
}

3. 完整代码:

Gitee代码展示。

在这里插入图片描述

完~

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

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

相关文章

【秋招笔试】8.18大疆秋招(第一套)-后端岗

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

Springboot发邮件功能如何实现?详细步骤?

Springboot发邮件配置指南&#xff1f;如何集成Spring Mail发邮件&#xff1f; 无论是用户注册、密码重置还是重要通知的发送&#xff0c;邮件都是不可或缺的沟通方式。Springboot作为一个流行的Java开发框架&#xff0c;提供了简洁易用的方式来实现邮件功能。AokSend将详细探…

音频转换器有哪些?一键转换,轻松享受

暑假里&#xff0c;你是否也沉浸在激情四溢的演唱会中&#xff0c;用手机记录下了那些难忘的现场音频&#xff1f; 但回到家中&#xff0c;想要将这些珍贵的现场记忆从手机迁移到电脑上永久保存时&#xff0c;却遇到了格式不兼容的难题。 别担心&#xff0c;今天我们就要解决…

基于Python的机器学习系列(8):Newton Raphson逻辑回归

在本篇博文中&#xff0c;我们将探讨一种比传统梯度下降更高效的优化方法——Newton Raphson方法&#xff0c;并学习如何在逻辑回归中应用它。Newton Raphson方法通过利用二阶导数的曲率信息&#xff0c;快速地找到使代价函数最小化的参数。尽管这种方法在处理较小规模的数据集…

前端项目重新打包部署后如何通知用户更新

前端项目重新打包部署后如何通知用户更新 前端项目重新打包部署后如何通知用户更新常用的webSocket解决方案纯前端方案路由拦截多线程main.ts中 创建多线程多线程逻辑处理 前端项目重新打包部署后如何通知用户更新 前端项目重新打包部署后&#xff0c;由于用户没及时更新页面&…

什么是OpenTiny?

OpenTiny 是一套企业级的 Web 前端开发解决方案&#xff0c;提供跨端、跨框架的 UI 组件库和低代码引擎&#xff0c;帮助开发者高效构建 Web 应用 。企业运用开发中&#xff0c;可以利用 OpenTiny 的以下核心组件和优势&#xff1a; TinyVue 组件库&#xff1a;一个丰富的组件库…

python初级爬虫实战:我是怎么用python下载音乐的

今天分享的内容是如何使用python下载歌曲和歌词信息&#xff0c;文章涉及内容主要为了帮助大家学习python技能&#xff0c;请大家合规合理使用。 如果你正在学习Python爬虫&#xff0c;但是找不到方向的话可以试试我这一份学习方法和籽料呀&#xff01;点击 领取&#xff08;不…

汽车IVI中控OS Linux driver开发实操(二十四):I2C设备驱动的编写

概述: 在Linux驱动中I2C系统中主要包含以下几个成员: I2C adapter(即I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,最主要的工作是需要完成i2c_algorithm结构体。这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功…

0.91寸OLED迷你音频频谱

一、简介 音频频谱在最小0.91寸OLED 屏幕上显示&#xff0c;小巧玲珑 二、应用场景 本模块为音频频谱显示模块&#xff0c;用来获取声音频谱并展示频谱&#xff0c;跟随音乐声音律动 三、产品概述 基于主控芯片设计的将声音采集分析频谱&#xff0c;显示到0.91寸OLED的功能…

我们如何将数据输入到神经网络中?

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 下面我拿识别美女的例子来给大家介绍如何将美女的图片数据输入到神经网络中。 此例中&#xff0c;待输入的数据是一张图像。为了存储图像…

Java中String类的经典问题、错误认知以及归纳总结

在学习过程中对String类的理解反复刷新&#xff0c;以此文记之&#xff0c;做归纳总结&#xff0c;也适合新手避坑。 以实用性考虑&#xff0c;环境为Java 8 以及 之后版本。 String类相比其它类特殊的地方在于有一个字符串常量池(StringTable)&#xff0c;里面存着字面量的引…

Hackademic.RTB1靶场实战【超详细】

靶机下载链接&#xff1a;https://download.vulnhub.com/hackademic/Hackademic.RTB1.zip 一、主机探测和端口扫描 nmap 192.168.121.0/24 ip:192.168.121.196 端口:22、80 二、访问80端口 发现target可点击 点击后跳转&#xff0c;页面提示目标是读取到 key.txt 文件 fin…

Enhancing Octree-Based Context Models for Point Cloud Geometry Compression 论文笔记

1. 论文基本信息 发布于&#xff1a; IEEE SPL 2024 2. 创新点 分析了基于 one-hot 编码的交叉熵损失函数为什么不能准确衡量标签与预测概率分布之间的差异。介绍了 ACNP 模块&#xff0c;该模块通过预测占用的子节点数量来增强上下文模型的表现。实验证明了ACNP模块在基于八…

【Java】 力扣 最大子数组和

目录 题目链接题目描述思路代码 题目链接 53.最大子数组和 题目描述 思路 动态规划解析&#xff1a; 状态定义&#xff1a; 设动态规划列表 dp &#xff0c;dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。 为何定义最大和 dp[i] 中必须包含元素 nums[i] &#xff1a;…

前端css动画缩放transform: scale()

transform: scale(2) scale等比例放大 大于1是放大 小于1是缩小 负值是倒着放大 scaleX scaleY 可以单独设置只在x轴y轴放大 改变中心点放大的位置 左上 left top 左下 left bottom 左中 left center 右上 …

Python之列表的基本使用

列表 一、什么是列表二、创建 Python 列表三、二维列表四、索引和切片五、运算符六、列表的函数&#xff08;1&#xff09;len(列表名&#xff09;&#xff08;2&#xff09;min(列表名&#xff09;&#xff08;3&#xff09;max(列表名&#xff09;&#xff08;4&#xff09;s…

探索顶级PDF水印API:PDFBlocks(2024年更新)

引言 在一个敏感信息常常面临风险的时代&#xff0c;能够轻松高效地保护文档的能力至关重要。PDF水印已成为企业和个人寻求保护其知识产权、确保文件保密性的基本工具。 PDFBlocks 文字水印 API是什么&#xff1f; PDFBlocks API 提供了一个强大的解决方案&#xff0c;用于在…

如何使用博达网站群管理平台的树状导航

1 介绍 由于网站建设需要&#xff0c;需在首页的左边竖栏部分使用树状导航。我又过了一遍《网站群管理平台用户手册》&#xff0c;没发现如何在网站的首页设置树状导航组件。昨天&#xff0c;我之所以在创建树状导航上不知所措&#xff0c;是因为平台本身有一些误导&#xff0…

如何用Java SpringBoot+Vue搭建校内跑腿业务系统?实战教程解析

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

ASM实例的SPILE 存储在ASM的磁盘组上时,集群要如何去获取SPFILE并启动ASM实例?(1)

从11g R2 开始&#xff0c;ASM spfile 会自动存储在安装集群软件时创建的第一个磁盘组中&#xff0c;一般为OCR磁盘组。由于投票盘/OCR 存储在 ASM 上&#xff0c;因此需要在节点上启动 ASM。要启动 ASM&#xff0c;需要其 SPFILE 。但 SPFILE 仅位于 ASM 磁盘组上。集群是如何…