[!info]
本文所涉及的工作方式,均仅代表其最原始与基础的实现,OSI七层模型的工作方式经过数十年的发展已经变得非常丰富、非常高效且稳定。
你重生了,重生在了某个喧嚣的午后。空气里有一股焦糊的味道,像是电路板被过载烧灼的气息。
你猛地睁开眼,天花板上一排闪烁的日光灯嗡嗡作响,脚边传来风扇电机的低鸣,混杂着纸带打印机嗒嗒嗒的节奏。只有桌上的日历正胸有成竹地告诉你,现在是20世纪80年代。
数据链路层
导师抱着一卷厚重的论文走进办公室。“实验室的第十八台计算机要正式启动了。”他面无表情地宣布着“你抓紧时间,把网络连接好。”
他将手里的文件重重放在桌上,转身离开,消失在了风扇的轰鸣中,只留下一脸懵逼的你在热浪中凌乱。
机房里已有的17台计算机,通过机器上预留的16个网络接口互相连接,如果想要向某台计算机发送信息,只需要在对应的网口将比特流发送出去即可,十分方便。
[!info]
以上设计是早期网络在物理层面上最原始的连接方式——点对点物理拓扑。
你穿过如蛛丝般盘根错节的线缆,随后愣在原地——网口只有16个,如果要再连接一台计算机,还得给原有的每台机器多加一个网口。加网口倒也不是什么大事,但这新增的17根网线,可是哪里都放不下了。
你灵机一动,一个大胆的想法在脑海中形成——如果改进一个集线器,将所有设备都连接进来,通过集线器转发比特流,是不是可以减少线缆和网口的需求呢?
说干就干,你更新了每台计算机的网卡,为它们分配了一个唯一的十六进制编号,例如0x01代表第一台计算机,0x02代表第二台……
同时,你发明了一个叫做集线器的东西,它具有18个网口,收到某个网口的数据包之后,便会向剩余的网口无差别地发送消息。例如,第一台计算机想要向第二台计算机发送数据,那么就通过0x01编号网口向集线器发送数据:
[发件人 0x01]
[发送给 0x02]
Hello world!
其他设备收到数据后,检查了自己网卡的唯一编号,都与0x02不符合,于是便丢弃了这个数据。只有二号计算机发现这是发送给自己的数据,于是如法炮制地进行回复,目标正是刚才的发件人——0x01。
[发件人 0x02]
[发送给 0x01]
I am here!
同样的,只有一号计算机检查发现,这是给自己的数据包。
你长舒了一口气,这下只需要18条网络线和1台集线器,就可以让这18台计算机相互通讯。如果以后还有机器需要新增,那就只需要给集线器增加一个网口,再多加一条网线即可。
[!info]
传统集线器实现的是物理层的经典网络拓扑设计——星型设计。
在这个阶段,集线器实际上并不理解数据的内容,只是忠实地进行放大和广播。
非常完美?
我看未必。向所有网口无差别地广播消息,意味着发送到每个网口的数据中,有90%以上都是需要丢弃的,孱弱的单芯网线和计算机处理能力很快就限制了数据的传输效率。
于是你又设计了一款新的集线器Plus,它具有18个网口。它的基本工作原理和集线器并无二致,但会从每个网口发来的数据包里认识发件人。随着时间的推移,集线器Plus就将更多网口和地址的对应关系一一记录了下来:
1号网口: 0x01
2号网口: 0x02
3号网口: 0x03
……
18号网口: 0x12
这时候,第一台计算机想要向第二台计算机发送数据,那么仍旧通过0x01编号网口向集线器Plus发送数据:
[发件人 0x01]
[发送给 0x02]
Hello world!
集线器Plus在自己的1号网口收到消息,通过查表发现,1号网口对应的编号是0x01,说明发送方的编号是0x01;消息头部说了它要发送给0x02,因此我应该向2号网口发送数据。
于是集线器Plus不再漫无目的地广播,而是将数据向2号网口发送。显然,2号网口正连接着正确的目标——二号计算机上,编号为0x02的网口。
2号计算机接收到消息,于是它通过自己的网口发送回复:
[发件人 0x02]
[发送给 0x01]
I am here!
集线器Plus按照相同的方式得知,这条数据来自0x02,应该从自己的1号网口发送给0x01。
于是,这个数据包从集线器Plus的1号网口,精准无误地向一号计算机的0x01网卡发送出去。这下,因为广播产生的无效的数据包基本被消灭殆尽,实验室的网络又恢复了通畅。
你想了想,集线器Plus的价值已经不再是简单的汇聚缆线,它能够识别数据包,并搭建数据交换的桥梁。那么不如给它取个新名字,就叫交换机(Switch)好了。
恭喜你,发明了数据链路层和交换机。
[!info]
现实中,这个“唯一编号”正是MAC地址——Media Access Control Address。MAC地址具有6个字节,即48位二进制位或12位十六进制位。形如
00:01:01:00:00:01。其中前24位由IEEE分配给硬件厂商,后24位由厂商自行分配给自己生产的设备。可用的MAC地址理论上有2^48(约281万亿)个。
网络层
导师对你的工作十分满意,随即又向你提出了一个问题:
对面实验楼也有十几台计算机,怎么让我们实验室的设备和它们联络呢?
你又陷入了沉思。把对面楼的计算机都连接到同一台交换机上吗?不可行。且不说要增加多少个网口,就是把那十几条网线拉过去,也是个不小的工程。
看着桌面上厚厚的信件,你突然灵光一闪——邮递员通过邮政编码,将不同的邮件整理起来,通过高速公路统一发往对应的总站,总站根据邮政编码,再将不同具体辖区的信件分拣出来,通过邮递员寄往各处。如果按照网络的拓扑,为网络空间里的设备也分配一个“邮政编码”,让这些具体目的地不同,但辖区相同的数据通过同一条高速公路上传输,基建效率不就大大提高了吗?
说干就干。很快,一套全新的设备——你管它叫“引导机”——就被你发明了出来,它可不同于集线器和交换机,更像是一个“有两个网口的计算机”。这两个网口分别有自己的MAC地址。你在对面楼的实验室里也部署了一台交换机,然后用两条线缆将两个实验室的交换机分别接在了这个引导机的两个网口上。
graph LR
subgraph 实验室A
A1[主机A1]
A2[主机A2]
A3[主机A3]
end
subgraph 实验室B
B1[主机B1]
B2[主机B2]
B3[主机B3]
end
A1 --- SWA[交换机A]
A2 --- SWA
A3 --- SWA
SWA --- R[引导机]
R --- SWB[交换机B]
SWB --- B1
SWB --- B2
SWB --- B3
同时,你设计了一套全新的规则,使用16位二进制数,为每个设备贴上了独一无二的“邮政编码”。
你告诉引导机,为接入的每一个MAC地址,都分配0001 **** **** ****地址。它可以表示为:
IP: 0001 0001 0000 0000
Mask: 1111 0000 0000 0000
其中,Mask(掩码)为1的部分,是固定的,不能动;而Mask为0的部分,可以让引导机自由分配。
当然,引导机保留了一个地址(网关/Gateway)给自己使用:0001 1110 1110 1110
[!info]
- 实际上,ipv4的地址是32位二进制,而ipv6则高达128位。这里使用16位只是为了简化说明。
- 拓扑图中的单向箭头请无视。mermaid渲染好像有点问题,实际上连接是没有固定方向性的。
- 家庭路由器常用的IP段(192.168..),实际上属于C类私有地址(192.168.0.0/16)
你顺手为每台计算机设计了全新的程序,接入网络时,程序会通过交换机发送一个广播包,询问“我刚加入网络,谁是这里的老大,我要一个地址!”
收到这个广播包的引导机马上回应:
“我是这里的老大,我的网关是0001 1110 1110 1110,我的子网掩码是1111 0000 0000 0000,你的地址是0001 0001 0001 0001”
程序再询问:
“0001 1110 1110 1110,请告诉我你的MAC地址”
引导机看到这是网口1发来的消息,便回复网口1的MAC地址:
“我的MAC地址是<引导机网口1的MAC地址>”
于是,两个实验室的每一台计算机,都获得了属于自己的地址代码。
你需要通过地址为0001 0001 0001 0001的计算机A1,向对面楼实验室的一台计算机B1发送信息。程序并不知道目标的MAC,但知道它的地址代码是0001 0002 0001 0001。于是,你让程序设计了一份新的网络层数据包:
[发件人 0001 0001 0001 0001]
[发送给 0001 0002 0001 0001]
Hello world!
当然,这份数据包还需要进一步包装,以通过数据链路层逻辑发送给引导机。
[发件人 <主机A1的MAC>]
[发送给 <引导机网口1的MAC地址>]
<网络层数据包>
根据上一章节你发明的数据链路层原理,这个数据包很快被发送到了引导机。引导机拆开包装后,解读到了以下内容:
[发件人 0001 0001 0001 0001]
[发送给 0001 0002 0001 0001]
Hello world!
引导机分析道:它的目标是0001 0002 0001 0001,正好是我管理的范围。让我查一下表,这个地址通过2号网口接入,它的MAC地址是……
于是引导机又进行了一次数据链路层包装,通过数据链路层逻辑,从2号网口发送给交换机B
[发件人 <引导机网口2的MAC地址>]
[发送给 <主机B1的MAC>]
<网络层数据包>
交换机B通过MAC地址,很快将数据包送达计算机B1的网口。
计算机B1收到数据包后,发现内容如下:
[发件人 0001 0001 0001 0001]
[发送给 0001 0002 0001 0001]
Hello world!
与计算机A1发送时一模一样[1]。
于是,计算机B1向发件人的地址发送了一份回复……
[发件人 0001 0002 0001 0001]
[发送给 0001 0001 0001 0001]
I am here!
毫无疑问,这个数据包遵循着相同的逻辑,通过同一条网络线准确地传输给了计算机A1。这样一来,不仅同一个实验室的所有计算机可以靠MAC和网络地址通讯,对于其他实验室的计算机,使用网络地址同样也可以实现通讯——只需要用一条网线,将实验室的交换机连接到引导机上就行。
这下是真·十分方便。
完成了这么多工作,该给它们取个好听点的名字了。
引导机?不好听。它负责在路径之间引导数据,干脆取个拗口的名字叫路由器吧。
网络地址?既然已经确定了框架,不如完善一下,叫做“网络协议地址”(Internet Protocol Address / IP Address)好了。
恭喜你,发明了网络层协议和路由器!
[!success] 一点吐槽
那我问你,MAC地址都知道要设计48位,为什么ipv4就那么小气只分配32位捏?
一些补充小知识
上文中我使用了两种方式表示网段,其中一种是IP+子网掩码,而另一种则写为(192.168.0.0/16)
实际上,这两种写法代表的实际意义是相同的。
如何用“子网掩码”的视角理解192.168.0.0/16呢?我们知道,ipv4实际上是32位二进制,而/16就代表着,其子网掩码的前16位网络号都是1,而剩余的部分自然就是0。固定的部分叫做网络位,而可以自由分配的部分叫做主机位。
因此,我们不难发现,192.168.0.0/16的二进制子网掩码写法是:
IP: 11000000 10101000 00000000 00000000 #192.168.0.0
Mask: 11111111 11111111 00000000 00000000 #255.255.0.0
举一反三,192.168.1.0/24自然可以被写成:
IP: 11000000 10101000 00000001 00000000 #192.168.1.0
Mask: 11111111 11111111 11111111 00000000 #255.255.255.0
需要注意的是,子网掩码为0的部分,IP段可以任意填0或者1,并不影响结果(也就是说192.168.0.0/16和192.168.1.1/16描述的其实是同一网段,只是前者以某个IP为参照)。你可以理解为,将子网范围内的任意IP与子网掩码按位做AND运算,得到的结果总是已知且固定的。
尽管IPv4通常被写为每8位一段的十进制点分形式,但子网掩码并不总是每位都是255或0(非整字节掩码)。其中最典型的就是B类私有地址(172.16.0.0-172.31.255.255)。按照上述方式,它应该写为:
IP: 10101100 00010000 00000000 00000000 #172.16.0.0
Mask: 11111111 11110000 00000000 00000000 #255.240.0.0
或者172.16.0.0/12
[!success]
对于/n子网而言,它的网络位(固定的部分)有n个,主机位自然有32-n个,一个网段所能表示的IP数量是2^(32-n)个。
不包括TTL等部分 ↩︎