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

2024-08-21 03:36

本文主要是介绍【计算机网络】网络版本计算器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

此前我们关于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.chinasem.cn/article/1092006

相关文章

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~

poj 3068 有流量限制的最小费用网络流

题意: m条有向边连接了n个仓库,每条边都有一定费用。 将两种危险品从0运到n-1,除了起点和终点外,危险品不能放在一起,也不能走相同的路径。 求最小的费用是多少。 解析: 抽象出一个源点s一个汇点t,源点与0相连,费用为0,容量为2。 汇点与n - 1相连,费用为0,容量为2。 每条边之间也相连,费用为每条边的费用,容量为1。 建图完毕之后,求一条流量为2的最小费用流就行了

poj 2112 网络流+二分

题意: k台挤奶机,c头牛,每台挤奶机可以挤m头牛。 现在给出每只牛到挤奶机的距离矩阵,求最小化牛的最大路程。 解析: 最大值最小化,最小值最大化,用二分来做。 先求出两点之间的最短距离。 然后二分匹配牛到挤奶机的最大路程,匹配中的判断是在这个最大路程下,是否牛的数量达到c只。 如何求牛的数量呢,用网络流来做。 从源点到牛引一条容量为1的边,然后挤奶机到汇点引一条容量为m的边

配置InfiniBand (IB) 和 RDMA over Converged Ethernet (RoCE) 网络

配置InfiniBand (IB) 和 RDMA over Converged Ethernet (RoCE) 网络 服务器端配置 在服务器端,你需要确保安装了必要的驱动程序和软件包,并且正确配置了网络接口。 安装 OFED 首先,安装 Open Fabrics Enterprise Distribution (OFED),它包含了 InfiniBand 所需的驱动程序和库。 sudo

【机器学习】高斯网络的基本概念和应用领域

引言 高斯网络(Gaussian Network)通常指的是一个概率图模型,其中所有的随机变量(或节点)都遵循高斯分布 文章目录 引言一、高斯网络(Gaussian Network)1.1 高斯过程(Gaussian Process)1.2 高斯混合模型(Gaussian Mixture Model)1.3 应用1.4 总结 二、高斯网络的应用2.1 机器学习2.2 统计学2.3

网络学习-eNSP配置NAT

NAT实现内网和外网互通 #给路由器接口设置IP地址模拟实验环境<Huawei>system-viewEnter system view, return user view with Ctrl+Z.[Huawei]undo info-center enableInfo: Information center is disabled.[Huawei]interface gigabit

PostgreSQL中的多版本并发控制(MVCC)深入解析

引言 PostgreSQL作为一款强大的开源关系数据库管理系统,以其高性能、高可靠性和丰富的功能特性而广受欢迎。在并发控制方面,PostgreSQL采用了多版本并发控制(MVCC)机制,该机制为数据库提供了高效的数据访问和更新能力,同时保证了数据的一致性和隔离性。本文将深入解析PostgreSQL中的MVCC功能,探讨其工作原理、使用场景,并通过具体SQL示例来展示其在实际应用中的表现。 一、