网络原理(一)

众所周知,我们TCP协议一共有五层,接下来就让我们来介绍这些层

  1.应用层

   1)  这一层,有很多现成的协议,但有些时候,也需要程序员自己定义协议,我们需要根据需求来考虑并做出如下设计

     a)明确当前请求和响应中包含哪些信息

     b)明确具体的请求和响应的格式

             所谓格式就是看你按照啥方式,构造出一个字符串,后续这个字符串就可以作为tcp或者udp的payload进行传输,另一方面服务器就可以对这个字符串进行解析,所以 网络上传输的数据本质上就是字符串

  我们用java写代码时,都是各种对象,但是在最后在发送数据时,需要把对象转成(二进制)字      符 串【序列化】,在收到数据时,也需要把(二进制)字符串转换回对象【反序列化

   这里我们来举一个例子:

bea257cd8116484eba19738c5bb4da41.png

      在上述例子看来,请求和响应,具体的组织请求,是非常灵活的,程序员想怎么组织都行,只需要保证客服端和服务器使用相同的规则即可

  c)为了避免过于离谱的协议格式   ,有些大佬们搞出来了通用的协议格式供大家参考

   2)关于自定义的协议格式

      a)xml:

           1) 是以成对的标签,来表示“键值对”信息,同时标签支持嵌套,就可以构成一些更为复杂的协议格式(html可以视为xml的特化版本)

           2)请求:

72e05331fa2e4963b7a223425b17a98e.png

  

            3)响应

072db3e3a8ed476abf02eb87b4d71593.png

      4)xml的优缺点

              优点:xml非常清晰的把结构化数据表示出来了,

               缺点:表示数据需要引入大量的标签,看起来繁琐,同时也会占用不少的网络带宽

b)json(最流行的一种数据组织形式)

      1)本质上也是键值对,但是对比xml要干净不少

       2)请求

{
  
  userid:1234,
  position:"100 80"
}

      3)响应)

     json中,使用{}来表示键值对,使用[]来表示数组,数组里的每个元素,可以是数字,可以是字符串,还可以是其他的{ }或者[ ]

141026a2d9124d8cba35c6f3b24eeac1.png

       4)这是我们当前,最主流使用的一种网络数据的格式,在未来的实际开发中会经常遇到用json的数据,json对换行并不敏感,假如把这些内容都放在同一行,也是完全合法的

5)json的优势劣势

      优势:相比于xml,表示的数据简洁很多,可读性非常好,方便程序员观察中间结果,方便调试问题

      劣势:终究是要花费一定的带宽来传输key的名字

 c)protobuffer

      1) 这是谷歌提供的一套二进制的数据序列化方式,使用二进制的方式,约定某几个字节,表示哪种属性 这样可以最大化的节省空间(不必传输key,根据位置和长度,区分每个属性)

       2)优点和缺点:

             优点:节省带宽,最大化效率

             缺点:二进制数据,无法肉眼直接观察,不方便调试,使用起来比较复杂,需要专门编写一个proto文件,描述数据的格式是哪样的  ,牺牲了开发效率,换来运行效率

        3)这个主要用于,对于性能要求更高场景, 

     d)除了上面三种之外,业界还存在很多其他的序列化方式,java标准库,就提供了这种方式,还有其他的第三方库

2.传输层

  1. UDP协议

       1.udp的特点:无连接,不可靠传输,全双工,面向数据报

  2.UDP数据报的组成是由UDP报头和UDP载荷组成

bed31b2c4fe5478490d188fb705bce5e.png

     我们需要深入了解报头和载荷里面分别都是什么东西

03ee1b5798e94040b9fff143c14eda0a.png

  a)这里来温习一下字节

  1字节 有符号:-128=>127  无符号:0=>255 

  2字节  有符号  -32768 => 32767    无符号: 0=>65535

  4字节   有符号  -21亿=>=21亿     无符号:0=>42亿8千万

   b)udp报文长度 最长就是64kb,一个udp数据报最长就是这么长

   c)网络传输数据过程当中,容易受到外界的干扰,就可能会导致你原本要传输的

      发生比特翻转,因此接收方在收到数据之后,就需要确认一下,这个数据是否是一个正确的数据 检验和就是一个简单有效的方式。因此使用了简单粗暴的CRC校验算法(循环冗余校验和)

  把UDP数据报中的每个字节,都依次进行累加,把累加结果,保存到两个字节的变量中去,所有字节都加了一遍,最终得到了校验和,传输数据时,就会把原始数据和校验和一起传递过去,接收方收到数据,同时也收到了发送端送过来的校验和(旧的校验和)接收方按照同样的方式再算一遍

 假如旧的校验和和新的校验和相同,则视为数据传输过程中是正确的,如果不同,则视为传输过程中数据出错了(出错了就丢弃,TCP出错了会要求重发)

   2.TCP(重点)(核心特性)

TCP 的特点:有连接,可靠传输,面向字节流,全双工

9d518b1f27c14951bf9fddf3da64268c.jpeg

这里的tcp协议其实是从左到右很长的一条,因为排版才变成2多行

  a)第一行的端口号:端口号其实是重要的部分,知道了端口号,才能进一步确定数据报应该交给哪个应用

  b)四位首部长度:TCP的报头是变长了的,4bit的范围是0-15,但这里的单位是4字节,应在这个基础上乘4,因此,TCP的最长报头长度是60,TCP报头的前20个字节是固定的(TCP报头最短是20字节),因此,我们要使用首部长度来确认,报头到哪里就结束了,载荷数据从哪里开始

  c)选项部分:可以有也可以没有,可以有一个选项,也可以有多个选项

  d)保留(六位):reserved :这里是预留的位置,可以用,也可以不用

  e)可靠传输的机制:确认应答 ,这是保证可靠性的最核心的机制。但是当我们连续发多条数据时,可能会出现“后发先至”的情况

     1.后发先至的解决办法:针对数据进行编号,TCP是字节流,因此针对字节进行编号

       f)32位序号部分:因此我们只需要知道这一串字节的开始编号,以及数据的长度,每个字节的编号自然也就知道了,只需要在TCP报头中,把这一串字节第一个字节的编号表示出来,再结合报文长度,此时每个字节的编号就清楚了

80a405efc3fc40a1bda7a3ad25d75291.png

g)数据部分:这里存储的是TCP的载荷数据,包含若干个字节

h) 32位确认序号:这个字段是给应答报文使用的,确认信号的数值,就是收到最后一个字节的编号再加1

i)ca117e970afe45479a149d5e804d830e.png

这部分就可以区分普通报文和应答报文

ACK为0表示的就是普通的报文,此时只有32位序号,是有效的

ACK为1表示的是一个应答报文,这个报文的序号和确认序都是有效的

j)关于丢包:

丢包在网络上,很可能出现,因为在网络上传输数据要经历一系列的路由器和交换机,因此网络的结构复杂,传输的数据量也不确定,网络复杂越高,越繁忙,就越容易丢包,因此这里就需要超时重传

ff2f95f752654b0f89509789445d6271.png5f670f04a9b142538f7f8d86f38192c8.png

进行重传之后,接收方在收到数据之后,要对数据进行去重,把重复的数据丢弃掉,保证应用程序调用inputStream.read的时候,读到的数据不会重复,因此我们直接使用TCP的序号作为判定依据

TCP会在内核中,给每个socket对象都安排一个内存空间,相当于一个序列,也称为“接受缓冲区”

收到的数据,都会被放在接受缓冲区当中,并且按照序号排列好顺序,此时就很容易的找到该接受的数据是否重复了

 ca117e970afe45479a149d5e804d830e.png

当RST这里为1时 表示是一个复位报文

因此TCP核心的一句话就是:确认应答,是TCP保证可靠性最核心的机制,超时重传,是TCP可靠性的有效补充

  2.连接管理

  1.建立连接(三次握手) 

        握手:意思是发一个打招呼的数据,使用打招呼来触发“特定场景”,假如A和B要完成建立连接的过程,就需要“三次”这样打招呼的方式

d0ece31c53984b77927f44cc90dea17d.png

中间b发的两条可以合并为一次,变成如下那样

46846b79c97e447bb1ee1ce108b0bfa7.png

合并之后,节省了封装和分用的过程,降低了成本,节省了效率

这里是一张完整的图

40b2f45ec97d48e4aacb34c268c64e38.png

ACK是应答报文,SYN是申请建立连接报文

b077dbfa360a4e84889fa9a02eb9d72e.png

三次握手还能起到,“消息协商”这样的效果,通信的时候涉及到一些参数,需要双方保持一致,通过协商,来确定参数具体是多少,TCP通信过程中,有很多信息是需要进行协商的,比如双方的序号从几开始,这样做主要是保证,两次连接,消息的序号能够有较大的差异从而好去判断出某个消息是否是属于这个连接的

 比如,极端情况下,有一个消息迟到了很久,当消息达到的时候,服务器和客服端已经断开了上一个连接,这时候就可以通过序号,识别出这个是上一个连接的消息,就可以丢弃这个消息了

三次握手总结

 三次握手的初心主要是两个方面:

  1.投石问路:验证通信路径是否畅通,双方的发送和接受是否正常

   2.协商必要的参数,使客户端和服务器使用相同的参数进行消息传输

     

   2.断开连接(四次挥手)

50bba39441b140649efc89e882a83d8b.png

     019d8705c5604168a999292fe51fed54.png

四次挥手有时候可以三次完成,有时候不行,中间服务器发送的那两次,不一定能合并

ACK是内核程序顺发的,而FIN是根据用户代码控制的,举个例子来说;这是TCP程序的close代码

6204f4cc72e54ad5906c36d1a1f54f6b.png

close结束的很快,客户端一断开,服务器就会立即感受到并且结束,触发close,而玩意服务器还需要有收尾工作,就无法马上结束,close触发的慢此时就无法和上一个ack合并

   疑点:

1.a6c66ee316f64210902b22bcefc1664d.png

2.edafc7a6411844fdb9285f6e8332825a.png

这里举个例子

08b06f63dbc74593b40bce8a178d600a.png

如果第一组FIN/ACK丢失了,A直接重传ACK即可

如果是第二组FIN/ACK丢失了,重点关注ACK即可

04157d4a69d0418d87c44d728aa67e3e.png

同样的道理,A这边要等待一段时间才能释放连接,等待时间就是网络上任意两点之间的传输数据的最大时间*2 此事件定义为MSL,超时重传的时间必然是<=MSL,如果超时时间达到MSL的上限,此时%100包已经丢了

3.滑动窗口

    提高传输效率(更准确的意思是,是让TCP在可靠传输的前提下,效率不要太拉胯)

   使用滑动窗口不能使TCP变得比UDP更快,但是可以缩小差距

fedb48c4475749a2b8f8a5a9a049945e.png

如这幅图,没有滑动窗口,但是带有确认应答,这样的传输,能够保证可靠性,但是有大量的时间,都消耗在等ack上面,因此,下面这张图是带有滑动窗口的机制

b439f587d6964875b0053d59f599053d.png

上面这幅图一次性发一组数据,在发这一组数据的过程中,不需要等待ack,就直接往前发

此时,就相当于使用“一份等待时间”等4个ack。我们把一次发多少数据,不用等ack这样的大小,称为窗口,窗口越大,此时批量发送的数据就越多,效率就越高,但是窗口不能无限大,相当于完全不必等ack,此时就和不可靠传输差不多了

1e5b966844ce40bfadf262576c3eedc9.png

如图所示,在收到2001这个ack之后,A就立即发送5001-6000这个数据,此时A等待的还是四个ack 3001,4001,5001,6001,还是等四条ack(窗口还是一样大,但是往后挪了个格子)

滑动窗口是一个形象的比喻,实质上是批量发送数据,这样可以缩短等待的时间,比之前能提升一定的效率(虽然提升了效率,但不会比udp快)

    3.1按照这个批量传输的方式,中间丢包了咋办,我们分为两种状态来看
           1.数据丢了

cbcb59dcc1554a0496c08401281bba71.png

如果数据丢失了,那必须进行重传,我们仔细看这张图,在1001-2000丢失了之后,2001-3000这个数据到达了B,B返回的ACK确认信号,依然是1001,B仍然在向A索要1001这个数据,当S重新传1001到达B之后,B返回的ACK就是7001了

为什么会这样呢,因为在接收方,有个缓冲区在接受数据

e9b774bbf1fb4140bfff3ddbf0d42b4a.png

此时,相当于用最小的成本,来完成这个重传的操作,这个叫快速重传,当数据规模比较大的时间,我们使用滑动窗口

           2.ACK丢了

3797a8eabfde4b99ad85388c43ef91a6.png

    ACK如果丢了,不用做任何处理,也是正确的,因为收到了2001,虽然没收到1001,但是2001这个ack涵盖了1001的含义

4.流量控制(作为滑动窗口的补充)

    1.滑动窗口,窗口越大,传输效率也就越高,但是窗口也不能无限大,如果窗口太大了,就可能使接收方处理不过来,或者中间链路处理不过来,这样就会出现丢包的情况,就要重传,这样窗口并没有提高效率,反而还影响了效率

     2.因此流量控制,就是给滑动窗口踩踩刹车,避免窗口太大,导致接收方处理不过来

我们处理数据是这样处理的

7537b93933c644d1b401891fda98b1a3.png

   1.假如A这边生产的很快,B这边消费速度跟不上,接受缓冲区这边就越来越多,最终就满了,满了之后继续发送数据,就会丢包,因此,流量控制,就是根据接收方的处理能力,来限制发送方的速度

   2.因此,我们怎么衡量接收方的处理速度,此处就使用接收缓存区剩余空间的大小来作为衡量标准,剩余空间越大,应用程序消费数据的速度就越快

78d720b06c4f4027a189b4a64738ec09.png

5.拥塞控制

   网络传输在,总的传输效率,是一个木桶效率,取决于最短板,在网络传输过程中,会经历很多中间设备,这些中间设备,有些设备传输阔值高,有些阔值低,因此,我么怎么平衡中间设备的转发能力

   我们把中间设备看成一个整体,采取实验的方式,动态调整,产生出合适的窗口大小

    1)使用一个较小额窗口传输,如果传输通畅,就调大窗口

    2)使用一个较大的窗口传输,如果传输丢包(出现拥堵),就调小窗口 

    5.1 在TCP中,拥塞控制是这样展开的

           拥塞窗口:在拥塞机制之下:采用的窗口大小

           1)慢启动:刚开始通信的时候,会使用一个非常小的窗口,试试水

            2)指数增长:在传输通畅的过程中,拥塞窗口就会指数级增长(*2),指数增长是极快的,我们要加以限制

             3)线性增长:指数增长当拥塞窗口达到一个阔值的时候,就会从指数增长,转换为线性增长(+n)

              4)拥塞窗口回归小窗口:在窗口大小增长过程中,如果传输出现丢包,就会认为当前网络出现拥堵了,此时就会把窗口大小调整成最初的窗口大小,调整阔值(指数增长->线性增长)

4907f780a4e04dc3b9289569c42f52f5.png

6.延时应答

  提高传输效率的机制,围绕滑动窗口进行的

     我们在返回ACK的时候,拖延一点时间,利用这个拖延的时间,就可以给应用程序腾出更多的消费数据的空间

51b6ac25d8fd4fb398aa933d8c58e97a.png

如果此时立即返回ack,此时的窗口大小,就是3kb

此时稍微等500ms,再返回ack,此时有可能还会消耗2kb,此时返回的窗口大小就是5kb了

7.捎带应答

   在延时应答的基础上,引入第一个进一步提高效率的方式,延时应答,是让ACK传输的时机更慢,捎带应答,是基于延时应答,让数据进行合并

3ba76afe03e340a797cfe747504a69d0.png

让ack返回的时间更迟,就会是ack和响应合并在一起

811e38f2fa3943d49010d7e37472a917.png

数据报从两个合并成一个,效率会有明显的提升,主要还是因为这里的每次传输数据都是要封装分用的,能合并的原因,一方面是时机上是可以同时的,一方面是ack数据本身不需要携带载荷,和正常数据也不冲突,完全就可以让这个数据报,既能携带载荷数据,又能带有ack的信息

8.面向字节流

   在面向字节流这样的情况下,会产生一些其他的问题,例如粘包问题

  通过tcp read/write 的数据,都是tcp报文的载荷,也就是应用层数据

   发送方一次性是可以发送多个应用层数据报,但是在接受的时候,如何区分,哪里到哪里是一个完整的数据呢,如果没有设计好,就很难进行区分,甚至产生bug    

81205afdb93f4773852a2480b000fa96.png    

此处正确的做法是:合理的设计应用层协议

   1.在应用层协议中,引入分隔符,区分包之间的边界

      使用\n约定包之间的分隔符

56b2d0709c3c4c308aafe54c5906355e.png

    2.在应用层协议中,引入包长度,也能区分包之间的边界

       使用包的长度来进行区分

8d30fa7f0a3449bda5b2cf0df87bfaa1.png

40bd63ca5c4c422e9bf2250fd6b21de3.png

粘包问题,不只是tcp才有的,只要是面向字节流的机制都存在同样的问题

我们来看其他几种方案

 xml/json 都是通过分隔符来区分包的

protobuffer则是通过长度来区分的

9.TCP异常情况处理

   网络本身就存在一些变数,导致TCP连接不能正常工作了

    1)进程崩溃

        进程就没了=>PCB就没了=>文件描述符表也就被释放了=>相当于调用了socket.close()

         崩溃的这一方就会发出FIN,进一步触发四次挥手,此时,tcp的处理和进程正常退出没有什么区别

      

    2)主机关机(正常关机)

      正常关机,就会尝试干掉所有的进程,(强制终止进程)和上方处理崩溃的进程一样

      主机关机也有一定的时间,在这个时间内,四次挥手可能完成,如果没有完成,也没有影响

b4b3cdbc672b4f7682f64150b9a18f30.png

    3)主机掉电(拔电源,没有任何反应空间)

           分两种情况,假如有两个设备AB

         a)如果B在给A发消息(A掉电)

                B发的消息,没有ACK,B就会触发超时重传,重传继续失败,就会触发复位报文(RST字段变成1),就会尝试重置连接,连接失败,此时就会单方面释放连接了

          b)如果A在给B发消息(A掉电)

       B在等A发消息,A突然不发消息了,B就会阻塞等待。

      此处就会涉及“心跳包”,B是接收方,也会周期性的给对方发起一个不带任何业务数据的TCP数据报,发这个包的目的就是为了触发ACK确认一下对象是否还能正常工作/网络是否畅通,如果对方不能正确返回ack 说明对方已经挂了,

    虽然TCP已经有了心跳包的支持,但是还不够,往往需要在应用层中重新实现心跳包

    4)网线断开(主机掉电的升级版)

      假如A向B发送数据,此时A和B无法通信,

     A就是主机掉电的第一种情况,B就是主机掉电的第二种情况

3.总结

  以上TCP介绍了是个核心的特性

  1.确认应答(可靠性)

   2.超时重传(可靠性)

   3.连接管理(可靠性)

   4.滑动窗口(效率)

   5.流量控制(可靠性)

   6.拥塞控制(可靠性)

   7.延时应答(效率)

   8.捎带应答(效率)

   9.面向字节流=>粘包问题(编程注意事项)

   10.异常情况处理(心跳包(异常情况))

    3.1关于TCP和UDP对比

         TCP优势在于可靠性,适用于绝大部分场景

         UDP优势在于效率,适用于机房内部主机之间的通信

        也有适用于游戏这种的协议,比如像KCP这样的协议(其他的所谓“传输层协议”,并非真正在传输层,往往是在应用层实现类似于传输层的机制,同时底层是基于UDP来完成的)

   3.2 如何基于UDP实现可靠传输

    这个问题的本质还是考察TCP

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值