计算机网络——应用层协议(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.rhkb.cn/news/317315.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue 组件单元测试深度探索:细致解析与实战范例大全

Vue.js作为一款广受欢迎的前端框架,以其声明式的数据绑定、组件化开发和灵活的生态系统赢得了广大开发者的心。然而,随着项目规模的增长,确保组件的稳定性和可靠性变得愈发关键。单元测试作为软件质量的守护神,为Vue组件的开发过程…

FPGA高端项目:FPGA帧差算法多目标图像识别+目标跟踪,提供11套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐FPGA帧差算法单个目标图像识别目标跟踪 3、详细设计方案设计原理框图运动目标检测原理OV5640摄像头配置与采集OV7725摄像头配置与采集RGB视频流转AXI4-StreamVDMA图像缓存多目标帧差算法图像识别目标跟踪模块视频输出Xilinx系列FPGA工程源…

PDF高效编辑器,支持修改PDF文档并转换格式从PDF文件转换成图片文件,轻松管理你的文档世界!

PDF文件已成为我们工作、学习和生活中不可或缺的一部分。然而,传统的PDF阅读器往往只能满足简单的查看需求,对于需要频繁编辑、修改或转换格式的用户来说,就显得力不从心。现在,我们为您带来一款全新的PDF高效编辑器,让…

绿色便携方式安装apache+mysql+tomcat+php集成环境并提供控制面板

绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境 目录 绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境[TOC](目录) 前言一、XAMPP二、安装和使用1.安装2.使用 三、可能的错误1、检查端口占用2、修改端口3、JDK原因导致 前言 安装集成环境往往配置复…

paddlehub的简单应用

1、下载安装 pip install paddlehub -i https://pypi.tuna.tsinghua.edu.cn/simple 报错&#xff1a; Collecting onnx<1.9.0 (from paddle2onnx>0.5.1->paddlehub)Using cached https://pypi.tuna.tsinghua.edu.cn/packages/73/e9/5b953497c0e36df589fc60cc6c6b35…

[1688]jsp工资投放管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 工资投放管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0…

TDengine写入2000万数据查询体验

最近在寻找时序数据库&#xff0c;想应用在公司的项目上。 上一篇文章实验了InfluxDB:windows上使用influx2.7学习,还学习了flux语言&#xff0c;最后发现宽表查询比较困难&#xff0c;就放弃了&#xff0c;于是决定试试国产时序数据库TDengine 参考 官方文档&#xff1a;htt…

Vitis HLS 学习笔记--IDE软件高效操作指引

目录 1. 简介 2. 实用软件操作 2.1 C/RTL Cosimulation 选项 2.2 Do not show this dialog again 2.3 New Solution 2.4 对比 Solution 2.5 以命令行方式运行&#xff08;windows&#xff09; 2.6 文本缩放快捷键 2.7 查看和修改快捷键 2.8 将Vitis HLS RTL 导入 Viv…

Postgresql源码(127)投影ExecProject的表达式执行分析

无论是投影还是别的计算&#xff0c;表达式执行的入口和计算逻辑都是统一的&#xff0c;这里已投影为分析表达式执行的流程。 1 投影函数 用例 create table t1(i int primary key, j int, k int); insert into t1 select i, i % 10, i % 100 from generate_series(1,1000000…

前端性能优化知识梳理

1.重要性 当我们面试的时候&#xff0c;前端性能优化方面算是必考的知识点&#xff0c;但是工作中我们又很少会重点的对项目进行前端优化&#xff0c;它真的不重要吗&#xff1f; 如果我们可以将后端响应时间缩短一半&#xff0c;整体响应时间只能减少5%~10%。而如果关注前端…

【C语言】——数据在内存中的存储

【C语言】——数据在内存中的存储 一、整数在内存中的存储1.1、整数的存储方式1.2、大小端字节序&#xff08;1&#xff09;大小端字节序的定义&#xff08;2&#xff09;判断大小端 1.3、整型练习 二、浮点数在内存中的存储2.1、引言2.2、浮点数的存储规则2.3、浮点数的存储过…

mac用Homebrew安装MySQL并配置远程登录

1. 简介 MySQL 是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;后被 Oracle 公司收购。MySQL 使用 SQL&#xff08;Structured Query Language&#xff09;作为查询语言&#xff0c;并提供了强大的功能和性能…

python安卓自动化pyaibote实践------学习通自动刷课

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文是一个完成一个自动播放课程&#xff0c;避免人为频繁点击脚本的构思与源码。 加油&#xff01;为实现全部电脑自动化办公而奋斗&#xff01; 为实现摆烂躺平的人生而奋斗&#xff01;&#xff01;&#xff…

Linux——socket套接字与udp通信

目录 一、理解IP地址与端口 二、socket套接字 三、TCP与UDP的关系 四、网络字节序 五、socket编程 1.socket()创建套接字 2.填充sockaddr_in 结构体 3.bind() 绑定信息 4.recvfrom()接收消息 5.sendto()发送消息 六、UdpServer代码 一、理解IP地址与端口 IP地址是In…

【C++】详解string类

目录 简介 框架 构造 全缺省构造函数 ​编辑 传对象构造函数 拷贝构造 析构函数 容量 size() capacity&#xff08;&#xff09; empty() clear() reserve() ​编辑 resize() 遍历 检引用符号"[ ]"的重载 迭代器 begin() end() rbegin() rend(…

【触摸案例-控件不能响应的情况 Objective-C语言】

一、接下来,我们来说这个“控件不能响应的情况”, 1.素材里边,有一个“不接受用户交互的情况”,这么一个代码,把它打开, 把这个项目啊,复制过来,改一个名字,叫做“04-控件不能响应的情况”, 打开之后,command + R,运行一下, 在storyboard上,你也可以看得出来,我…

智慧农业设备——虫情监测系统

随着科技的不断进步和农业生产的日益现代化&#xff0c;智慧农业成为了新时代农业发展的重要方向。其中&#xff0c;虫情监测系统作为智慧农业的重要组成部分&#xff0c;正逐渐受到广大农户和农业专家的关注。 虫情监测系统是一种基于现代传感技术、图像识别技术和大数据分析技…

链表-----返回倒数第K个节点回文结构的判断相交链表

目录 1.返回倒数第K个节点 2.回文结构的判断 3.相交链表的判断&#xff0c;返回交点 1.返回倒数第K个节点 &#xff08;1&#xff09;返回链表的第k个节点&#xff0c;我们这里的做法是定义两个指针&#xff0c;这两个指针之间相差的是k这个长度&#xff1b;这个过程的实现就…

Android手势识别面试问题及回答

问题 1: 如何在Android中实现基本的手势识别&#xff1f; 答案: 在Android中&#xff0c;可以通过使用GestureDetector类来实现基本的手势识别。首先需要创建一个GestureDetector的实例&#xff0c;并实现GestureDetector.OnGestureListener接口来响应各种手势事件&#xff0c…

创建SpringBoot和RabbitMQ的整合项目

文章目录 创建SpringBoot和RabbitMQ的整合项目首先快速创建一个maven项目引入SpringBoot整合rabbitMQ的依赖在src/main目录下创建resources目录并引入配置文件写消息发送者MessageSender写消息接收者MessageReceiver写RabbitMQConfig配置类写SpringBoot启动主类CommandLineRunn…