网络分层的真实含义是什么?

长时间从事计算机网络相关的工作,我发现,计算机网络有一个显著的特点,就是这是一个不仅需要背诵,而且特别需要将原理烂熟于胸的学科。很多问题看起来懂了,但是就怕往细里问,一问就发现你懂得没有那么透彻。

我们上一节列了之后要讲的网络协议。这些协议本来没什么稀奇,每一本教科书都会讲,并且都要求你背下来。因为考试会考,面试会问。可以这么说,毕业了去找工作还答不出这类题目的,那你的笔试基本上也就挂了。

当你听到什么二层设备、三层设备、四层 LB 和七层 LB 中层的时候,是否有点一头雾水,不知道这些所谓的层,对应的各种协议具体要做什么“工作”?

1、这四个问题你真的懂了吗?

教科书或者老师往往会打一个十分不恰当的比喻:为什么网络要分层呀?因为不同的层次之间有不同的沟通方式,这个叫作协议。例如,一家公司也是分“层次”的,分总经理、经理、组长、员工。总经理之间有他们的沟通方式,经理和经理之间也有沟通方式,同理组长和员工。有没有听过类似的比喻?

那么第一个问题来了。请问经理在握手的时候,员工在干什么?

很多人听过 TCP 建立连接的三次握手协议,也会把它当知识点背诵。同理问你,TCP 在进行三次握手的时候,IP 层和 MAC 层对应都有什么操作呢?

除了上面这个不恰当的比喻,教科书还会列出每个层次所包含的协议,然后开始逐层地去讲这些协议。但是这些协议之间的关系呢?却很少有教科书会讲。学习第三层的时候会提到,IP 协议里面包含目标地址和源地址。第三层里往往还会学习路由协议。路由就像中转站,我们从原始地址 A 到目标地址 D,中间经过两个中转站 A->B->C->D,是通过路由转发的。

那么第二个问题来了

A 知道自己的下一个中转站是 B,那从 A 发出来的包,应该把 B 的 IP 地址放在哪里呢?B 知道自己的下一个中转站是 C,从 B 发出来的包,应该把 C 的 IP 地址放在哪里呢?如果放在 IP 协议中的目标地址,那包到了中转站,怎么知道最终的目的地址是 D 呢?

教科书不会通过场景化的例子,将网络包的生命周期讲出来,所以你就会很困惑,不知道这些协议实际的应用场景是什么。

再问你一个问题。你一定经常听说二层设备、三层设备。二层设备处理的通常是 MAC 层的东西。那我发送一个 HTTP 的包,是在第七层工作的,那是不是不需要经过二层设备?或者即便经过了,二层设备也不处理呢?或者换一种问法,二层设备处理的包里,有没有 HTTP 层的内容呢?

最终,我想问你一个综合的问题。从你的电脑,通过 SSH 登录到公有云主机里面,都需要经历哪些过程?或者说你打开一个电商网站,都需要经历哪些过程?说得越详细越好。实际情况可能是,很多人回答不上来。尽管对每一层都很熟悉,但是知识点却串不起来。

上面的这些问题,有的在这一节就会有一个解释,有的则会贯穿我们整个课程。好在后面一节中我会举一个贯穿的例子,将很多层的细节讲过后,你很容易就能把这些知识点串起来。

2、 网络为什么要分层?

这里我们先探讨第一个问题,网络为什么要分层?因为,是个复杂的程序都要分层。

理解计算机网络中的概念,一个很好的角度是,想象网络包就是一段 Buffer,或者一块内存,是有格式的。同时,想象自己是一个处理网络包的程序,而且这个程序可以跑在电脑上,可以跑在服务器上,可以跑在交换机上,也可以跑在路由器上。你想象自己有很多的网口,从某个口拿进一个网络包来,用自己的程序处理一下,再从另一个网口发送出去。

当然网络包的格式很复杂,这个程序也很复杂。复杂的程序都要分层,这是程序设计的要求。比如,复杂的电商还会分数据库层、缓存层、Compose 层、Controller 层和接入层,每一层专注做本层的事情。

3、程序是如何工作的?

我们可以简单地想象“你”这个程序的工作过程。

img

当一个网络包从一个网口经过的时候,你看到了,首先先看看要不要请进来,处理一把。有的网口配置了混杂模式,凡是经过的,全部拿进来。

  1. 拿进来以后,就要交给一段程序来处理。于是,你调用 process_layer2(buffer)。当然,这是一个假的函数。但是你明白其中的意思,知道肯定是有这么个函数的。那这个函数是干什么的呢?从 Buffer 中,摘掉二层的头,看一看,应该根据头里面的内容做什么操作。

  2. 假设你发现这个包的 MAC 地址和你的相符,那说明就是发给你的,于是需要调用 process_layer3(buffer)。这个时候,Buffer 里面往往就没有二层的头了,因为已经在上一个函数的处理过程中拿掉了,或者将开始的偏移量移动了一下。在这个函数里面,摘掉三层的头,看看到底是发送给自己的,还是希望自己转发出去的。

  3. 如何判断呢?如果 IP 地址不是自己的,那就应该转发出去;如果 IP 地址是自己的,那就是发给自己的。根据 IP 头里面的标示,拿掉三层的头,进行下一层的处理,到底是调用 process_tcp(buffer) 呢,还是调用 process_udp(buffer) 呢?

    假设这个地址是 TCP 的,则会调用 process_tcp(buffer)。这时候,Buffer 里面没有三层的头,就需要查看四层的头,看这是一个发起,还是一个应答,又或者是一个正常的数据包,然后分别由不同的逻辑进行处理。如果是发起或者应答,接下来可能要发送一个回复包;如果是一个正常的数据包,就需要交给上层了。

  4. 交给谁呢?是不是有 process_http(buffer) 函数呢?没有的,如果你是一个网络包处理程序,你不需要有 process_http(buffer),而是应该交给应用去处理。交给哪个应用呢?在四层的头里面有端口号,不同的应用监听不同的端口号。如果发现浏览器应用在监听这个端口,那你发给浏览器就行了。至于浏览器怎么处理,和你没有关系。浏览器自然是解析 HTML,显示出页面来。

电脑的主人看到页面很开心,就点了鼠标。点击鼠标的动作被浏览器捕获。浏览器知道,又要发起另一个 HTTP 请求了,于是使用端口号,将请求发给了你。

  1. 你应该调用 send_tcp(buffer)。不用说,Buffer 里面就是 HTTP 请求的内容。这个函数里面加一个 TCP 的头,记录下源端口号。浏览器会给你目的端口号,一般为 80 端口。
  2. 然后调用 send_layer3(buffer)。Buffer 里面已经有了 HTTP 的头和内容,以及 TCP 的头。在这个函数里面加一个 IP 的头,记录下源 IP 的地址和目标 IP 的地址。
  3. 然后调用 send_layer2(buffer)。Buffer 里面已经有了 HTTP 的头和内容、TCP 的头,以及 IP 的头。这个函数里面要加一下 MAC 的头,记录下源 MAC 地址,得到的就是本机器的 MAC 地址和目标的 MAC 地址。不过,这个还要看当前知道不知道,知道就直接加上;不知道的话,就要通过一定的协议处理过程,找到 MAC 地址。反正要填一个,不能空着。
  4. 万事俱备,只要 Buffer 里面的内容完整,就可以从网口发出去了,你作为一个程序的任务就算告一段落了。

4、层与层之间的关系

知道了这个过程之后,我们再来看一下原来困惑的问题。

首先是分层的比喻。所有不能表示出层层封装含义的比喻,都是不恰当的。总经理握手,不需要员工在吧,总经理之间谈什么,不需要员工参与吧,但是网络世界不是这样的。正确的应该是,总经理之间沟通的时候,经理将总经理放在自己兜里,然后组长把经理放自己兜里,员工把组长放自己兜里,像套娃娃一样。那员工直接沟通,不带上总经理,就不恰当了。

现实生活中,往往是员工说一句,组长补充两句,然后经理补充两句,最后总经理再补充两句。但是在网络世界,应该是总经理说话,经理补充两句,组长补充两句,员工再补充两句。

那 TCP 在三次握手的时候,IP 层和 MAC 层在做什么呢?

当然是 TCP 发送每一个消息,都会带着 IP 层和 MAC 层了。因为,TCP 每发送一个消息,IP 层和 MAC 层的所有机制都要运行一遍。而你只看到 TCP 三次握手了,其实,IP 层和 MAC 层为此也忙活好久了。这里要记住一点:只要是在网络上跑的包,都是完整的。可以有下层没上层,绝对不可能有上层没下层。所以,对 TCP 协议来说,三次握手也好,重试也好,只要想发出去包,就要有 IP 层和 MAC 层,不然是发不出去的。

经常有人会问这样一个问题,我都知道那台机器的 IP 地址了,直接发给他消息呗,要 MAC 地址干啥?这里的关键就是,没有 MAC 地址消息是发不出去的。

所以如果一个 HTTP 协议的包跑在网络上,它一定是完整的。无论这个包经过哪些设备,它都是完整的。所谓的二层设备、三层设备,都是这些设备上跑的程序不同而已。一个 HTTP 协议的包经过一个二层设备,二层设备收进去的是整个网络包。这里面 HTTP、TCP、 IP、 MAC 都有。什么叫二层设备呀,就是只把 MAC 头摘下来,看看到底是丢弃、转发,还是自己留着。那什么叫三层设备呢?就是把 MAC 头摘下来之后,再把 IP 头摘下来,看看到底是丢弃、转发,还是自己留着。

5、总结

总结一下今天的内容,理解网络协议的工作模式,有两个小窍门:

  • 始终想象自己是一个处理网络包的程序:如何拿到网络包,如何根据规则进行处理,如何发出去;
  • 始终牢记一个原则:只要是在网络上跑的包,都是完整的。可以有下层没上层,绝对不可能有上层没下层。

6、思考问题?

1、如果你也觉得总经理和员工的比喻不恰当,你有更恰当的比喻吗?

答案:

(1) 很像发快递的过程(http,应用层),你向顺丰下单(第一次请求),顺丰接单(应答),你向取件小伙联系(回应应答),你将消息放进盒子里(开始封装请求,会话层),快递员封装一层盒子贴上快递单带回快递点(传输层),到快递点检查是否区域快件(网络层),将快件交给运输车(链路层),各个快递转运中心(物理层),转运输车(链路层),达到区域分发(网络层),网点派送(传输层),快递员方面签收(会话层),拆开检查(表示层),收到快递(应用层)。

2、要想学习网络协议,IP 这个概念是最最基本的,那你知道如何查看 IP 地址吗?

答案:

(1) 在 Windows 上是 ipconfig,在 Linux 上是 ifconfig (net-tools)

(2) ip addr (iproute2)

3、为什么要分层?

答案:

其实这是一个架构设计的通用问题,不仅仅是网络协议的问题。一旦涉及到复杂的逻辑,或者软件需求需要经常变动,一般都会通过分层来解决问题。

假如我们将所有的代码都写在一起,但是产品经理突然想调整一下界面,这背后的业务逻辑变不变,那要不要一起修改呢?所以会拆成两层,把 UI 层从业务逻辑中分离出来,调用 API 来进行组合。API 不变,仅仅界面变,是不是就不影响后台的代码了?

为什么要把一些原子的 API 放在基础服务层呢?将数据库、缓存、搜索引擎等,屏蔽到基础服务层以下,基础服务层之上的组合逻辑层、API 层都只能调用基础服务层的 API,不能直接访问数据库。比如我们要将 Oracle 切换成 MySQL。MySQL 有一个库,分库分表成为 4 个库。难道所有的代码都要修改吗?当然只要把基础服务层屏蔽,提供一致的接口就可以了。

网络协议也是这样的。有的想基于 TCP,自己不操心就能够保证到达;有的想自己实现可靠通信,不基于 TCP,而使用 UDP。一旦分了层就好办了,定制化后要依赖于下一层的接口,只要实现自己的逻辑就可以了。如果 TCP 的实现将所有的逻辑耦合在了整个七层,不用 TCP 的可靠传输机制都没有办法。

4、层级之间真实的调用方式是什么样的?

答案:

如果文中是一个逻辑图,这个问题其实已经到实现层面上来了,需要看 TCP/IP 的协议栈代码了。这里首先推荐一本书《深入理解 Linux 网络技术内幕》。

其实下层的协议知道上层协议的,因为在每一层的包头里面,都会有上一层是哪个协议的标识,所以不是一个回调函数,每一层的处理函数都会在操作系统启动的时候,注册到内核的一个数据结构里面,但是到某一层的时候,是通过判断到底是哪一层的哪一个协议,然后去找相应的处理函数去调用。

调用的大致过程我这里再讲一下。由于 TCP 比较复杂,我们以 UDP 为例子,其实发送的包就是一个 sk_buff 结构。这个在Socket那一节讲过。

int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)

接着,UDP 层会调用 IP 层的函数。

int ip_send_skb(struct net *net, struct sk_buff *skb)

然后,IP 层通过路由判断,最终将包发给下一层。

int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)

发送的时候,要进行 ARP。如果有 MAC,则调用二层的函数,neigh 其实就是邻居系统,是二层的意思。

int neigh_output(struct neighbour *n, struct sk_buff *skb)

接收的时候,会调用这里的接收函数。

int netif_receive_skb(struct sk_buff *skb)

这个函数会根据是 ARP 或者 IP 等,选择调用不同的函数。如果是 IP 协议的话,就调用这里的函数。

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)

这里也有路由判断。如果是本地的,则继续往上提交这个结构。

int ip_local_deliver(struct sk_buff *skb)

接着,还是根据 IP 头里面的协议号,来判断是什么协议,从而调用什么函数。下面这个是对 UDP 的调用。

int udp_rcv(struct sk_buff *skb)

5、什么情况下会有下层没上层?

答案:

有时候我们自己写应用的时候,不一定是直接调用应用层协议的接口,例如 HTTP 等,而是自己写 Socket 编程,来约定应用层的协议。再如,ping 也是一个应用,但是它没有用传输层的协议,而是用了 ICMP 的协议。


   转载规则


《网络分层的真实含义是什么?》 bill 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
复杂度分析 复杂度分析
1、如何分析、统计算法的执行效率和资源消耗?我们都知道,数据结构和算法本身解决的是“快”和“省”的问题,即如何让代码运行得更快,如何让代码更省存储空间。所以,执行效率是算法一个非常重要的考量指标。那如何来衡量你编写的算法代码的执行效率呢?(
2020-03-11
下一篇 
git rebase详解 git rebase详解
git rebase 处理流程bill@ars:~/go/src/github.com/filecoin-project/lotus-office$ git branch master master-1211 master-a
2020-03-11
  目录