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

news2024/11/14 3:37:34

此前我们关于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.coloradmin.cn/o/2057154.html

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

相关文章

GlobalMapper-大疆的航点kmz转航线文件展示空间轨迹

0序&#xff1a; 在大疆遥控器或者司空2中设置航线&#xff0c;都是一个个的航点&#xff0c;如果把航点转为航线&#xff0c;在三维地球中显示其空间效果。用于分析和实际物体的距离&#xff0c;或者展示该航线都做了哪些方面的考虑。 如何把一堆点连城一条线&#xff1f; 本…

进程相关命令和函数

查询进程相关命令 ps aux 查看进程相关信息 1.就绪态、运行态 R 2.睡眠态、等待态 可唤醒等待态 S 不可唤醒等待态 D 3.停止态 T 4.僵尸态 Z 5.结束态 top 根据CPU占用率查看进程相关信息 kill 和killall kill和killall发送一个信号 kill -2 PID 15 发送信号PID对应的进程&…

又一实锤 美元丧钟敲响

文&#xff5c;琥珀食酒社 作者 | 积溪 咱们又要见证历史了 之前我说美元霸权快终结了 没想到马上又来了一个实锤 就在刚刚 “159个国家将采用金砖国家新支付系统“的消息 冲上热搜 据大毛媒体爆料 这个新支付系统 将在今年10月份上线 替代现有的swift系统 这是要挖…

nginx的详细介绍及配置

Nginx&#xff08;发音为“engine X”&#xff09;是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP代理服务器。Nginx以其稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。它最初由俄罗斯人Igor Sysoev编写&#xff0c;并于2004年首次公开发布。N…

解决旧版CMS内容管理无法登录的问题

最近遇到了输入正确的账户密码&#xff0c;旧版的CMS内容管理的平台提示登录成功却无法跳转的问题 遇到这种情况请不要慌&#xff01;&#xff01;&#xff01; 请按照下面的步骤解决问题&#xff1a; 1.点击账号管理 2.点击右上角的返回旧版控制台 3.点击cloud1环境 4.点击扩…

财务会计与管理会计(十一)

文章目录 快速切换日记账余额SUMPRODUCT、LOOKUP函数应用 销售业绩分段统计表SUMPRODUCT函数的应用 自动打印发票签收单VLOOKUP函数的应用 快速切换日记账余额 SUMPRODUCT、LOOKUP函数应用 C2SUMPRODUCT((A5:A100B2)*C5:C100) D2SUMPRODUCT((A5:A100B2)*D5:D100) E4公式1&…

javaweb学习之HTML(一)

推荐学习使用网站 w3school 在线教程 认识HTML HTML&#xff08;HyperText Markup Language&#xff09;是超文本标记语言&#xff0c;它是一个用于创建网页和网页应用程序的标准标记语言。HTML文档由一系列的元素&#xff08;elements&#xff09;组成&#xff0c;这些元素通…

大模型日报|10 篇必读的大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.斯坦福推出大模型网络安全能力和风险评估框架 Cybench 用于网络安全的语言模型智能体&#xff08;agent&#xff09;能够自主识别漏洞并执行漏洞利用&#xff0c;有可能对现实世界造成影响。政策制定者、模型提供者…

海外媒体投稿:怎样在法国媒体发稿宣传中获得成功

法国是一个充满机遇的销售市场&#xff0c;而媒体发稿营销推广是企业在法国市场里扩张曝光度和提升知名度的有效途径。下面我们就共享如何运用低投资得到高收益的办法&#xff0c;帮助企业在法国媒体发稿推广过程中获得成功。 第一步&#xff1a;掌握目标群体在进行法国媒体发稿…

HCIP-交换实验

根据实验要求&#xff0c;完成实验内容&#xff1a; 实验拓扑图如下所示 &#xff1a; 搭建拓补图&#xff1a; LSW1&#xff0c;LSW2&#xff1a; [LS1]interface Eth-Trunk 0 [LS1-Eth-Trunk0]q [LS1]interface g0/0/3 [LS1-GigabitEthernet0/0/3]eth-trunk 0 [LS1]interf…

微信支付商家转账到零钱申请必过方案总结

商家在申请商家转账到零钱时总会遇到各种原因的驳回&#xff0c;不少商家不断的修改又产生新的驳回原因从而导致工期无限延长&#xff0c;本文根据我们上万次成功申请商家转账到零钱的经验整理&#xff0c;帮助商家可以快速过审&#xff1a; 准备工作和注意事项 - 确认主体资格…

mq-fanout交换机

交换机 交换机是什么?步骤 交换机本身具备路由功能 消息先发到交换机,交换机在路由到队列,消费者监听队列拿到消息 广播模式是什么 是什么 例如:每个微服务创建队列,订单服务只启动1台,1个消费者,订单 怎么创建 创建一个队列 -交换机里type-选择模式(广播模式) 在交换…

AMD为何花49亿美元收购ZT Systems?

是的&#xff0c;是不是很震惊&#xff01; 苏妈再次出手&#xff0c;在美国当地时间8月19日&#xff0c;AMD同意用价值49亿美元的现金和股票收购ZT Systems——这笔费用超过了AMD 2024年预期在数据中心GPU销售总额。 从收购的金额我们就能看出这笔收购的重要性。 但为什么AM…

NGINX常用指令及其防盗链

目录 1 NGINX的指令及其应用 1.1 if指令 1.2 set 指令 - 实现变量定义 1.3 break 指令 1.4 return 指令 1.5 rewrite 指令 1.5.1 Nginx rewrite 介绍 1.5.2 Nginx rewrite 语法 1.5.3 rewrite 指令结尾的 flag 标记说明 2 域名永久与临时重定向 2.1 永久重定向301 2.2 临时重…

同行聚势,喆啡酒店11周年熠熠生辉

当下酒店与消费者之间已超越传统服务关系&#xff0c;在旅途中彼此相伴&#xff0c;相互支持&#xff0c;并肩前行&#xff0c;喆啡酒店作为生活方式酒店的标杆品牌&#xff0c;超百万忠诚消费者正是其品牌旅伴。十一年发展旅程&#xff0c;喆啡酒店携手万千消费者&#xff0c;…

Mantel Test分析与绘图

目录 1.前言 2.步骤 3.在R语言中&#xff0c;除了mantel_test函数&#xff0c;还有其他几个工具和方法可以用于进行Mantel Test分析&#xff1a; 4.利用ggcor包在进行Mantel Test分析 5.使用ggcor包进行Mantel Test分析 6.两个距离矩阵的行名和列名不完全相同的处理方法 …

一个BUG搞懂ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal

首发公众号&#xff1a;赵侠客 引言 最近我收到一个非常诡异的线上BUG&#xff0c;触发BUG的业务流程大概是这样的&#xff1a;A系统新建任务数据需要同步到B系统&#xff0c;数据是多租户的&#xff0c;比如C租户在A系统新建了一条任务&#xff0c;那么C租户登录B系统后会看到…

基于springboot和vue的酒店管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图详细视频演示技术栈系统测试为什么选择我官方认证闲鱼玩家&#xff0c;服务很多代码文档&#xff0c;百分百好评&#xff0c;战绩可查&#xff01;&#xff01;入职于互联网大厂&#xff0c;可以交流&#xff0c;共同进步。有保障的售后 代码参考数据…

《黑神话.悟空》:一场跨越神话与现实的深度探索

《黑神话.悟空》&#xff1a;一场跨越神话与现实的深度探索 在国产游戏日益崛起的今天&#xff0c;《黑神话.悟空》以其独特的剧情、丰富的人物设定和深刻的主题&#xff0c;成为了无数玩家翘首以盼的国产3A大作。这款游戏不仅是一次对传统故事的创新演绎&#xff0c;更是一场对…

oracle日常巡检命令

一、日常巡检命令 1、检查Oracle实例状态 SQL> set pages 600 lines 600 SQL> select instance_name,host_name,startup_time,status,database_status from v$instance; 说明&#xff1a;“STATUS”表示Oracle当前的实例状态&#xff0c;必须为“OPEN”&#xff1b;“…