前言
hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
前面的博客中已经解析了网络通信的基础知识、TCP/UDP服务器和客户端的代码实现,也就是初始网络编程和网络编程(一)(二)这三篇博客,初学的铁汁建议看完网络编程部分的三篇博客再来看网络原理部分,对知识点会更加清晰
从这篇博客就会开始解析网络原理,是理论上的知识,设计代码的部分比较少,在网络通信过程中进行调参、排查故障,网络原理是必备的基础知识,这部分知识也是面试高频考点,还是非常重要的🐵
网络原理部分主要解析TCP/IP五层体系中的应用层和传输层,网络层和数据链路层只会进行简单的介绍,对大部分Java程序员是不会涉及到这两层的(只有开发路由器/交换机会用到),物理层是硬件岗位,程序员就更涉及不到了🎆个人主页:洋不写bug的博客
🎆所属专栏:JavaEE学习
🎆铁汁们对于JavaEE的各种常用核心语法,都可以在上面的前端专栏学习,专栏正在持续更新中🏀,有问题可以写在评论区或者私信我哦~
1,应用层协议
应用层对于程序员来说,是最重要的一层,因为下面的4层都是系统已经实现好的,而应用层则是用户自己写的,在应用层中,程序员通常需要定义好数据的传输格式,调用传输层api(socket api),进行网络通信
程序员定义数据格式,就相当于是在自定义应用层协议,在实际开发中也称为“约定前后端交换接口”,大体上就可以把前端理解成客户端,后端理解成服务器
自定义协议的过程,主要是约定两件事:
- 通信的信息
- 通信的数据的格式
1,通信信息
首先是通信的信息,就是约定客户端给服务器要发送什么信息,服务器收到信息,做出响应后,返回什么信息
就比如外卖软件,打开软件时,会根据个人喜好来推荐一些附近的餐厅,这就是客户端访问服务器后,服务器返回的结果
如下图,客户端访问服务器时要发送用户信息和当前的位置信息,然后服务器会返回很多的信息
当点击某个商家,进入到菜品列表中时,显示菜品信息,也是访问服务器得到的
如下图,客户端发送用户信息和商家的id,服务器返回各个菜品的数据
这些需求就是产品经理提出来的,因为大部分用户是没法准确的表达自己的需求的,规模稍微大点的公司都有产品经理
2,通信数据格式
数据通信格式主要有以下四种:
- 行文本方式
- XML
- JSON
- google protobuffer
在网络编程(一)博客中,提到了这样一个例子,在聊天软件上给好友发送信息,发送方的id为1234,接收方的id为5678,发送时间为2026-9- 21 8:41:00,发送内容是"hello"
如下图,程序员就可以在应用层规定用这种方式来传输数据,这个叫做行文本方式
行文本方式的可读性非常差,对于一些复杂的数据也难以表达,因此现在使用就比较少了
XML格式的可读性是非常好的,就是通过成对的标签,来对数据内容进行解释说明,跟html看起来比较类似,只不过标签是我们自己定义的,如下图
XML的缺点就是引入了很多标签,这些标签需要占用网络带宽(网络链路在单位时间内最多能传输的数据量),网络带宽资源还是比较贵的,因此现在XML用的也不多,在本地配置文件中会用到
JSON算是当前最流行的一种方案,在实际开发中会经常用到,如下图
XML每个部分中需要有开始标签和结束标签,而JSON中只写一次即可,相比XML就更节省网络带宽
google protobuffer就是二进制压缩方案,也就是把要传输的数据按照一定的规则,编码成二进制比特位,再进行传输,可读性非常差,但是消耗的网络带宽是最少的
google protobuffer适合在特别缺网络带宽的时候使用,不缺的话一般都是使用JSON
2,UDP报文格式
传输层主要有TCP协议和UDP协议,虽然程序员不需要在传输层实现代码,但需要调用传输层提供的api,因此就要学习传输层的报文格式,这里先来解析UDP报文格式
传输层收到应用层的数据包后,会在前面加上个UDP报头,构造出传输层数据报(如下图),UDP报头中不只存储了源/目的端口号,还存储了其他的数据
UDP报头部分有8个字节,里面分成了四个部分,每部分存储的空间为2个字节,分别负责存储源端口号、目的端口号、UDP长度、校验和,如下图:
因为端口号是16位无符号整数(十进制就是0 - 65535),这里源端口和目的端口部分的大小就都是两个字节
第三个部分表示UDP 报头 + UDP 载荷的总长度,单位是字节
因为是用两个字节来表示的,UDP长度最大也就是65535,也就是说UDP数据报的最大长度大概就是64KB,数据报再大就没办法记录长度了
一个UDP数据报最大长度大概是64KB,现在用手机拍张照片,大小就十几MB了,很多数据UDP数据报是装不下的
在15年前,计算机的内存还是比较小的,主流就是2GB - 4GB
就拿浏览器搜索来说,会显示很多搜索结果,每个搜索结果基本上都是用文字描述的,比较简单,用UDP数据报传输是绰绰有余的
后来,每个搜索结果中显示的东西就多了,如下图,搜索腾讯视频,会发现一个搜索结果中包含很多东西(客户端下载、电影、综艺、历史、以及一些剧的图片和链接等等),一个UDP数据报大小是64KB,是装不下的
有的铁汁可能会想:为什么那些大佬不把UDP协议改一下,例如存储UDP长度的部分从2个字节改成4个字节,让UDP数据报能发送更多的数据?
这个在具体实施上是非常难的,升级之后两台设备想要通信,就都需要升级,但是升级后,一定会出现一部分设备是旧版本,一部分设备是新版本的情况,这两种设备之间就无法用UDP进行通信,后果还是很严重的
而且协议具体的实现,是操作系统的厂家完成的(Windows系统的厂家是微软,macOS的厂家是苹果),这些厂家也不会随便升级协议,因为可能会引入新的bug
在绝大多数需要传输大型文件的场景,都使用TCP协议
接着是校验和,校验和就是验证UDP数据报是否在传输过程中出错了
数据报的传输,本质上是在物理层通过电信号/光信号/电磁波传输的,日常通信数据传输会受到磁场的影响,太空中,卫星的数据传输会受到高能粒子流的影响,这些影响就会造成比特翻转,也就是0变成1,1变成0
通过校验和,数据在传输过程中出错就能发现,那就可以让这次数据传输失败,重新发送数据,校验和的原理是这样的:
- 发送方在构造完成UDP数据报后,大概就是对 UDP 报中的所有数据做反码求和,最后对总和按位取反(了解下即可),就得到了校验和check1,就把check1填充到UDP报头的校验和字段那里
- 接收方在接收到数据报后,按照相同的算法,重新算一遍校验和,得到check2,再从UDP报头部分取出check1,对比check1和check2是否相等,不相等就说明数据传输过程中出错了
校验和的计算准确率并不是100%,一些特定的情况下,恰好两个bit的翻转在计算时抵消了,那就判断不出来数据出错了,但是这种情况出现的概率相对来说比较小,可以看作是校验和判断出现的误差
对于准确性要求非常高的场景,那就不只是用校验和判断数据传输是否出错,会加入一些其他的校验算法来判断,例如计组中的”海明码“,可以识别出是哪一位出现了比特翻转,并进行自动纠错,当然,这样的方案就比较复杂,代价也更大了
UDP主要会用于分布式系统中服务器之间的通信,原因有两个:
- 这些服务器在同一个机房中,网络环境简单,出现丢包的概率比较小
- UDP的传输效率相对较高
在教材中,UDP数据报的格式如下,这样适合教材排版
3,TCP报文格式
UDP是面向报文的,传输单元叫数据报,TCP是面向字节流的,传输单元叫报文段,报文段相比数据报就要复杂了,TCP报文段如下图
TCP报文段首部最大为60个字节,包含的数据还是比较多的,源端口号、目的端口号、校验和(跟UDP校验和算法原理一样),这些就不再赘述了
选项部分是用来扩展TCP的基础功能的,可以选择是否扩展,以及扩展多少,选项部分的大小就是 0 - 40个字节,不扩展选项部分就是0个字节,选项也算是数据报首部的一部分,因此TCP首部的大小就是20 - 60字节
有的铁汁可能会想:首部长度部分只有4位,也就是能表示0 - 15,而TCP首部最小就20个字节,如何表示呢?
这里相当于做了个压缩优化,这里的值还要乘以4个字节,这里的值为10,那首部长度就是40个字节,这里的值为15,那首部长度就是60个字节
TCP头部还有6位的保留位,前面提到,想升级UDP协议是特别麻烦的,TCP这6位的保留位就是先预留下来,给未来协议升级扩展用
TCP是可靠传输,那这个可靠传输是什么意思呢?
传输的可靠性不是保证数据能100%的到达对方那里,这个是做不到的,因为这个数据传输不只是软件层面,还受各种硬件因素的影响,就比如15年支付宝有几个小时用不了,就是因为在杭州,挖机工作时把光纤挖断了😅
可靠性主要解决四个问题:
- 丢包
- 重复收到
- 乱序
- 比特差错(比特翻转)
这个比特差错问题就是靠16位校验和来解决的,前三个问题又如何解决呢?
首先是丢包的问题,丢包是没办法避免的,可以约定接收方接收到数据后给发送方回复个收到:
- 发送方收到回复,就说明这个数据发送成功了
- 发送方没有收到回复,就说明这个数据丢包了,就采取补救措施(例如重发)
但是这样约定就又引入了一个问题,如果接收方发送的收到在传输过程中丢包了,发送方没有收到回复,就认为前面数据传输丢包了,再去发送一遍,这样接收方对于收到前面已经收到过的数据,这就是重复收到问题,后果还是比较严重的,例如重复扣款、重复发货
乱序问题,在数据传输中,发送方发送多个数据报,不一定先发送到的数据报就先到达,因为两个数据报传输的路径可能不一样,可能有的路径会比较堵,就会出现“先发后到”的情况
这个“先发后到”就类似于结婚的车队,因为路途中有很多红绿灯,道路情况比较复杂,有的车可能出发时走到了车队前面,途中就会到后面
乱序问题有个比较搞笑的例子,小帅喜欢小美,给小美发微信,说周末能不能约个饭,小美说可以呀,小帅又问小美:能不能做我女朋友,小美说滚,通信流程如下图:
如果小美发送的数据出现了乱序,如下图,发送的“可以”在传输过程中堵了一会,发送的“滚”先到了,那在小帅的视角来看,就是问小美周末能不能约饭,小美说滚,接下来向小美表白,小美同意了,小帅还以为小美刚开始是跟自己开玩笑的😂
丢包、乱序、重复收到这三个问题,可以通过32位序号和32位确认序号来解决,TCP是传输字节流,序号和确认序号就是针对字节进行编号
因为编号是连续递增的,所以只需要存储TCP 报文段数据部分第一个字节的编号即可,后面每个字节的编号就知道了,如下图
接收器会返回个应答报文段(Acknowledgement,又称ACK),应答报文段中的确认序号就是收到数据的最后一个字节的序号 + 1,如下图,收到数据的最后一个字节的序号为1,那就返回1001
确认序号有以下两层含义:
- 所以小于确认序号的数据,接收方已经收到了
- 发送方接下来从确认序号的位置,继续发送数据
应答报文段的数据载荷部分基本上都为空,只有 TCP 头部,确认序号只在应答报文中生效,那如何确定当前接收端返回的报文段是不是应答报文呢?
这个就用到了6位标志位的ACK位(如下图),ACK位的值为0或者1,ACK为1,就说明这个报文段是应答报文,ACK位的值为0,就说明报文段不是应答报文
通过这种接收方给发送方返回应答报文段的方式,丢包、重复收到、乱序问题就都能够解决:
- 对于丢包问题,如果一段时间后发送方没有收到接收方的应答报文段,就重新传送数据
这个过程称为“超时重传”,不同系统上,超时时间是不同的,超时重传是对抗丢包的核心机制 - 对于重复收到问题,接收方在接收到数据的时候,会在操作系统内核中维护一个“接收缓冲区”,如果又收到了同一个数据,就可以根据数据的序号在接收缓冲区中进行去重操作,确保应用程序从接收缓冲区中读到的数据不会重复
- 对于乱序问题,接收方会在数据缓冲区中对收到的数据进行排序,来解决这个问题
超时重传的时间不是固定的,是动态变化的,超时重传后还是没有收到ack,那等待的时间就会变长
如下图,第一次发送数据时,如果等待t1时间没有收到接收端的应答报文,就进行数据重传,重传后的等待时间为t2,t2 > t1
那为什么要这样设定呢?
假设网络传输数据的丢包率为10%,这时候就已经属于是严重卡顿了,在这种情况下,数据第一次传输丢包,第二次传输仍然丢包的概率是很小的,仅为1%(不考虑ack丢包,这里只是大概计算下)
如果重传后仍然没有收到ack,那就说明网络的丢包率可能已经远远大于10%了,如果再去频繁重传,会加重网络的故障程度,这里等待时间增加可以理解为已经摆烂了,如果能收到ack更好,但是已经不指望能传输成功了
如果重传次数/重传等待时间,达到一定的上限,重传还没有成功,tcp连接就会被重置,也就是单方面的释放连接,也就是删除保存的对方的信息
在断开连接前,还会向对方发送一个复位报文,意味着这个连接不要了,如果能传到对方,对方也释放连接,这个也属于是单方面的通知,对方有没有收到复位报文无所谓
标志位中的RST(Reset)就是区分当前报文是不是复位报文,如果值为1就是复位报文,否则就不是
结语💕💕
网上一些资料会说“三次握手”(下篇博客会提到)保证了TCP传输的可靠性,这种说法是不太科学的,三次握手是在建立连接时涉及的环节,连接建立好就不涉及握手了,可以看作是可靠传输的一个前提条件
确认应答、超时重传才是TCP实现可靠传输的重要机制之一这部分知识就是比较繁杂,通信时的一些设定或者遇到的问题跟日常生活中的一些事情是比较类似的,结合起来就会更好的理解
TCP报文中还有一些数据项没有解析,基本上每个都是涉及到一个大的知识点,会在后面博客中慢慢解析
以上就是今天的所有内容啦~完结撒花~🥳🎉🎉