【计算机网络】网络版本计算器
此前我们关于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代码展示。

完~
相关文章:
【计算机网络】网络版本计算器
此前我们关于TCP协议一直写的都是直接recv或者read,有了字节流的概念后,我们知道这样直接读可能会出错,所以我们如何进行分割完整报文?这就需要报头来解决了! 但当前我们先不谈这个话题,先从头开始。 将会…...
使用 Python 爬虫进行网站流量分析:Referer 头的利用
在互联网时代,网站流量分析是了解用户行为、优化网站结构和提升用户体验的重要手段。本文将介绍如何使用 Python 爬虫技术结合 HTTP Referer 头进行网站流量分析,以及如何实现这一过程。 什么是 HTTP Referer 头? HTTP Referer 头是一个请求…...
梧桐数据库(WuTongDB):数据库技术中LL算法详解
LL 算法是一种自顶向下的语法分析算法,广泛用于构建解析器。LL 分析器逐个读取输入符号,从左到右分析(Left-to-Right),并使用最左推导(Leftmost Derivation)来生成语法树。因此,LL 分…...
【秋招笔试】8.18大疆秋招(第一套)-后端岗
🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…...
CSS 的text-size-adjust属性
text-size-adjust 属性在CSS中用于控制用户是否可以调整网页中文字的字体大小。这个属性主要针对移动设备上的浏览器,尤其是那些允许用户通过捏合(pinch)手势来缩放整个页面的浏览器。 语法 text-size-adjust: none; text-size-adjust: aut…...
阿里MAXCOMPUTE数据专辑信息读取并同步数据表
阿里MAXCOMPUTE数据专辑信息读取并同步数据表 在阿里云大数据体系中,我们可以使用数据地图的数据专辑,对数据的类别等进行一个管理 那么管理后的数据,我们想要落表进行相关的数据分析,如何做呢? 查看阿里云官方文档…...
rufus制作ubantu的U盘安装介质时,rufus界面上的分区类型选什么?
rufus制作ubantu的U盘安装介质时,rufus软件界面上的分区类型选什么(如下图)? 在使用Rufus制作Ubuntu的U盘安装介质时,分区类型的选择取决于我们的计算机的引导方式。 以下是具体的选择建议: 1、查看计算机的引导方式…...
【系统架构设计师-2018年】案例分析-答案及详解
试题一(25分) 阅读以下关于软件系统设计的叙述,在答题纸上回答问题1至问题3。 【说明】 某文化产业集团委托软件公司开发一套文化用品商城系统,业务涉及文化用品销售、定制、竞拍和点评等板块,以提升商城的信息化建设…...
linux驱动入门实验班——平台总线设备驱动模型和设备树
目录 前言 一、重要结构体 二、编程思路 1.platform_driver结构体 2.probe 三、使用设备树 1.步进电机 2.红外遥控 四、代码示例 前言 在这里主要记录学习韦东山老师Linux驱动人入门实验班的笔记,韦东山老师的驱动课程讲的非常好,想要学习驱动…...
零基础学习Python(六)
1. 元类的应用 使用元类给对象添加一个固有属性author: 对类名进行限定,要求类名必须是大写字母开头: class MetaC(type):def __init__(cls, name, bases, attrs):if not name.istitle():raise TypeError("类名必须是大写字母开头~")return …...
微信小程序--31(todolist案例)
一.功能 输入待办事件添加代办事件删除代办事件 二、步骤 1.添加输入框 .wxml代码: <!-- 1.输入框 --><input type"text" bindinput"handleInput" value"{{text}}" /> .wxss代码: /* 1.输入框样式 */ i…...
springboot项目使用本地依赖项,打包后出现NoClassDefFoundError的一种解决方法
可以把本地依赖项上传到本地仓库后再引用 建立 Maven 本地仓库并将依赖上传到本地仓库 要建立 Maven 本地仓库并将依赖上传到本地仓库,可以按照以下步骤进行操作: 1. 配置 Maven 本地仓库路径 Maven 默认会在用户的主目录下的 .m2/repository 目录创…...
Maven高级使用指南
在开发大型项目时,Maven作为一个强大的构建和项目管理工具,能显著提升项目管理和构建的效率。然而,随着项目的扩大,维护和管理的复杂性也随之增加。本文将探讨一些高级的Maven用法和解决方案,以帮助你更好地管理大型项…...
windows docker 执行apt-get 权限问题
今天在windows下安装的docker 部署的容器执行apt-get遇到权限问题 PS C:\Users\xiaok> docker exec -it jenkins sh $ apt-get update Reading package lists... Done E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied) E: Unable to l…...
Linux系统信息排查
目录 介绍步骤 介绍 1、熟悉查看CPU信息、操作系统信息、用户信息、特殊权限账户、启动项和任务计划的排查命令 2、在进行受害主机排查时,首先要对主机系统进行基本排查,方便对受害主机有一个初步的了解。 3、利用lscpu和uname -a查看系统硬件软件基本…...
《图解设计模式》笔记(四)分开考虑
九、Bridge模式:将类的功能层次结构与实现层次结构分离 类的两个层次结构和作用 类的功能层次结构:希望增加新功能时 父类有基本功能,在子类中增加新功能 Something父类 …├─SomethingGood子类 想要再增加新功能 Something父类 …├─So…...
Linux shell编程学习笔记74:sed命令——沧海横流任我行(中)
0 前言 自 60 年代末以来,sed 一直是 Unix 标准工具箱的一部分。 Sed在以下三种情况下特别有用: 编辑太大的文件,无法进行舒适的交互式编辑; 当编辑命令序列过于复杂而无法在交互模式下轻松键入时,可以编辑任何大小的…...
[数据集][目标检测]道路积水检测数据集VOC+YOLO格式2699张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):2699 标注数量(xml文件个数):2699 标注数量(txt文件个数):2699 标注…...
不同路径
不同路径 思路: 法一:动态规划 const int N 110; class Solution { int dp[N][N];//dp[i][j]:从起点走到 i j的路径个数。 public:int uniquePaths(int m, int n) {for(int i1;i<n;i){dp[1][i]1;} for(int i1;i<m;i) dp[i][1]1;f…...
【HTML】HTML学习之引入CSS样式表
1、CSS样式规则 选择器{属性1:属性值1; 属性2:属性值2; 属性3:属性值3;}2、HTML引入CSS样式表 2.1、行内式 行内式也称为内联样式,是通过标签的style属性来设置元素的样式,其基本语法格式如下: <标签名 style"属性1:属性值1; 属性2:属性值2;…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...
若依登录用户名和密码加密
/*** 获取公钥:前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...
