自我表扬:《Dubbo 实现原理与源码解析 —— 精品合集》
表扬自己:《D数据库实体设计合集》

摘要: 原创出处 https://juejin.im/post/5b152061e51d4506a269a34f 「YangAM」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

上篇文章 我们完整的描述了计算机五层模型中的『应用层』和『运输层』,阐述了较为复杂的 TCP 协议的相关原理,相信大家一定也有所收获,那么本篇将继续五层模型的学习。

网络层

『网络层』其实解决的就是一个「转发」的问题,通过传说中的『IP 协议』划分了网络范围,即我没有直接用网线和你连在一起,我也能通过你的 IP 分析出该怎么样找到负责你的网关路由器,并通过你的网关路由给你传输数据报。

这就是『网络层』做的事情,它本质上解决了两台不存在于同一子网络下的主机相互通信的问题。而『IP 协议』以及「如何解析 IP 的算法」算是两个最核心的内容,我们首先看看这个『IP 协议』的相关概念。

以 IPv4 为例,使用 32 个比特位描述一个 IP 地址,所以理论上,整个 IPv4 可以提供 40 几个亿的 IP 地址,我们一般使用『点分十进制』来表示。

例如:11000001 00100000 11011000 00001001 的 IP 地址一般记为 193.32.216.9。

由此,我们解决了 IP 编址的问题,但是如何通过 IP 地址判断出它所属的子网络呢?

引入一个名词『子网掩码』,它在形式上和 IP 地址一样,使用 32 位比特位进行表述。其中,描述网络部分的比特位全为 1,子网络中的该主机编号部分全为 0 。

例如:子网掩码 11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。它明确了某个使用该子网掩码的 IP 的前 24 位是它的子网络部分,而后 8 位是该 IP 对应的主机在子网络下的一个编号。

举个例子:

IP 地址 172.16.254.1 所对应的子网掩码为 255.255.255.0,那么我们只需要做『AND』运算这两者即可得到该 IP 地址的网络部分。

所以,这个 IP 地址的网络号为 172.16.254 。

下面我们探讨一个十分重要的协议,它解决了一个刚加入子网络的主机如何获取属于它的 IP 地址的问题,这个协议叫,动态主机配置协议(DHCP)

DHCP

一般来说,我们有两种方式来配置主机的 IP 地址,一种是管理员手动的指定一个 IP 地址,当然,这样的成本是非常高的,你不能配置了一个已经被分配出去的 IP地址,即管理员需要记录所有已分配的 IP 地址。

另外一种呢,就是我们的 DHCP 协议,它允许新加入的主机自动获取一个 IP 地址以及相关的子网掩码和网关地址等。

默认情况下,路由器隔离广播包,不会将收到的广播包从一个子网发送到另一个子网。当 DHCP 服务器和客户端不在同一个子网时,充当客户端默认网关的路由器将广播包发送到DHCP服务器所在的子网,这一功能就称为 DHCP 中继(DHCP Relay)。

也就是说,一个子网络中应当有一台 DHCP 服务器,用于整个子网中 IP 地址的分配。但为每个子网都单独配置一个 DHCP 服务器也有点「愚蠢」。

所以另一种解决办法就是,某个网络中的网关会知道负责该网络的 DHCP 服务器在什么位置,IP 地址是什么,网关路由会负责转发 DHCP 报文请求并返回响应的报文,这就叫 DHCP 中继。

当然了,实际上现在的路由器本身就可以充当一个 DHCP 服务器,为其所在的子网提供动态地址获取服务,所以往往也不需要转发那样麻烦。

而完整的 DHCP 请求与响应的过程则是这样的:

第一步:

DHCP 服务器发现。 这个阶段的首要任务是,找到当前网络中 DHCP 服务器的位置,并且整个 DHCP 报文的交换是基于 UDP/IP 协议的,向目的端口 67 发送。

本机由于没有 IP 地址,所以 IP 数据报中的『源地址』为「0.0.0.0」,『目的地址』为「255.255.255.255」。

这样在链路层广播该数据报的时候,同一子网络下的所有主机都会接受该数据报,但只有 DHCP 服务器会响应这个请求。

于是如果路由器本身就是一个 DHCP 服务器的话,那将进入第二步,否则路由器将分组转发到 DHCP 服务器所在的网络内。

第二步:

DHCP 服务器提供。 DHCP 服务器,无论是位于外网或是网关路由本身,在收到一个『发现报文』后,将响应一个『提供报文』。

该报文中将包含,推荐客户使用的 IP 地址、子网掩码、IP 地址租用周期等信息。

第三步:

DHCP 请求。这其实是一个选择阶段,客户端主机确认服务器推荐的参数,决定使用,于是依然以广播的形式发送请求向服务器确认。

第四步:

DHCP ACK。收到客户端主机发来的确认请求后,服务器将实际从 IP 池中分配出一块 IP 地址出来,并返回客户端确认信息的 ACK。

从此之后,该主机算是获得了一块可用的 IP 地址了,终于加入了网络。

除此之外,还有一个细节不知道大家日常有没有留心,就是我们对于同一个子网络,IP 地址基本总是一样的,并没有因为每次开机后连入网络而被分配不同的 IP。

这一点算是 DHCP 协议的一个约定了,当某台主机第一次加入某个子网络,它将从 DHCP 服务器获取一个全新的 IP 地址。

而以后该主机重新加入到该网络时,将直接进入 DHCP 请求的第三步,将主机上次使用的参数发给服务器,确认是否可用。而一般情况下服务器会同意并按照你的要求分配出去一块 IP 地址,这也是为什么你每次使用的几乎是同一 IP。

讲完了 DHCP 动态获取 IP 地址,接着我们简单看看 IP 数据报的基本格式,并在最后讨论一下路由器的选择算法,看看一个 IP 数据报是如何被路由器给转发出去的。

image

关于其中的各个字段或选项是如何被使用的,我们这里暂时先不做讨论,强行解释并适合大家理解,等到具体分析报文分发与解析时会容易理解很多。

路由器

路由器是网络层的一个核心设备,它完成了从「目的 IP 地址」到「目的 IP 所在的子网络」的完整路径转发过程。它的内部结构如下:

image

每个端口都直接连接了一台设备,而其中的『路由选择处理器』则负责解析一个输入端口进来的数据应该被推出到哪个输出端口中去。

所以,你应该也发现了,整个路由器的核心应该是这个『路由选择处理器』,也就是驱动这个『路由选择处理器』工作的算法,我们称之为『路由选择算法』。算法本质上就是解决,一个数据报输入进路由器内存,该从哪个输出口转发出去的问题。

一个好的 『路由选择算法』不仅仅应该解决如何到达目的地的问题,还应该考虑如何最快的到达目的地,即能够判断并选择性的绕过拥塞的网络路径。

整个路由选择算法分为两大类,全局式路由选择算法和分散式路由选择算法。前者的一个最典型的实现就是『链路状态路由选择算法』,后者的一个最典型的实现就是『距离向量算法』。

这两者算法的理论原理这里不再和大家一起探讨了,我们着重看看因特网中是如何基于这两种算法实现的路由选择。

首先,整个因特网是一个很庞大且复杂的系统,所以整体上被划分为一个一个的自治系统(AS),在每一个 AS 中都运行着同样的路由算法,自治系统之间使用 BGP 协议交换信息。

image

整个因特网大致就是这样的一个个自治系统互联构成的,而自治系统内部的所有路由器都运行着同样的路由选择算法,基于距离向量的『RIP 协议』或基于链路状态的『OSPF 协议』。

至于为什么要拆分自治系统,等我们介绍完这个 RIP 或者 OSPF,你就明白了。

RIP 协议的算法是这样的

image

简单的一个自治系统,我们以此为例看看整个 RIP 协议是如何工作的。

首先最开始,路由器 A 的转发表肯定是这样的:

----------------------------
目的子网 下一跳路由 跳数
x B 1
q E 1
----------------------------

其他路由器也是类似的,第一步都建立起与自己直接相连邻居的连接。

第二步是一个不断进行的过程,相邻的路由器之间每隔 30 秒就相互交换信息,告知对方自己的转发表内容。

所以经过一次交换之后,路由器 A 将收到来自 B 和 E 的转发表信息,于是路由转发表更新如下:

----------------------------
目的子网 下一跳路由 跳数
x B 1
q E 1
y B 2
p E 2
----------------------------

但是这里有一个细节,子网络 y 是可以通过 A - B - y 到达的,但同时也可以通过 A - E - C - y 到达。你也许已经猜到了,路由器当然会选择最短路径的一条来更新自己的转发表。

所以,这个距离向量的算法本质上就是通过相互之间不断的交换信息以保证某个自治系统内,所有的路由器都知道某个目的子网的最短路径。

OSPF 的实现是这样的:

我们同样以上面的例子进行解释:

image

OSPF 是基于链路状态路由选择算法进行实现的,所以它也是一个全局性路由选择算法,算法运行一次即可完成全网的路由信息更新。

而 OSPF 本质上就是一个迪杰斯特拉求最短路径问题,它通过不断的迭代与计算更新整个路由转发表。假设现在我们的路由器 A 运行 OSPF 协议:

第一次迭代完成后,它得到与 B、E 两台路由器相关的子网络的路径计算。

第二次以 B 或者 E 为起点重新运行算法,这里我们假设以 B 为起点运行了算法,那么与 C 相关的子网络的路径也被更新进 A 的路由转发表。

第三次以 C 为起点同样的运行算法,得到和 D 相关的子网络路径更新。

由于 D 作为末端路由,并没有直接相连的其他路由,所以算法不再继续,回到 E。

第四次,以 E 作为原点,运行算法,得到了 C 相关子网络的路径,如果有更短的路径,将更新 A 的转发表以最优路径。

那么,待整个算法运行结束,一个自治系统中的所有路由器几乎全部遍历,但是却不同于 RIP,OSPF 相对而言收敛快,可以迅速完成任务,而 RIP 则需要不断的交换信息以达到需求,往往会陷入一个长周期。

当然了,OSPF 需要较强的 CPU 计算能力和更多的内存存储空间。所以总的而言,他们都广泛应用于整个因特网之中,RIP 应用在较为底层的 ISP 上,而 OSPF 则运行在较为高级的 ISP 中。

至此,整个网络层的基本内容也介绍完了,总结一下,网络层的核心任务就是负责转发分组,而如何将分组转发到目的主机的网络中呢,牵扯出 IP 协议,通过 IP 地址与子网掩码划分子网络,而路由器执行路由选择算法得知目的子网络的完整路由路径并进行分发。

链路层

网络层解决的是,分组转发的目的网络,也就是转发给目的网络的网关路由,而链路层解决的是,将分组广播给个人,也即目的主机。

网络层的 IP 数据包会在链路层被封装成『以太网帧』,它的基本结构是这样的:

image

前导码用于同步时钟,按照我的理解就是区分一个一个的帧,源和目的地址指的是『Mac 地址』,也称作物理地址。

『Mac 地址』是硬件级别的主机唯一标识,由生产厂家唯一确定。类似这样:

34-E6-AD-17-A5-6B

全球任意一台主机的 Mac 地址都是不同的,它不像 IP 地址可以在别人不用的时候共享。

下面我们要讲一个协议,它完成了主机 IP 地址到 Mac 地址的转换,他就是 ARP 地址解析协议。

ARP 协议其实有点类似于我们之间在应用层介绍的 DNS 协议,输入一个域名 地址,输出一个 IP 地址,而 ARP 而言,输入一个 IP 地址,输出一个 Mac 地址。

网络中的每台主机,包括路由器,都内置的 ARP 模块和 ARP 表。当一份数据报到达链路层时,首先要做的就是以该数据报的目的 IP 作为输入,先查询自己主机的 ARP 模块,如果能够得到该 IP 的目的主机 Mac 地址,那么封装一个以太网帧交给物理层发送出去就好。

而如果本机的 ARP 表中并没有存储目的 IP 主机的 Mac 地址,那么就需要向同网络中的其他主机进行查询。

发送方会构建一个特殊的 ARP 分组,源 Mac 地址为发送方的 Mac 地址,目的 Mac 地址为广播地址:255.255.255.255,以及源和目的 IP 地址,本质上就是一个特殊的以太网帧。

于是该网络下的所有主机都将收到这个 ARP 分组,那么他们要做的就是拆开 IP 地址比对是否和自己的 IP 地址相同,如果是则响应一个 ARP 分组,告诉发送方自己的 Mac 地址。

如果不是自己,则还会检查自己的 ARP 模块,看看是否能提供帮助。

最终,发送方会得到想要的目的 Mac 地址并更新自己的 ARP 表,然后封装一个正常的以太网帧发送出去。

由于以太网采取的是『广播』方式,所以同一子网络中任意一台主机发送报文,所有的其余主机都会收到,但是它们会匹配目的 Mac 地址是否是自己,不是则丢弃,这一点很重要。

好了,那么到此为止我们也简单介绍了链路层的相关内容,关于物理层,其实没什么介绍的,就是 0、1 的电信号传输。

关于整个 OSI 五层模型,我们从上至下也已经完成了学习,下一篇将完整的看看 「www.baidu.com」之后,整个计算机网络发生的故事,其实有点标题党了,最后一篇才介绍完整的 HTTP 请求过程,见谅!


文章中的所有代码、图片、文件都云存储在我的 GitHub 上:

(https://github.com/SingleYam/overview_java)

666. 彩蛋

如果你对 Dubbo 感兴趣,欢迎加入我的知识星球一起交流。

知识星球

文章目录
  1. 1. 网络层
  2. 2. 链路层
  3. 3. 666. 彩蛋