计算机网络——应用层协议(1)

在这篇文章初识网络中,我介绍了关于计算机网络的相关知识,以及在这两篇文章中Socket编程和Socket编程——tcp,介绍了使用套接字在两种协议下的网络间通信方式。本篇文章中我将会进一步介绍网络中网络协议的部分,而这将会从应用层开始。

1. 应用层协议以及序列化的引入

我们知道两台机器能够在网络上进行通信,主要的原因就是两台主机都遵守网络协议,而我们也知道网络协议本质上就是一种约定,体现到计算机中那就是,网络协议就是一个结构化的字段。
就比如我们现在在一个聊天软件上发消息,那么这个消息会携带三个内容:发出消息的用户昵称、发出消息的时间以及消息内容,那我们要将这个数据在网络上从一台主机发送到另一台主机这三个部分是一个一个的发吗?现在看似好像合理,确实可以一个一个的往过发,但是假如发送的消息很多呢?接收方如何知道哪个昵称对应哪个消息内容以及时间,所以在发送这三个内容时,必定是以整体发送的,在计算机中我们可以将这三个内容放在一个结构化字段中:

struct Message
{
	char nickname[128];
	char msg[1024];
	char time[128];
};

然后我们把这个结构体声明在服务端和客户端中,这样当一个服务端进行转发消息时,服务端和客户端都能知道接收到的消息该如何使用。这就是处在应用层的网络协议。
但是这样的作法还是具有局限性,对于上面的这个结构体,它不具有跨平台性,比如Linux和Windows下这个结构体可能大小不一样,或者直接就是服务端所使用的语言和客户端使用的语言不一样,这导致客户端就根本不认识这个结构体,那么当客户端接收到这么一个报文时,在应用层根本无法使用这个报文,所以我们建议在将这种结构化字段在网络上发送时以字符串(字节流)的方式发送数据比如这个结构体我们可以这样"nickname-msg-time"中间以横杠分割,字节流在任何平台上都是可以被正确识别的,现在我们只要规定这个字节流如何读取(也就是制定好协议),那么服务端和客户端就算是不同平台也能正常进行网络通信了。
上面这种将结构化字段转化为字节流的过程叫做序列化,将字节流再转变为结构化字段时,叫做反序列化。
将网络上传输的数据在用户层中变成结构化字段的原因是为了便于用户处理这些信息。
而真正在网络上传输需要将结构化字段进行序列化是为了便于网络传输,以及支持跨平台。
需要注意的是,在网络协议的其他层中操作系统发送网络数据仍然是固定大小的结构体,而它不需要进行序列化然后再进行网络传输的原因是那几层协议很长时间才会迭代一次,而应用层协议的迭代是很快的(比如我现在就要往Message结构体中添加头像字段),所以我们需要通过使用序列化结构化字段来让网络传输变得更方便。
现在我将会使用tcp协议的套接字实现一个简易版的计算器,来更好的认识应用层协议,以及会引入更加规范的应用层协议的制定方法。

2. 套接字接口的封装

在此之前,我先要对套接字接口进行封装以为了更方便的使用它们(对tcp协议相关接口)。
我们知道在tcp协议下使用套接字接口会使用到这些接口:
在这里插入图片描述
我们也知道在服务端进行bind时时需要固定IP的,只需要一个端口号,客户端不需要显式bind,客户端会在connect时进行bind随机端口。
所以我们可以对这些接口进行封装:
在这里插入图片描述
在这里我们定义了一个Socket类,是一个抽象类,而我们将tcp所使用的接口设置为纯虚函数,让子类来进行实现,以黄框中的函数作为骨架,让整体的执行逻辑不变,而只在底部对纯虚函数进行重写。这样的设计模式就是模板方法类:它在父类中定义了一个算法的框架,允许子类在不改变算法结构的情况下重新定义算法的某些特定步骤。
所以我们只需要继承这个抽象类然后对其中的纯虚函数进行重写来实现我们的tcp套接字就可以了:
在这里插入图片描述
至此我们的tcp套接字接口就编写好了,接下来我们就可以开始构建服务端的服务了。

3. 服务端

作为使用tcp协议的服务端,我们应该有一个端口号和一个监听的socket文件:
在这里插入图片描述
现在就是我们的服务的主要逻辑了:
在这里插入图片描述
我们的新连接进入新线程之后,我们的连接中一定要有要执行的业务,那么这个业务我们想从TcpServer的外部也就是回调的方法,实现建立连接和提供给客户端服务进行解耦。而且提供服务我们的可执行业务一定要具备读写网络数据的能力,也就是得需要新连接的文件描述符,这里我们对文件描述符进行了封装所以我们这里需要将newsock传过去:
在这里插入图片描述
接下来我们就需要来编写服务端的主函数了:
在这里插入图片描述

4. 业务编写

接下来就是我们的业务编写了,而我想写一个简单的计算器的功能,客户端通过传输服务端两个操作数和一个运算符,之后服务端进行运算以及给客户端发送运算的结果。
为了两端能够正常通信,我们就需要指定协议了,而前面也说过制定协议,其实就是对通信过程中的信息做结构化处理,让两端都能认识对方发过来的是什么,那么现在开始我们就来编写一个简单的具有请求和相应的计算器功能:
在这里插入图片描述
客户端发送请求,然后服务端接收到请求,对这个请求进行处理,最后响应回客户端:
在这里插入图片描述

5. 客户端

在开始进行通信之前,我们先进行客户端的简单的编写:
在这里插入图片描述

c. 正式开始编写业务逻辑

1). 直接传结构化字段

我们在上面已经制定了一个不太完善的协议,我们先使用直接传结构体的方式,进行网络上的请求和响应 :

在这里插入图片描述
现在我们回到ServerHandler业务逻辑的编写:
在这里插入图片描述
这样服务端和客户端就可以通信了。
在这里插入图片描述
但是直接传结构化字段正如开始所说,他是有缺陷的,用结构化字段来直接作为报文的话,服务器和客户端的应用场景就很局限,只要客户端进行了跨平台(例如使用其他语言)的话,这个客户端就不能使用了,就算能发给服务器信息,服务器也不认识他发过来的是什么,因为客户端方是根本不认识C++中的结构体的,所以我们就需要将我们要传输的结构化字段序列化。再规范点的话就将他变成一个报文。

2). 较为规范的应用层协议

上面我们简单的写了一个客户端和服务端进行网络通信以计算器为基本业务的代码,其中通信方式是双方共同认识计算器结构体,以此作为应用层协议。但是这样的传输方式是有问题的,首先就是使用场景太局限,除此之外我们还有其他的问题,在此之前我们需要再次认识tcp中面对字节流,以及tcp协议中的一些更为细节的知识:

tcp协议中的细节知识

我们知道当服务端和客户端使用tcp协议建立连接之后,双方都会有一个用来通信的socket文件描述符,其实我们的tcp协议的通信方式所对应的通信的文件描述符底层是这样的:
在这里插入图片描述

我们的socket文件描述符在操作系统中有着两个文件缓冲区,一个用来接收网络消息,一个用来发出网络消息,所以我们的服务端在应用层所使用的write和send是发送给了客户端吗?其实并不是,write和send是以拷贝的形式拷贝给了发送缓冲区,然后再由操作系统发送给客户端的接收缓冲区,在说得清楚点,就是tcp协议决定着发送缓冲区中的数据发送给客户端,这其中的决定包括什么时候发?发多少?发送过程出错了怎么办?这一切都由tcp协议来做决定。所以我们使用tcp协议进行通信的时候,本质上其实是操作系统间进行通信。
我们也知道tcp协议是面向字节流的一种通信方式,这就意味着我们发送缓冲区的内容有多少发送到了客户端的接收缓冲区中,我们是不确定的(这一般是由客户端接收缓冲区的容量有关),就比如我们现在在应用层使用send发送了一个"hello world",我们将这段内容实际上是拷贝到了本主机的发送缓冲区中,然后可能此时客户端的接收缓冲区中只能容纳五个字节了,这个时候我们的tcp协议在将自己的发送缓冲区中的内容发给客户端的时候就只能发过去个hello:
在这里插入图片描述
这个时候我们的客户端将这个不完整的数据读上去,那么就会导致数据发生错误,这假如要是我们的上面的计算器结构体的话,这就会直接读取错误。虽然在大部分情况下不会出现这种情况,但是这种情况我们能确定一定不会发生(主要是当前网络压力太小)。
又或者我们的服务端发送了两次hello world,而客户端在服务端第一次发送的时候第一时间没读,而是后来一起读的,这也会导致我们读取的数据错误。
我们发现在使用tcp协议通信时,通信的消息没有明显的 “边界感” 这就是面向字节流的特性。
而udp协议就不需要担心这样的问题,因为udp协议是面向数据报的,它发送数据时,要么不发送要么就全部发送过去,并且在对方未进行读取前,发送端是不能再次进行发送的,发送端会被阻塞。数据和数据之间具有明显的 “边界感” ,这就是面向数据报。
所以为了解决跨平台、数据间没有边界感等问题,我们就需要制定比完善的用户层协议。
所以接下来我们就来写一个比较规范的用户层协议。

序列化和反序列化

首先我们的两端传输的数据不再是结构化字段,而是具有一定格式的字节流也就是字符串,我们叫做序列化和反序列化,所以我们需要在计算器中的添加序列化和反序列化的功能:
我们要清楚我们要序列化的内容是有效载荷,要反序列化的内容也是有效载荷:
并且这里我们约定,请求序列化格式是:

	// 中间用空格隔开
	// _data_x _oper _data_y

在这里插入图片描述

在这里插入图片描述

响应的序列化格式是

	// 中间也是空格隔开
	// _result _code

在这里插入图片描述
而我们请求反序列化代码如下:
在这里插入图片描述
响应反序列化:
在这里插入图片描述
现在我们就可以直接使用一下这个序列化和反序列化的代码了,在这个时候我写了一个关于计算器中的对象的工厂模式:
在这里插入图片描述
计算器请求中的Result函数也得修改:
在这里插入图片描述

现在我们来使用一下这个序列和反序列化:
客户端
在这里插入图片描述
服务端:
在这里插入图片描述
在这里插入图片描述
然后我们再将客户端的操作数等,使用随机数的方式来初始化操作数以及描述符:
在这里插入图片描述
再来看效果:
在这里插入图片描述
但是,还有问题,我们现在只解决了结构化字段序列化可以跨平台的功能,但是我们仍无法保证我们两端收到的信息是独立的且完整的,也就是网络通信的数据还是没有边界感,而现在我们传的数据实际上还只能算作有效载荷,所以我们要将数据发送时,变成一个较为规范的报文。

报文

我们规定报文的格式是这样的:

	// "len\n有效载荷\n"

也就是在有效载荷的前面添加一个字段len,这个字段用来表示有效载荷的长度,中间用特殊字符\n来隔开,因为我们能确保在读取这个报文的时候在len字段中一定是没有\n的存在的,它只有数字。其中len字段就是报文的自描述字段,这样规定的好处是,它不仅可以用来给计算器业务进行封装报头,它也可以给任意的有效载荷进行封装报头。
而有效载荷的后面的\n不属于有效载荷也不属于封装的报头,只是为了Debug方便。
那么现在我们就来编写对有效载荷进行封装以及对报文解包的代码:
在这里插入图片描述
在将这个模块 添加到我们的服务端和客户端时,我们需要对服务端的代码进行改造:
在这里插入图片描述
我们这段代码中它有两个功能,一个是收发网络信息的功能,一个是处理网络数据的功能,我们只想让它具有网络数据处理的功能,而将手收发网络数据的功能让外面的线程来做,所以:
在这里插入图片描述
在这里插入图片描述

TcpSocket中封装并重写recv和send:
在这里插入图片描述
ServerHandler函数:
在这里插入图片描述
在这里插入图片描述
线程函数优化:
在这里插入图片描述

客户端代码:
在这里插入图片描述
运行结果:
在这里插入图片描述
可以看到,我们现在的代码仍然能够正确运行,并且网络间传输的数据格式较为规范。这就是一个比较规范的应用层协议。

6. 引入较为成熟的序列化方案

在上面的代码中,我们通过自定义应用层协议使得网络通信变得较为规范,其实现在已经有一批比较成熟的序列化方案,比如json、protobuf、xml等,而其中在客户端使用较多的就是json,protobuf较多的使用在服务端。而我们上面计算器的序列与反序列化我们可以使用json来编写:
计算器请求序列化与反序列化:
在这里插入图片描述
响应序列化和反序列化:
在这里插入图片描述
代码运行结果:
在这里插入图片描述
json可以通过键:值字符串的方式来存储你想要存储的结构化信息,最后用花括号把所有内容包含起来,键值对和键值对之间用逗号隔开,json也提供了类型的转换。

7. 网络协议

我曾在在这篇文章初识网络中介绍过,网络协议在实际编码过程中是四层协议,加硬件是五层协议,但是osi标准定制的网络协议是七层,而osi网络协议制定的非常完善:
在这里插入图片描述
在实际的编码过程中网络协议被分为五层的原因就是,上面红框中的三个部分被统一成了应用层。
在osi标准中:
会话层。负责维护两个结点之间的传输连接,确保点到点传输不中断,以及管理数据交换
这不就是我们使用tcp协议建立新连接之后得到的文件描述符吗?我们的连接的建立和数据的发送和接受都由我们编写。
表示层。处理在两个通信系统中交换信息的表示方法,主要包括数据格式变换、数据加密与解密、数据压缩与恢复等功能。
这就是相当于我们的报头的封装以及结构化字段的序列化以及反序列化,也需要我们来实现。
应用层。直接向用户提供服务,完成用户希望在网络上完成的各种工作,如文件服务器、数据库服务、电子邮件等。
这不用多说,这本来就是我们应该实现的东西。
所以osi标准定制的是很完善的,只不过这三层都是由我们程序员来编写,所以实际开发过程中这三层就被归为了一层。
这就是关于网络协议中自定义应用层协议的全部内容。

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

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

相关文章

基于SpringBoot+Vue乡村养老服务管理系统

项目介绍: 使用旧方法对乡村养老服务管理系统登录的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在乡村养老服务管理系统登录的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误…

[笔试训练](十)

目录 028:最长回文子串 029:买卖股票的最好时机(一) 030:过河卒 028:最长回文子串 最长回文子串_牛客题霸_牛客网 (nowcoder.com) 题目: 题解: 1.中心扩展算法: 每…

Docker镜像和容器操作

目录 一.Docker镜像创建与操作 1. 搜索镜像 2. 获取镜像 3. 镜像加速下载 4. 查看镜像信息 5. 查看下载的镜像文件信息 ​编辑6. 查看下载到本地的所有镜像 7. 根据镜像的唯一标识ID号,获取镜像详细信息 8. 为本地的镜像添加新的标签 9. 删除镜像 10. 存入…

Linux 第十一章

🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C,linux 🔥座右铭:“不要等到什么都没有了…

BUUCTF-WEB2

[SUCTF 2019]EasySQL1 1.启动靶机 2.寻找注入点和注入方法 随便输入一个字母,没有回显 随便输入一个数字,发现有回显,并且回显结果一样 3.堆叠注入 1; show databases; #查看数据库 1; show tables; #查看数据表 里面有个flag 1;set …

Three.js和Cesium.js中坐标

在了解Three.js和Cesium.js前先了解并弄清楚图形学关于空间的基本概念流程: 计算机图形学 图形学中涉及到多个坐标空间,这些空间之间的变换是图形渲染中的核心部分。下面是一些常见的图形学空间及其变换顺序: 对象空间(Object Sp…

【完整指南】如何在Visual Studio Code中轻松运行Llama 3模型?

Meta 发布了最新的开源语言模型Llama 3。因为它是开源的,你可以下载这个模型,并在自己的电脑上运行。 我清楚,你可能会想,在个人笔记本上运行一个拥有80亿参数的AI模型似乎只有技术高手才能做到。但别担心!这篇文章会…

MATLAB 运算符

MATLAB 运算符 运算符是一个符号,告诉编译器执行特定的数学或逻辑操作。MATLAB设计为主要在整个矩阵和数组上运行。因此,MATLAB中的运算符既可以处理标量数据,也可以处理非标量数据。MATLAB允许以下类型的基本运算- 算术运算符 关系运算符…

前端复习资料

前端复习资料 落叶的位置,谱出一首诗,时间在消逝,我们的故事。 这篇文章呢,整理写给需要的前端同学的。 核心知识,必须掌握的,也是最基础的,譬如浏览器模型,渲染原理,JS…

网页模版如何用

现在的网页模版已经得到了许多人的喜爱和使用。随着人们对互联网的需求不断增加,更多的公司和组织需要拥有自己的网站,以推广他们的品牌和服务。而网页模版为他们提供了一个简单而高效的方法来创建自己的网站。 网页模版是预先设计好的网站模板&#xff…

【数据分析】NumPy

文章目录 [toc]ndarray的创建np.array()方法np.arange()方法np.zeros()方法np.ones()方法np.full()方法np.eye()方法np.random模块np.random.random()方法np.random.randint()方法np.random.choice()方法np.random.shuffle()方法 ndarray的属性ndarray.dtypendarray.ndimndarra…

初识BootStrap

目录 前言: 1.Bootstrap的特点包括: 1.1响应式设计: 1.2组件丰富: 1.3易于定制: 1.4兼容性良好: 1.5强大的社区支持: 1.6一致的样式和布局: 1.7 插件和扩展性 2.初识Ajax: 2.1同步请求…

Linux——(关于权限常见的3个问题)

文章目录 1.修改文件或者目录的拥有者和所属组1.1chown指令1.2chgrp指令 2.常见的权限三个问题2.1对应一个目录,如果要进入,需要什么权限?2.2为什么我们创建的文件默认权限不是7772.2.1关于Linux下的权限掩码 2.3文件能否被删除取决于什么2.3…

Paddle OCR v4 微调训练文字识别SVTRNet模型实践

文字识别步骤参考:https://github.com/PaddlePaddle/PaddleOCR/blob/main/doc/doc_ch/recognition.md 微调步骤参考:https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.7.1/doc/doc_ch/finetune.md 训练必要性 原始模型标点符号和括号容易识别不到 数据…

【kettle004】kettle访问本地MySQL数据库并处理数据至execl文件

一直以来想写下基于kettle的系列文章,作为较火的数据ETL工具,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 熟悉、梳理、总结下MySQL关系数据库相关知识体系 3.欢迎批评指正,跪谢一键三连&…

大模型微调:技术迭代与实践指南

在人工智能领域,大模型(LLM)的微调是一个关键过程,它使模型能够适应特定的任务和数据集。微调是深度学习中用于改进预训练模型性能的重要技术。通过在特定任务的数据集上继续训练,模型的权重被更新以更好地适应该任务。…

揭秘工业大模型:从人工智能小白到技术先锋

工业大模型的五个基本问题 信息化时代,数字化转型成为企业提升营运效率、应对经营风险和提升核心竞争力的重要途径。在此过程中,数据作为一种客观存在的资源,所产生的价值日益凸显。党的十九届四中全会从国家治理体系和治理能力现代化的高度将…

详解Qt绘图机制

Qt框架以其强大的图形界面功能著称,其中绘图机制是构建丰富视觉效果的关键。本文将详细介绍Qt中的绘图机制,包括绘图基础、绘图设备、绘图工具及高级特性,并通过实战C代码示例,带你领略Qt绘图的魅力。 绘图基础 Qt的绘图操作主要…

vs2019 - release版中_DEBUG宏生效的问题

文章目录 vs2019 - release版中_DEBUG宏生效的问题概述笔记总结END vs2019 - release版中_DEBUG宏生效的问题 概述 在加固程序,需要去掉PE的字符串表中和逻辑相关的字符串。 编译成release版后,用IDA看,还是发现有debug版才有的字符串。 那…

gitee关联picgo设置自己的typora_图床

一:去gitee官网创建仓库:typora_图床 1.百度搜索关键字:gitee,进入官网 2.进入gitee登录或者注册自己的账号 3.进入主页后,点击右上方 4.点击新建仓库 5.设置仓库名:typora_图床 6.点击5的创建&#xff0…