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

此前我们关于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 oper
    std::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)
{
    // 还没有读到len
    auto 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.mfbz.cn/a/871588.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kubectl命令、初识pod、namespace

文章目录 一、Kubectl简介基础命令1.基本信息命令2.创建和更新资源命令3.删除资源命令4. 查看日志和调试命令5. 端口转发和复制文件命令6. 部署管理命令7. 伸缩命令8. 配置和上下文管理命令9.常用命令 二、Pod简介核心概念pod常见状态调度和初始化阶段容器创建和运行阶段异常状…

Zookeeper服务注册及心跳机制详解

ZooKeeper提供了一种类似于文件目录的结构来保存key值&#xff0c;其提供了四种key类型&#xff0c;分别是持久节点&#xff0c;临时节点&#xff0c;持久有序节点&#xff0c;临时有序节点。其中临时节点的特性是当创建此节点的会话断开时&#xff0c;节点也会被删除。这一特性…

Apache Commons-IO 库

Apache Commons-IO是Apache开源基金组织提供的一组有关IO&#xff08;Input/Output&#xff09;操作的小框架。这个库的主要目的是为了提高IO流的开发效率&#xff0c;减少在进行文件读写、目录遍历等操作时编写的样板代码量。通过使用Commons-IO库&#xff0c;开发者可以更加简…

WT32-ETH01开发板模块,启明云端物联网方案,乐鑫ESP32多样化开发应用

在物联网(IoT)的浪潮中&#xff0c;无线Wi-Fi模块作为连接传统硬件与现代智能网络的桥梁&#xff0c;正逐渐成为智能家居和设备通信不可或缺的一部分。Wi-Fi模块也被称为串口Wi-Fi模块&#xff0c;是一种嵌入式模块&#xff0c;它能够将串口或TTL电平信号转换为符合Wi-Fi无线网…

关于AR在医疗领域创新应用

AR技术在医疗领域创新应用&#xff0c;旨在展示AR技术如何为医疗行业带来革命性的变化&#xff0c;我们可以从以下几个方面入手&#xff1a; 一、引言 随着科技的飞速发展&#xff0c;增强现实&#xff08;AR&#xff09;技术正逐步渗透到医疗领域的各个环节&#xff0c;为患…

蓝桥杯2021第十二届蓝桥杯青少年组省赛试题真题

带我去看题解&#xff01;&#xff01;&#xff01; 带我去看题单&#xff01;&#xff01;&#xff01; 目录 第一题&#xff1a;[2021第十二届蓝桥杯青少年组省赛] 字符串 题目背景 题目描述 输入格式 输出格式 输入输出样例 第二题&#xff1a;[2021第十二届蓝桥杯…

【Docker】安装Docker环境遇到的坑(VirtualBox)

利用vagrant工具在VirtualBox安装CentOS7环境后&#xff0c;安装Docker环境遇到的坑 前期准备工作 1、卸载原有环境 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engi…

Spire.PDF for .NET【文档操作】演示:创建 PDF 组合

PDF 作品集是一组文件&#xff0c;其中可以包含文本文档、电子表格、电子邮件、图像、PowerPoint 演示文稿和绘图。尽管 PDF 作品集将不同类型的文件组合成一个单元&#xff0c;但其中的每个文件都保留了其原始格式、分辨率和大小。在本文中&#xff0c;您将学习如何使用Spire.…

C#实现数据采集系统-多设备采集

系统功能升级-多设备采集 数据采集系统在网络环境下&#xff0c;性能足够&#xff0c;可以实现1对多采集&#xff0c;需要支持多个设备进行同时采集功能&#xff0c;现在就开发多设备采集功能 修改多设备配置 设备配置 将DeviceLink 改成List集合的DeviceLinks删掉Points&a…

datawind可视化查询-其他函数

飞书文档学习链接:https://www.volcengine.com/docs/4726/47275 1. 用户名函数 用户名函数并非 ClickHouse 官方函数,而是与项目用户信息相结合,用于返回当前使用用户的指定信息的函数。 USERNAME()可返回当前用户的用户名,如下所示。该函数也可与其他函数组合使用 2. J…

Android 应用集成百度地图定位SDK

在当今的移动互联网时代&#xff0c;位置服务已经成为了众多应用不可或缺的功能之一。无论是社交应用中的位置分享&#xff0c;还是服务类应用中的位置导航&#xff0c;都离不开精准的定位技术。为了满足这一需求&#xff0c;越来越多的开发者选择集成第三方的定位SDK。其中&am…

【ARM Hypervisor And SMMU 系列 5 -- SMMU 和 IOMMU技术】

文章目录 SMMU 和 IOMMU技术ARM 的 SMMUTranslation process overviewTBU 和 TCU 的关系TBUTCUTLBSMMU 和 IOMMU技术 文章 讲到了为支持I/O透传机制中的DMA设备传输而引入的IOMMU/SMMU技术,同时留了一个问题:IOMMU/SMMU是否可以同时支持GVA->GPA和GPA->HPA的转换? 答案…

三种相机模型总结(针孔、鱼眼、全景)

相机标定 文章目录 相机标定前言 前言 我们最常见的投影模型Perspective Projection Model描述的就是针孔相机的成像原理。从上面的图根据相似三角形可以得出 参考链接 https://zhuanlan.zhihu.com/p/540969207 相机标定之张正友标定法数学原理详解&#xff08;含python源码&a…

SpingBoot集成kafka发送读取消息

SpingBoot集成kafka开发 kafka的几个常见概念 1、springboot和kafka对应版本&#xff08;重要&#xff09;2、创建springboot项目&#xff0c;引入kafka依赖2.1、生产者EventProducer2.2、消费者EventConsumer2.3、启动生产者的方法SpringBoot01KafkaBaseApplication2.4、appli…

蓝队技能-应急响应篇挖矿病毒系统样本家族威胁情报异常定性排查分析处置封锁

知识点 1、应急响应-挖矿病毒-定性&排查 2、应急响应-挖矿病毒-应急&处置演示案例-蓝队技能-挖矿病毒-样本&定性&排查&应急&处置 挖矿病毒 随着虚拟货币的疯狂炒作&#xff0c;挖矿病毒已经成为不法分子利用最为频繁的攻击方式之一。 可以利用个人电…

[论文笔记]Improving Retrieval Augmented Language Model with Self-Reasoning

引言 今天带来一篇百度提出的关于提升RAG准确率的论文笔记&#xff0c;Improving Retrieval Augmented Language Model with Self-Reasoning。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 检索增强语言模型(Retrie…

你应该停止使用的 7 个已弃用的 Python 库

欢迎来到雲闪世界。升级您的 Python 工具包&#xff1a;发现 7 个应停止使用的过时库以及替代它们的功能。最近&#xff0c;我回顾了 Python 的新特性&#xff0c;发现每个版本都引入了创新&#xff0c;使我们的日常开发工作变得更加轻松。 这让我意识到科技是一门永无止境的艺…

有了这4款工具,你就知道电脑怎么录屏了!

电脑屏幕录屏这个问题很多人都会碰到&#xff0c;比如教学视频录制&#xff0c;游戏技巧分享&#xff0c;软件操作演示等等。因为场景众多&#xff0c;电脑自带的录屏功能不一定能满足&#xff0c;所以借助第三方工具是一个很有效的办法。如果大家不知道如何录屏&#xff0c;可…

I2C学习:上拉电阻选取

一&#xff0e;I2C简介 I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。I2C总线在使用时&#xff0c;需要接上拉电阻&#xff0c;这是因为I2C接口是开漏输出&#xff0c;如图1所示。 图1 I2C开漏输出 I2C有5种速度模式&#xff1a;标准&#xff08;100KHz&am…

在亚马逊云科技上安全、合规地创建AI大模型训练基础设施并开发AI应用服务

项目简介&#xff1a; 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践&#xff0c;并应用到自己的日常工作里。 本次介绍的是如何在亚马逊云科技利用Servi…