目 录
🏉一. 应用层协议
我们自己写的应用程序就是在应用层
虽然应用层里面有一些现成的协议,但是在实际工作中也会存在 自定义应用层协议(发明协议?协议就是约定,约定好客户端和服务器按照啥样的格式来传输数据)
那么应用层协议如何约定呢?
例如前面我们写的回显服务器实现中,隐含了应用层协议的约定。约定了:每个请求,都是以 /n 结尾;每个响应,都是以 /n 结尾 。(scanner.next();)
自定义应用层协议,需要从两个角度入手:
- 考虑清楚交互过程要传递的信息有哪些
如我们点外卖:
- 启动程序:涉及到网络交互
- 请求:用户信息
- 响应:商家信息(商家名称,商家评分,商家位置,商家预览图…)
交互中涉及到哪些信息的传递,和需求又是密切相关
点击某个商家,涉及到网络交互
- 请求:用户信息,点击的商家信息
- 响应:该商家的详细情况(有哪些外卖可以点,每个菜多少钱,评价怎么样,销量如何,分类信息,预览图…)
- 考虑清楚这些信息组织格式
方式一:一种典型的约定方式,直接使用简单分隔符来对不同部分的信息做区分,考虑获取商家列表,约定的数据组织格式
- 请求:用户 ID;位置坐标(经度纬度)
- 响应:商家名称;商家评分;商家位置;商家预览图地址;
使用;以及 /n 都是可以替换成其他符号的
方式二:一种典型的数据约定格式,使用固定长度,来区分从哪里到哪里是一个信息
请求:(一个请求固定 12 个字节)
响应:
方式三:方式一和方式二混合搭配,有些字段使用固定长度,有些字段使用分隔符。
方式四:通过 XML 的格式来约定数据
请求:
响应:
XML 格式和 HTML 格式非常像,都是通过标签这样的结构来组织数据的。标签一般是成对出现的,表达了 “树形结构”(多级嵌套)。
方式五:json 相比于 xml 更常用的数据组织形式
请求:
响应:
使用 { } 来包含一些 “键值对”。键值对之间,使用逗号分割,每个键值对的键和值之间使用冒号分割
json 要求键的部分必须是字符串,而值的部分,可以是数字,可以是字符串,还可以是数组(使用 [ ] ),还可以是另外一个 json。
json 是当下用来自定义协议的时候,非常常用的格式!也有很多相关的第三方库,可以用来操作 json 数据。
方式六:还有一些其他的 “二进制” 组织格式,protobuffer,thift 等
- 像 xml 和 json 都是属于文本格式(优势是可读性高,劣势是效率低,占用的带宽更多)
- protobuffer,thift 都是二进制格式,格式上就要更加的复杂(优势是效率高,占用带宽低,劣势是可读性差)
应用层协议数据格式有很多种,在实际开发中要根据实际的情况来决定使用哪种更合适
拓展带宽的相关知识:
在国内,带宽是最贵的硬件成本,和 CPU ,内存,硬盘相比。升级服务器的带宽花的钱是比 CPU ,内存啥的升级要贵很多。
应用层协议不仅仅是可以自己写代码自定义协议,也有一些现成的协议,可以直接使用的,最著名的协议就是 HTTP 协议(后面的文章会更新介绍)
⚾️二. 传输层协议
传输层协议是操作系统内核中已经包含好的
其中包含了
- UDP:无连接,不可靠传输,面向数据报,全双工
- TCP:有连接,可靠传输,面向字节流,全双工
认识协议的细节,往往要认识协议的报文格式
👒1. UDP 协议
使用 IP 地址,来区分当前是哪个主机,一个主机上可能有多个应用程序,使用端口号来区分应用程序。
UDP 长度:表示了 UDP 数据报有多长。(注意:这里此处有个重大缺陷,此处的长度,单位是字节,2个字节,能表示的最大长度是 65535 => 64KB => 一个 UDP 数据报最长就是 64KB,由于当时创建的时候,64KB 算大的,但是目前而言,如果代码中使用 UDP ,需要保证一个数据报(一个 DatagramPacket)最大不能超过 64KB)
举例搜狗搜索,搜索的结果有广告和非广告两部分
在入口服务器和广告服务器之间本来是使用 UDP 进行交互,在业务发展的过程中,一次次传输的数据越来越接近 64KB 了,为了解决这个问题,提出了两种方案。
- 在应用层把广告数据拆分成多个部分,使用多个 UDP 数据报来传输。接收这一段,把拆成的多个包,进行组合。(方案可行,但是代码会很复杂,网络通信有个特点,后发先至)
如我们的客户端按顺序发送 1 2 3 ,通常情况下,收到的顺序和发送的顺序是一致的,但是网络出现一些抖动,很容易让接收顺序和发送顺序不一致,也有办法解决这个问题,但是要花费很多的代价 ,为了控制顺序,就得整队,整队就有额外的开销,就得消耗时间。
- 实际使用的方案,直接使用 TCP 替代 UDP,TCP 没有长度限制
会有人问为什么不把 UDP 协议升级更新一下,改为 4 个字节或者 8 个字节呢?
UDP 已经内置到各个操作系统内核中了,发布到全世界无数个主机中了,想要进行升级,就得要求全世界的主机的操作系统都统一升级到这个版本的 UDP 才行,万一有部分没升级,UDP 就无法工作了。(务必要考虑到升级带来的影响,很多的升级操作,都要考虑到 “兼容性”)
🌂2. 校验和
网络传输中,传输的数据,不一定是准确无误的!
本质上是光信号 / 电信号,使用不同频率的光信号,表示 1 - 0,使用不同电频的电信号,表示 1 - 0,传输过程中,信号是可能受到干扰的!(尤其是无线,更容易受到干扰),干扰就可能造成 “比特反转” ,接收方,收到了数据之后,就得验证下,看看当前的数据是否是正确的!(验货)
例如买瓷砖,我们会验证瓷砖数量,款式,破损情况。
校验和可以认为是针对数据特征进行的 “摘要”(相当于瓷砖数量和款式),接收方就可以根据校验和来进行检查了。
UDP 传输数据,网络上的数据是可能受到干扰
发送的时候,针对要传输的数据,计算一个 “校验和” (相当于配置单)
发送的时候,把数据和校验和一起发送过去
接收的时候,针对收到的数据重新计算校验和,对比一下自己计算的校验和和发来的校验和是否是一样的,如果是一样的,就说明数据没啥问题,不一样就有问题。
UDP 使用的校验和算法,是一种非常简单的 CRC 算法(循环冗余校验和)
把数据的每个字节,都往上加,如果超出了范围(2 字节),溢出的部分就不要了,所有的字节都累加完了,得到的结果就是校验和了
如果检验和相同,输入的字节一定完全相同吗?好像也不一定
10 = 9 + 1;10 = 8 + 2; 10 = 7 +3;…
确实可能存在这种情况,但是这是一种非常小的概率事件,如果校验和不同,可以认为数据一定是传输过程中错了,但是校验和不同,不能认为 100% 的确认数据就一定没错(有可能正好两个地方错一块去了,导致校验和没变),但是因为数据发生传输错误的概率不大,能够错到一块,并且校验和没发生改变,这种概率更是少之又少。(工程上就忽略不计)
校验和,不仅仅是在 UDP 这里,很多的别的地方也会涉及到,只要是数据传输可能出现问题,都可以使用校验和的思想。
注意:哪些应用层协议基于 UDP 只能靠硬背
基于UDP的应用层协议:
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
当然,也包括你自己写UDP程序时自定义的应用层协议
扩展问题
经典面试题:
- UDP本身是无连接,不可靠,面向数据报的协议,如果要基于传输层UDP协议,来实现一个可靠传输,应该如何设计?
- UDP大小是受限的,如果要基于传输层UDP协议,传输超过64K的数据,应该如何设计?
以上两个问题答案类似,都可以参考TCP的可靠性机制在应用层实现类似的逻辑:(后文详解TCP)
例如:
- 引入序列号,保证数据顺序;
- 引入确认应答,确保对端收到了数据;
- 引入超时重传,如果隔一段时间没有应答,就重发数据;
……
👓3. TCP 协议
由于 TCP 协议很重要切内容比较复杂过多,请看下篇文章详解 TCP 协议