下面我们来进行一个仿真实验,本仿真实验的内容为观察主机发送 IP 数据报的过程,以及路由器转发 IP 数据报的过程。
我已经在软件中构建好了之前我们在理论课中所讲解的那幅网络拓扑,并且将需要配置的内容标注在了网络中各设备的旁边。例如主机需要配置 IP 地址、子网掩码以及指定默认网关,而路由器的每个接口需要配置 IP 地址子网掩码。另外路由器还需要添加静态路由:
为了后续实验观察方便,我们将每个接口的 MAC 地址也标注在了设备的旁边,例如主机的 MAC 地址,我们可以点开主机,然后选择配置,然后在这个 FAST INTERNET0 也就是主机内部的这块网卡,那么我们可以看到 MAC 地址,这里就是 MAC 地址,那么把它复制一下,标注在主机的旁边。
对于路由器的话我们点开路由器,然后配置,那么在它的这三个接口 0 号 1 号 2 号,那么点开以后我们也同样可以看到它的 MAC 地址,我们把它复制出来标注在旁边,以方便我们后续实验的查看。图中的这三台交换机,我们并没有对他们进行任何信息的标注,这是因为我们在之前的理论课中已经讲解过交换机的工作原理,自学习算法以及生成树算法,并且都进行了仿真实验。因此在本实验里面,我们就要不去详细的观察交换机颁发 IP 数据报的过程了。
现在我已经按图中各设备标注的信息给他们配置了 IP 地址子网掩码,对于主机的话,我们还指定了默认网关,对于路由器的话,除了配置它的各接口的 IP 地址,子网掩码以外,还给他们添加好了所标注的相应的静态路由。如果您不会进行配置的话,请您参考我们上一个仿真实验的视频。接下来我们从实时模式切换到仿真模式,然后我们并不需要查看这么多的协议,我们把它先全部隐藏掉,然后选择我们需要的 ARP 协议和 ICMP 协议就可以了。
我们先来进行一下左上面这台主机对左下面这台主机发送 ICMP 的这种询问报文的情况,当然 ICMP 的询问报文是封装在 IP 数据报中进行发送,在发送之前我们需要把相关设备也就是左上这台主机这个路由器以及左下面这台主机,我们先把他们的 ARP 高速缓存表我们把它打开,另外我们还要把路由器的它的路由表也打开,我们点击右侧的查看按钮,然后选择主机 ARP 表,那么我们需要调整一下就这个窗口的大小,我们把它调成合适的位置。然后这三个栏也就是这 IP 地址硬件地址接口,这三个我们需要调整一下。
然后我们在标鼠标是在查看状态,我们就可以在点击这个路由器,我们把它的 ARP 表也打开,调整好以后我们放到它的旁边,还有下面这个主机,我们把它的 ARP 表也打开,如果它没有内容的话,它这三个栏目也就是三个列表的三列,其实是挤压在一起的,我们需要调整一下。
那么我们先来解释一下,由于我们现在是最初始的状态,那么或者你认为是刚上电的状态,那么这两个主机上下这两个主机它的 ARP 高速缓存表是空的,而我们路由器里面我们可以看到三条记录,这三条记录实际上分别记录着路由器的三个接口的 IP 地址,和他们自己和他们对应的 MAC 地址的关系,路由器上面以后它的 ARP 高速缓存表里面就有这样的三条记录,也就是标明了它的三个接口的 IP 地址和 MAC 地址的对应关系,另外我们还需要把因为待会我们要看到路由器转发 IP 数据报的这样的一个过程,所以我们需要把它的路由表也给它打开。同样的我们也来解释一下这个里面的情况,凡是标了 C 的,凡是标了 C 的,我们可以看一下这个类型是 C 的表示直接连接,也就是说路由器和上面网络直接连接,那么这是上面网络的网络地址,这个表明它和下面网络下面这个网络直接连接右边这个接口,这表明和它这边右边的网络直接相连也就这条记录。
另外 L 这种类型是表示它自己的各个接口的 IP 地址,另外我们看到这儿有一个 S 类型是 S 的,那么它的目的网络是 192.168.16.192/29,其实它标识的是到最右下角的这个网络这条记录。那么我们可以看到从路由器要到网络来下一跳,实际上是要跳给路由器的,这个接口也就是 192.168.16.202,所以我们看到下一跳在路由表里面也是这样标注的,这些东西都是 C 和 L 的这种的,是路由器自己生成的,他自己就知道,一旦你给他配了 IP 地址,这个端口,配了 IP 地址和地址掩码的,他就知道了。而 S 这种类型的是静态路由,是我们之前的添加的:
现在我们就让左上面主机给左下面主机发送一个 IP 数据报,我们点击右边的添加简单的 PDU,然后我们把它指定发送方,也就是源主机点上面主机,然后再指定目的主机点下面这个主机。我们可以看到他确实是先用 ICMP 的协议的封装了一个 ICMP 的询问报文,当然报文最终是要封装成 IP 数据报的来发送走的,但是我们看到现在好像他并没有发送,而是要发送另外一个绿色的这个地方表示的数据包,我们看到是 ARP 协议,我们点开 ICMP 的数据包,我们看一下它到底发生了什么事情。
那么在这里面我们可以看到体系结构的第三层第二层分别是怎么处理的,那有详细的描述,我已经把全部翻译成中文了,所以看起来呢并不复杂。您如果不太熟悉的话,可以逐条的去读一下它到底发生了什么事情。
而我这里就大概解释一下,为了节约时间,我们大概说一下主机要发送 ICMP 的询问报文的话,最终它是要封在这个 IP 数据报里面, IP 数据报的话源地址它填 192.168.16.1 这个没有问题。目的地址它要填谁,当然是我们指定的目的主机是 192.168.16.129,然后封装成 IP 数据报以后,还要封装成以太网的帧才能发送走。那么以太网的帧的首部里面,原来的地址填的是也就是源 IP 地址对应的 MAC 地址,也就这台主机的网卡上的 MAC 地址,但是下面就有问题了,就是目的 MAC 地址到底填什么,那么千万不要误解成说源 MAC 地址你是按照源 IP 地址对应关系填出来的,也就是原来 P 对应什么,源 MAC 地址你就填什么,那么目的 MAC 地址是不是也是按照这个 IP 数据报里面的这个目的 IP 地址,然后它所对应的 MAC 地址你把它填写上就就可以了,实际上是不是的。
这个 MAC 地址我们在理论课程中是讲过的,他在逐段链路上,在每段链路上源 MAC 和目的 MAC 是逐段链路变化的,当然这个 IP 地址源 IP 和目的 IP 是在 IP 数据报总部当中,从源网络传递到目的网络它都是不会变化的。从图中我们可以非常清楚的看到,主机应该把 ICMP 的报文发送给路由器,由这个路由器转发给下面这台主机,但是对于主机而言,他自己到底是怎么知道的,我们人是一下就可以看出来的,对于上面的主机他是怎么知道的呢?首先他把目的主机的 IP 地址,这个是我们告诉他的,让他上面就是发给目的 IP 地址这个地方,这个他是知道的,所以他把这个目的主机的 IP 地址 192.168.16.129 和自己的子网掩码的相与,这样与完的结果就是会得到一个网络地址,然后它的网络地址和它自己所在的网络的网络地址去比较,他自己所在的网络地址他也是知道的,比如说他怎么知道的,他用他自己的 IP 地址和子网掩码与,得出来这个东西,就是他自己所在网络的网络地址。
我们刚才说的是他把目的主机的 IP 地址和自己的子网掩码一与得到了一个地址,这是一个网络地址,他看这个网络地址和他自己的所在的网络相不相等,如果相等的话,他就知道它和目的主机是在同一个网络里面,这样属于直接交付,如果得出来的结果不等的话,他也会知道原来他和目的主机是不在同一个网络,需要这台主机上面所在网络上面的一台路由器,然后在这里我们叫做默认网关,需要由默认网关也就这台路由器帮他进行转发,因此主机将 ICMP 询问报文首先封装在 IP 数据报里面,在 IP 数据报的首部里面,源地址就填的这台主机的 IP 地址,而目的地址填的是下面这台主机的 IP 地址,这样 IP 数据报就要封装完成了,但是他还要再封装一次封装成把 IP 数据棒给它再封装成以太网的帧。在以太网的帧的首部源 MAC 地址填的就是主机网卡的 MAC 地址。目的 MAC 地址我们现在刚才已经解释过了,很显然的是应该先发送给路由器的,所以目的 MAC 地址应该填写的是路由器的接口的 MAC 地址,也就这个 MAC 地址,但是我们之前并没有给这个主机,并没有告诉他,实际上也没有机制来告诉他,我们其实只能告诉他的是默认网关的 IP 地址是什么。因此这台主机实际上他现在并不知道目的 MAC 地址填什么,但是他知道目的 MAC 地址应该填的是 192.168.16.126 这个 IP 地址对应的 MAC 地址,因此他就会在他的 ARP 的高速缓存表去查这个东西。很显然 ARP 高速缓存表由于是初始的状态,它里面没有任何的记录,所以查不到。
那么这个就用到了我们之前所讲过的 ARP 的协议,主机它的 ARP 进程就会发送一个 ARP 的广播请求,这是一个广播目的就是问 192.168.16.126 这个 IP 地址对应的 MAC 地址是什么?那么我们点右侧的捕获前进,我们让它进行一步广播请求到达交换机以后,由于它是个广播,所以交换机就会从除了进口以外的其他口都把它转发出来好。那么我们注意观察一下,转发出来到上面右上方的这台主机,这是一把叉,那么表明他不接受要丢弃的东西,这个是很合理的,因为右上相关主机的 ARP 进程最终收到 MAC 广播请求,把它的内容一看,其实问的并不是他自己,所以他就不理。会而我们请求也到了路由器上面,那么我们可以看到路由器原来有三条记录,现在当他收到 ARP 广播请求以后,它变成了多一条 4 条,那么具体多的就是这一条,这一条具体来看一下是一个 IP 地址和一个硬件地址的对应关系,这个 IP 地址 192.168.16.1,很显然就是主机的也就是源主机的,而后面硬件地址就是简单的,我就说它的最后 4 位就是 7793,也是源主机的网卡的 MAC 地址,正是因为路由器收到了源主机发的 ARP 的广播请求。
那么路由器就知道源主机的 IP 和源主机的 MAC 的对应关系,他顺便就把这个对应关系写到了自己的 ARP 的高速缓存表里面,以备以后可能会用到。那么他现在除了把写进来以后,他看到就问他自己的,也就是说我们源主机现在想知道你路由器的这个接口的 IP 地址,它对应的 MAC 地址到底是什么东西,所以我们这个路由器会做出响应,那么我们点击下一步。到了交换机由于这是一个单播,并且之前在发广播过来的时候,交换机已经记录了源主机的 MAC 地址和交换机的端口的对应关系,所以这回是一个明确的单播,交换机可以明确的转发给源主机。那么我们的源主机收到以后,我们可以看到它的 ARP 高速缓存表里立刻就会出现一条 IP 地址和硬件地址的或者叫做 MAC 地址的对应关系。这个 IP 地址 192.168.16.126,就是我们路由器那当然也是源主机的默认网关了,它的接口 192.168.16.126,所对应的 MAC 地址尾数是 6901,我们看到这只是 6901。
那么现在对于我们源主机来说,它封装以太网帧的时候目的那个地址就好填了,这时候就直接填他请求来的默认网关的这个接口它所对应的 MAC 地址,所以他现在准备发送之前 ICMP 的询报文是封装的 IP 数据报里,然后再把 IP 数据报封装在以太网帧里面,我们可以点开看一下。在第二层以太网的一个数据链的层的话,它的源 MAC 地址 7793,也就是这台主机的 7793 就是 MAC 地址,目的 MAC 地址填的尾号是 6901,我们可以看到这个路由器就是它的主机的默认网关,它的接口的 MAC 地址尾号就是 6901,也就是说他请求来了以太网的帧就可以封装成功了,那么接下来他就可以去发送了。
我们点击捕获前进按钮,让他开始发送 ICMP 的询问报文,到达交换机,然后再点一下就到达了路由器。那么到达了路由器以后出现了现在这样的现象,我们可以点开来观察一下。那么这是 ICMP 的报文进来以后逐层的解封装,然后再去准备要把它转发了。我们看一下准备转发的时候,具体又是什么样一个情况。为了节约时间,我们不详细去看每一条的软件里面给我们解释,我来大概解释一下,路由器收到这个数据帧以后对它进行解封装,把里面的 IP 数据报解出来,主要目的是为了提取 IP 数据报首部的目的 IP 地址,然后根据这个目的 IP 地址在自己的路由表中进行查表,来找一条路由记录,那么找到以后如果能够找到的话,就按照这条路由记录的下一跳所指定的 IP 地址进行转发。
那么具体的怎么去查这个表,我们在理论课当中已经讲过了,并且仿真软件里面并呃并没有一个具体的这样的查表的,可以仿真出来的一个过程,所以我们这个里面就不详细演示了,最终他查表的结果可以看到他应该是从下面这个接口把它转发出来,那也就是从下面这个接口我们在这个表格里面找一下,应该是从到达这个网络,却应该从它的一号接口把它转发出来,那么应该从一号接口转发出来以后,他就知道了从一号接口转发出来,这下一跳,其实是没有的,这表明了这是它一号接口和目的,主机所在的网络是同一个网络,所以这属于又是属于直接交付。于是路由器知道了直接交付的话,它重新封装 IP 数据报,把这个 IP 数据报重新封装成帧。此时在帧的源地址里面就应该填写,因为是他决定从下面这个一号接口把它送出来,那就应该填写 端口 1MAC 地址。
而目的 MAC 地址路由器他并不知道,他只知道目的 IP 地址是 192.168.16.129,所以他就会在自己的 ARP 高速缓存中去查找 192.168.16.129 这样一个 IP 地址它所对应的 MAC 地址。而我们可以很明显看到这个时候在这里面只有 4 条记录,并没有 192.168.16.129,所以他查不到,因此他无法封装成数据帧,所以他就把收到的之前收到的数据跟当然里面就是封装有我们的 ICMP 的报文数据帧的它就给它丢弃了。然后紧接着他会立刻发送一个,我们可以看到这个东西又是一个 ARP 的广播请求。我们点击下一步广播请求出来以后,到了交换机进行泛洪的这种转发,然后到达右侧这台主机的话,他把他丢弃了,因为不是问他:
而到达我们目的主机的时候,我们可以看到我们目的主机就立刻把广播请求所带来的信息,也就是路由器接口的 IP 和 MAC 地址的对应关系写在了自己的 ARP 高速缓存表中,以备以后会需要用到。
然后我们这个主机把这个内容解析出来,看到是问的是自己的,对方请求的自己的 IP 地址和 MAC 地址的关系,所以它会发送一个单播的这样的一个 ARP 的响应回去。当我们路由器收到以后,我们会观察到在路由器的 ARP 高速缓存表里面又会多了一条记录,这条记录就是记录的我们目的主机的 IP 地址和它的 MAC 地址的对应关系,我们可以看到在这里
这样的话,我们让源主机给目的主机发送 ICMP 的询问报文的过程其实就要结束了,但是我们看到其实我们目的主机最终实际上是没有收到 ICMP 的询问报文的,那是因为把路由器把它丢了,把它丢弃了,主动给它丢弃掉了,这是因为这个路由器它还要发一个 ARP 的广播请求去询问,并且还要等待目的主机,把 IP 地址和 MAC 地址对应关系,再用 ARP 单播响应的方式传回来,所以这样路由器就把之前的 ICMP 丢掉它,把它直接先丢弃掉。
那么这之后我们可以再来看一下又会是怎么样的情形。我们点击右下角这个箭头,把之前我们让它传递的这些场景给它删除掉。现在我们需要注意一下的就是我们的源主机的 ARP 高速缓存表里已经有默认网关的 IP 和 MAC 的对应关系,而路由器它的 ARP 高速缓存里面也有我们目的主机的 IP 和那个地址的对应关系,所以我们应该可以想象到,我们再让上面这台主机给下面这台主机发送一个 ICMP 的请求报文的话,应该很顺利的就能够这样一路的最终到达目的主机,而不会再看到之前的 ARP 的请求的过程。那么我们来看一下是不是这样,我们点击这儿的添加简单的 PDU 然后我们点击源主机再点击目的主机,然后我们点击捕获前进,到了交换机转发下来,然后到了路由器,它会把数据包的目的 IP 地址提出来比对,然后决定从它的端口一从这送下来,然后到了交换机以后又明确的转发给我们的目的主机。由于这是我们发送的是 ICMP 的询问报文,所以我们主机在收到以后还会给我们的从目的主机收到以后,还会给我们的源主机再发回一个 ICMP 的应答报文,所以我们可以看到他还会发一个应答报文过来,那么这一次我们就可以看到很顺利的源主机给目标主机发送 ICMP 的询问报文,目的主机收到后会给源主机回了一个响应。
这就是为什么 ping 的第一次会超时!
现在我们把之前打开的这些窗口都给他们关闭掉,比如说 ARP 的高速缓存,还有路由器的路由表,然后把我们之前做过的实验的场景也给它删除掉。
我们再来看一个现象,我就现在用这台主机,我想用右上这台主机来 ping 一下右下左侧这台主机,这也是我们在理论课过程中给大家提的一个练习题。我们说了是在仿真软件里面,最终我们来把它解决掉。我是怎么做,我用上面这台主机在他的命令行里面,也就是上面这台主机,我来 ping 一下下面这台主机,PING 这台主机的 IP 地址我们已经标成了 192.168.16.193,也就是我们给它设置的是 IP 地址,我们 ping 一下这个 IP 地址。192.168.16.193,然后我们回车。为了快速的看,我们需要把从仿真模式切换回实时模式,那么我们再点击出来它切换以后窗口丢掉了,你再点击它就出来了。我们看到第一次请求超时,第二次请求超时,第三次收到了这个目的 IP 地址,也就是这个目的主机回来的响应,第 4 次也收到了。
我们先解释一下这个 ping 实际上是干什么,相当于是我们比较熟悉的发送简单的 PDU 我们将添加给源主机,再添加给目的主机,让他发送一次。其实这个 ping 我们可以把它看成这样进行了,也就是发送了 4 个简单的 PDU。我们把鼠标状态切换回选择状态,然后我们再点主机就看到我们刚才 ping 的结果有两次超时,有两次它收到了,它相当于源主机给目的主机,依次发送了 4 个 ICMP 的请求报文,但是有两个没收到响应,他判定为超时而另外两个是受到了响应。那么根据我们之前所讲的左上面主机这台主机给左下这台主机发送 ICMP 的请求报文的过程,其实我们可以分析出来,或者说我们可以猜测出来,给这个主机它在发送 ICMP 的请求报文的过程中,第一次报文到达了路由器,但是由于路由器它的接口并不知道它的对应的对方的接口的 IP 地址所对应的 MAC 地址,所以首先路由器就是把 ICMP 的报文给它丢掉了,然后他去询问一下用 ARP 的广播请求去询问一下。然后这个过程就结束了,那也就是第一条超时的原因
而第二条超时是因为第二次主机我们说了他要发连续 4 个 ICMP 的请求报文,所以他就发了第二个到左边路由器以后,这个时候路由器通过查自己的路由表,然后直接转发,那么这个时候也不需要请求,不需要 ARP 请求了,直接转发给右边的路由器,而右边路由器查表转发决定从下面这个口送下来,但是这个时候他又不知道目的主机的 IP 地址对应的 MAC 地址,所以他又发了一次 ARP 的广播请求,在发之前他就把刚才说到的 ICMP 的第二个 ICMP 也丢掉了,那也就是我们看到的第二次超时,
然后他就发一个 ARP 广播请求,就获取到了目的主机的 IP 地址对应的 MAC 地址的关系。然后我们这台主机他第三次发送 ICMP 数据报的时候到了路由器很顺利的转发到这个路由器,很顺利的转发下来。然后第 4 次也就是说第 3 次我们看到是这样的,收到的响应第 4 次和第 3 次是一样的,我们现在再试下,我们想一想会看到什么结果,再 ping 一次的话应该是 4 次都顺利收到的。我们摁键盘的向上的箭头键就可以不用重复去打这条命令。现在我们可以看到第一次收到了 4 个响应,收到了 4 个目的主机发回来的响应
如果想自己单步仿真:重启工程即可。
如果您对我刚才的这种解释还是不能信服的话,您可以自己对我们刚才这个过程进行一下仿真,具体的仿真的话就要做一些工作,比如说因为我们刚才已经用 ping 的方式 ping 过了,也就是我们在这里面已经 ping 过了,所以那个过程并且我们在实时模式下做的,那么这样的话主机交换机还有路由器,他们的 ARP 高速缓存都已经有了相应的记录,所以你再去单步仿真的话,或者你不去单步仿真还是这么听的话,我们刚才已经 ping 过了,他就没有那种超时的,也就是你看不出来数据包被丢掉的情况,所以我们需要清一下 ARP 的高速缓存。
那么我们来说明一下对于主机的话,我们可以在它的命令行直接打 ARP -d 这样就清空了。那么当然 ARP -A 我们可以查看一下,就是说没有 ARP 条目找到就是 ARP 表示空的,你这样就可以清掉。而交换机我们要进去以后,在它的命令行界面那点回车,然后 DNABLE 进入他的客权模式,然后我们可以用一条命令叫 CLEAR mac -a 这是一条简写的命令,写完了是全写的话应该是 MAC -ADDRESS,我们可以用简写就可以,这样一做也是清了。那么另外路由器可不可以清,路由器也有相应的命令,我们点回车,然后 enable 进入它的特权模式,是 CLEAR ARP 这个说明一下刚才我们清交换机的时候,我们清的不是它的 ARP 缓存,清的是它的 MAC 地址表,也就是它的记录着各主机的 MAC 地址和它的端口号的对应关系,我们请的是交换机的 MAC 地址表,对于路由器和我们的主机而言,我们清的都是 ARP,只不过主机的命令是 ARP -D 而路由器的话它是 clear ARP,
但是这里面有一个问题,我们尽管这样打了,在路由器里面清除的 ARP,我们 show 一下 ARP 的高速缓存,我们仍然可以看到凡是有一条短线的,应该就是它的这三个接口的 IP 地址和 MAC 地址的对应关系,但是这里面你比如像这一条,192.168.16.2 其实是主机的,然后它对应的 MAC 地址我们看尾号是 81D1,就是这儿的 81D1 是主机的,我们打了一遍 clear arp 然后我们再把它收出来 ARP 其实在我们仿真软件里,它仍然存在这条记录,并没有把它删了,按道理来说应该是出了命令以后,除了它接口它本身自己知道的三个接口的 IP 地址,对于 MAC 地址这个是有所不变的,除非你人为去给它重新配置 IP 地址,会改变,否则是不会改变的。
而其他的它处于传输过程中动态获取到的,就 ARP 协议动态获取到的这样的一个 IP 地址和 MAC 地址的记录,比如说这一条,还有上面这一条,还有下面凡是现在我们看到标的 0 的都是那么什么意思?这个地方它有一个时间,也就是说这条记录从诞生以后有多长时间,比如说我们待会再瘦一下,这个时间就可能变为一了,当然它会有一个上限到一定时间的时候,这条记录就会被刷新掉,那这个可能是仿真软件的一个 BUG,也就是说我们打了 clear ARP 的话是清不掉这个的,而你清不掉路由器里面的 ARP 高速缓存表,
你自然的就会就不可能看到路由器去上面这台主机在访问下面这台主机的话,那么路由器要访问路由就是要获取路由器的接口 MAC 地址,而右边这个路由器要用他需要知道下面这个目的主机的 IP 对应的 MAC 地址 你自然就看不到,因为这两个路由器的 ARP 高速缓存在我们之前用 ping 的那种方式的时候,它已经自动获取到了 IP 地址和 MAC 地址的对应关系。
所以怎么办?您可以直接把实验的工程先保存了,然后退出去,然后重新启动这个软件,然后把这个工程再打开。这样的话我们各个主机里面的 ARP 高速缓存表就被清掉了,然后路由器里面的 ARP 高速缓存表就可以清掉了,然后您在单步的情况下,用 4 次就用添加 PDU 的情况这种方式让源主机给目的主机去发,但您会看到第一次的时候这个左边的路由器会把这个 ICMP 的请求丢掉,然后您再添加一次让源主机给他发,您这回可以看到的数据包应该是从路由器的转发到右边路由器以后,右边路由器又把它丢掉,而去询问他的 IP 地址对应的 MAC 地址。在这两次之后,您在做第三次的时候,再让主机给主机发送 ICMP 的报文的话,我们就可以看到非常顺畅的就可以到达目的主机第 4 次一样,这样的话您就可以验证出来和我们用 ping 在实时模式下 ping ,如果他们的 ARP 高速缓存都是空的话,那么你会看到有两次的请求超时,那有两次是没有超时是得到了回应。
标签:11,ARP,IP,MAC,计算机网络,地址,主机,路由器 From: https://www.cnblogs.com/PeterJXL/p/18226841