网络是怎样诞生的?——简介 OSI 七层模型&TCP/IP 四(五)层模型

最后更新于 2025-08-21 17:05:49
作者
分类 科技·工程

闲话

INFO

前几天刷某 CSP 初赛模拟题看见的,当时考了这么一道题:

【答案在文章后面】以下属于 TCP/IP 协议族中应用层协议的是:A.UDP B.ARP C.SMTP D.ICMP

加上学校信息课 OI 一点不学净学一些网络相关的导致我期末信息课挂分成 OI 界的耻辱了,故而整理一下。

考虑到网络和工程相关,和 OI 是弱相关,因此本文投“科技·工程”而非“算法·理论”。

下文中会混合 OSI 七层模型、TCP/IP 四(五)层模型和图论等多种内容,可能不是很清晰。如有错误欢迎指出!(但是也因此会掉全站推荐,推荐大家前往保存站阅读,后续我也会同步到我自己的博客上。)

本文为万字长文,配了不少图。请耐心阅读。

简介

OSI 模型,即开放式通信系统互联参考模型(Open System Interconnection Reference Model),是国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连为网络的标准框架。——维基百科

说人话:不同机器之间协作显然需要共同的标准,称为“协议”。把数据传输的协议分为七层,即 OSI 七层模型。

现在我们从头来看看 OSI 是怎么搞出来的。

物理层

话说你有一台电脑,代号 A。这天你闲得无聊,想和隔壁另一台机器 B 发点东西,怎么办呢?

嗨,连根数据线不就完了!


这个部分我们称为物理层。物理层就是现实世界的、物理意义上存在的,比如光纤、数据线等都属于这一层。

数据链路层

后来这里扩建成了一个机房,有很多的机器,该如何通信呢?

一样的,继续连线嘛。

但是这样子也有问题。假如一台机房有 $100$ 台机器,那么就需要 $C^{2}_{100} = 4950$ 根数据线!每台机器还要开 $99$ 个网口!显然这不可能,将近五千根线谁受得了啊,而且很乱。

可以看到,7 台机器的图已经看不太清了

于是我们引入了一台新的机器——集线器!这台机器的功能很简单:我们把机房的机器全部和它连线(连成了一个菊花),这样无论和谁发信息都只需要发给集线器,集线器再转发给其它所有机器就可以了。

这样的好处是每台机器只需要和集线器连线即可,只需要 $1$ 个端口 $100$ 条数据线。

现在是不是清爽多了

但是这样子也有问题。之前如果你只想和 B 发信息那么直接在线上传输就好了,现在发什么都需要集线器,而集线器会无脑转发给所有机器,那万一有点小秘密不就都被泄露了……

为了解决这个问题,我们可以对集线器做一点升级改造。首先在我们发送的每条数据前面加上自己的地址和对方的地址再发出去,这样集线器就可以识别你要发给谁,然后就可以转给它了(啥?你问为什么发件人也要带上?你们收信都不回的吗?)。


这个升级之后的集线器就是大名鼎鼎的交换机。如果你理解了交换机的概念,那么恭喜你进入了数据链路层

这里使用的地址一般是媒体存取控制位址Media Access Control Address,MAC 地址,注意和苹果 Mac 没有关系)。它的特点在于出厂就会烧录每台机器(其实是网卡)唯一的 MAC 地址,全球唯一。而 IP 地址是每个网络自行分配(离开了这个网络就变了),可能会冲突,而且也可以被修改,故而不用。

交换机如果遇到了 IP 地址也要把 IP 转换成 MAC 地址才会发送。而如果是一个不认识的 MAC 地址它就会发扬集线器精神给每台机器都发一遍。这个流程运用的就是地址解析协议(Address Resolution Protocol,ARP)

Notes

网络层

后来我们和隔壁机房合作,现在也要建立通信了。有了刚才的经验,我们知道现在不可能把隔壁机房的机器连到这里的交换机上,怎么办呢?

我们在隔壁机房也建立一个交换机嘛,然后再把这两个交换机之间加一个集线器或另一个交换机不就完了?

不对,我们的数据都是记录的 MAC 地址,总的交换机不可能记录每台机器的 MAC 地址啊(毕竟是两个独立的网络)。两个机房还好,一大堆机房那就……交换机要玉玉了 >_<。

于是我们发明了 IP 地址,分为网络地址主机地址两个部分。同一网络下网络地址是相同的,而每台机器的主机地址不同。

具体来说,我们定义一个数值(子网掩码),然后转成二进制,与 IP 地址进行按位运算,这样就得到了网络地址。只要网络地址相同,我们就认为它们在同一个网络中。

有了以上准备,我们就发明了大名鼎鼎的路由器

路由器和交换机类似,但是它基于 IP 地址工作。如果我们发现传过来的数据包属于这个网络就可以丢给交换机下传,如果是要让自己做中转就接着传下去,否则就不管它,直接把数据包扔了(对,就是这么简单粗暴,不过你留着这个包除了占空间以外也没啥用)。

而发送的时候就会检查自己的路由表,看一下它属于哪一个网络。如果这里没有就启动选路算法找到最短路线传过去。

新的传输路线


这个部分东西有点多,需要好好消化一下。

首先为了区分网络我们发明了 IP 地址和子网掩码。子网掩码一般使用 CIDR 斜杠表示法,例如子网掩码为 255.255.255.0/24 就是表示二进制下前 $24$ 位表示网络地址。当然,你也可以更改,如 255.255.255.128 对应的 CIDR 就是 255.255.255.128/25,这样就划分了上面网络的子网。特别的,我们有 A、B、C、D、E 五类 IP 地址,其中 D、E 分别是多路广播和保留地址一般不用,另外 127.0.0.1 是本机地址。剩余 A、B、C 类如表:

类别 CIDR 对应掩码 地址个数
A /8 255.0.0.0/8 16777214
B /16 255.255.0.0/16 65534
C /24 255.255.255.0/24 254

一般情况下家用的网络都是 C 类,如果你需要更多(不会吧?)那么也可以申请。

子网掩码 xxx.xxx.xxx.xxx/i 的原理其实是保留前 $i$ 个二进制位,前 $i$ 位都是 $1$,与 IP 地址的对应位进行与运算后不变;后面都是 $0$,全部都变成 $0$ 了,这样可以比较。最终一个网络可用的地址也就是 $2^{32 - i}$ 个。

转换关系表

注:上图中具体的数字是包含保留地址的,上面的表则扣除了保留地址(全 0 和全 1),因此同一类网络中,表中比图中要少 $2$ 个地址。具体计算见 https://www.cnblogs.com/zhezh/p/3773431.html

当然上面的概念基于 Internet Protocol version 4,即 IPv4,二进制下 $32$ 位,有 $2^{32}$ 个地址。由于现在设备越来越多,产生了 IPv4 地址枯竭的问题(不开 long long 见____)。于是又发明了 IPv6(别找了,我帮你们看了,IPv5 根本就没有成为正式广泛使用的协议),有 $2^{128}$ 个地址,达到了 __int128 的级别,号称“可以为全世界的每一粒沙子编上一个地址”,而且安全性和速度也有一定提升。

但是 IPv6 和 IPv4 不同,一般采用 $16$ 进制表示,$4$ 个 $16$ 进制位($16$ 个 $2$ 进制位)一组,每组之间用冒号分开(因此成为“冒分十六进制”,相应的,一般 IPv4 称为“点分十进制”)。特别的,如果一组的值是 $0$ 那么可以省略不写直接接到下一个冒号(不过貌似有些老爷机器不一定支持,另外冒号禁止省略)。

INFO

路由器会有一张路由表(下面的图经过简化了,实际上是 Trie+ 链表)。

路由表

接收数据包的时候会检查这是不是发给自己的,如果是就可以丢给交换机了,不是就看看是不是要在这里做中转发给别的路由器之类的机器的。

如果是发送数据包呢?

发送情况 1

如果目标网络有记录(比如图中 PC1 到 PC3)那么查表直接发过去就好了。

发送情况 2

如果不在表中(例如 PC1 发给 PC25)就会找默认网关,默认网关一般会有一张更大的路由表,以此类推。特别的,有几台特殊的服务器:

  • 地区 ISP 服务器
  • 主干网 ISP 服务器
  • NSP 服务器
  • NAP 节点

ISP(Internet Service Provider)和路由器连接你的路由器才能接入 Internet,常见的 ISP 就是移动、联通、电信三大运营商。ISP 可以从 NSP(Network Service Provider)批量买入带宽接入更高级的主干网。每个 NSP 都要接入至少三个 NAP 节点,你的请求可以通过 NAP 切换到不同的主干网。

但是好像哪里有问题?

你输入的不是 luogu.com.cn 吗,你咋知道洛谷服务器 IP 的?

黑进 kkk 账户看见的(bushi)。

也不是不刑,但是我们要有请 DNS(Domain Name System) 出场!

。。。

好吧 DNS 协议属于应用层因此鸽到下一篇再讲。光速逃。

INFO

ps:路由表的链路算法分成静态和动态的两种,但是静态不适于网络的变化,而动态的两种算法分别是链路状态(LS)算法,代表为 Dijkstra;以及距离向量(DV)算法,代表 Bellman-Ford 和优化后还没死透的 SPFA。

对,就是最短路。不过也很好理解,发送数据不就是要传输时间短嘛。路由表的过程就是最短路获得路径后递归转发。

之所以要花这么大篇幅讲路由器是因为这就是 OSI 七层模型的第三层——网络层,代表就是 IP 协议。网络层就是过路由选择算法,为数据包选择最优路径,另外数据包也是在这里被转换出来的。

另外,上面当中路由器和交换机的功能在家用路由器中一般被集成到一起了。

传输层

后来我们又发现了新的问题。有的时候我们可能需要传送像 NOI Linux 的镜像 之类的大文件,然而数据线由于折腾了半天有的已经老化了,传不了,丢件了……

几个 GB 的重要大文件就这么挂了,损失惨重。这个事情告诉我们我们必须要有一个处理丢件的措施。怎么办呢?

鸡蛋不能放在同一个篮子里。

对,我们要把数据拆分成数据包,每个包单独传输,这样就算真的有包寄掉了或者出 bug 了只需要重传那一个包就可以了。而且这样还可以让不同的包分头行动,有一个线路寄了其它线路不影响。分包是我们后续流程的基石。

但是随着机器越来越多,我们又发现了新的问题。有的机器是 i9 少爷机跑的飞快,有的机器是 Intel N100 的“高端” CPU,显然让它们处理同样的包大小很不公平。另外,不同的包走不同的线路到达的时间也不一样,谁先谁后不清楚,怎么办呢?还有,一台机器可能会有很多程序需要网络,该如何区分这个包是给哪个程序的呢?

不搞了,跑路。

首先我们给每个包编个号,这样就解决了顺序问题。然后我们规定对面机器每收到一个包就要返回一个编号 $n$,表示 $1 \sim n - 1$ 的所有包都收到了,现在需要包 $n$,我们这里接收到返回来的信号后继续发下一个包。这样如果对面不发出信号我们就知道包出 bug 了要重新传(这也就是为什么一定要设置一个超时时长)。

下一步我们引出一个概念叫“端口”,不同的程序使用不同的端口,输入和发出都需要端口。如果该端口已经被其它程序占用了那服务就没法搞了。一般一个机器有 $0 \sim 65535$ 共 $65536$ 个端口,不过鉴于一般普通机子 $300$ 多个进程就卡的要死,所以一般不会出现端口不够的问题。这样我们在包头上加上端口号就可以区分了。

接着我们来思考如何解决机器配置不同的问题。显然我们是要求每一个机器的承载能力以及网络状况的最小值。怎么求呢?我们发完(接收到了包正常的信号)就不用管了,同时每个机器的承载能力基本不变,而且一个包不用管之后就可以处理下一个包了……哎这不是滑动窗口吗?是的就是一个队列维护。而且由于这就是负责传输的协议,因此即使不知道什么原因窗口大小改变了也能通知到(事实上每一个返回的包都会带上新的窗口信息)。

啥?网络环境怎么办?三种方法:

  1. 慢启动。就是利用倍增(啊对,就是 1、2、4、8 那个倍增)不断试探网络环境的上限。
  2. 如果窗口大小大于之前试探出来的慢启动阈值就会改用拥塞避免模式,线性缓慢尝试。
  3. 快速重传,这种情况下不需要等到超时就可以提前发出包了。

快速重传比较复杂,这里简单讲一下。举个例子:

  1. 发送方发了 1、2、3、4、5 一共 5 个包。
  2. 接收方接收到了包 1,返回 2,表示需要包 2。
  3. 接收方接收到了包 2,返回 3,表示需要包 3。
  4. 接收方接收到了包 4,此时把 4 放入缓冲区,仍然返回 3。
  5. 接收方接收到了包 5,此时把 5 放入缓冲区,仍然返回 3。
  6. 接收方终于收到了包 3,此时把 4 和 5 从缓冲区放出来,返回 6。

这里 3 被返回了三次,其中第一次是正常接收,而后两次则是出现了乱序的情况。而如果乱序情况再次出现(如后面的包 6 都到了 3 还没到)那么发送发就会认为包 3 已经祭了就会重新发一遍,这就是快速重传,不需要等到超时。至于为什么是三次,那就是一个折中比较好的选择啦~

好,完事儿了,我们可以开始通信啦!

吗?不对,我们好像还有一个很重要的问题没解决——咋建立连接啊?

关于我传了俩小时文件结果对面还没开机这件事。

这就要引出大名鼎鼎的三次握手了!

首先我们规定一个包长什么样:

这里会有以下我们提到过的信息,也会有些其他的:

  • 发送方的 MAC 地址
  • 接收方 MAC 地址
  • 发送方 IP 地址
  • 接收方 IP 地址
  • 目标端口
  • 源端口

特别的,在路由器发送的时候会把你的主机 IP 改成路由器的外网 IP,还会把端口号给改了,对外完全隐藏你的主机。这个操作称为 NAT,是一种网络隐蔽技术,可以提高安全性、共享网络地址。

然后我们定义几个特殊的信号:

ACK:确认数据包头部的确认序列号有意义。

SYN:特殊报文,建立连接时使用

FIN:数据发送完毕,在关闭连接时使用。

最开始,客户端和服务端都是 “CLOSE” 状态没有任何连接。但是服务器会主动监听某个端口(对于 Web 一般是 80 和 443 两个端口,后面一篇会讲为什么)。

发送请求,此时客户端会生成一个 client_isn 的序列号,标记位是 SYN 表示要连接了,自己进入 SYN_SEND 状态。

服务端接收到之后也初始化序列号 server_isn,在确认号的位置填上 client_isn + 1 表示确认收到了客户端发过来的序列号,同时把 ACK 和 SYN 位置都置为 $1$,发送给客户端,自己变成 SYN_REVD 状态。

然后客户端也要告诉服务端它收到了这个信号,于是在确认号填上 server_isn + 1,ACK 状态为 1。

发完,客户端进入 ESTABLISHED 状态,服务端同时进入 ESTABLISHED 状态。

如果只有两次握手,那么服务端不能保证自己发的信号能被客户端接收(这是在第三次握手告诉服务端的),因此不行。

序列号随机生成可以保证安全性,同时也可以过滤掉不属于这里的信息。

如果前两个包丢了超时重传就可以了,第三个包需要分类讨论。

情况一:没有任何数据发送。那就直接超时重传。

情况二:客户端要发数据了,服务端接到了 ACK 和数据包,那就自然 ESTABLISHED 接收数据包。

情况三:服务端要发数据了但发不出去那就一直超时重传。

一张图总结就是:

在通信完成之后要释放端口当然就需要断开连接,也就是四次挥手

双方都有权利断开连接,以客户端为例:

首先客户端发送 FIN 的信号,进入 FIN_WAIT_1 状态。服务端回复 ACK 信号,进入 CLOSE_WAIT 状态。客户端收到 ACK 信号,进入 FIN_WAIT_2 状态

等服务端确认真的没活了都发完了就再次发送 FIN 信号,进入 LAST_ACK 状态,客户端收到 FIN 回应 ACK 之后进入 TIME_WAIT 状态,服务收到 ACK 信号之后就 CLOSE 了,客户端在 TIME_WAIT 等到 2MSL 个时间之后也就 CLOSE 了。

其中这个 MSL 是报文在网络上存在的最长时间,一般是 30 秒、1 分钟或 2 分钟,主要是担心最后一个 ACK 信号服务端没收到那就会超时重传,根本不能关闭连接,而且这样还能保证新建连接的时候老的连接的数据已经消失了。在 TIME_WAIT 状态下端口仍然被占用,因此时间不能太长。

那为什么要四次呢?因为发完 FIN 信号后客户端不发送了但是还能接收的,而服务端收到的时候也可能正好还有数据要传,因此先回一个 ACK 信号,后面真的没东西了才发 FIN 表示可以关了,一来一回一共四次。

我们发现建立连接的过程无非就是交换双方的状态,连接本身也只是维持 ESTABLISHED 这么个状态而已。


这个分包、传输、滑窗、握手、挥手……的过程就是 TCP 协议规定的。

这里花了大量篇幅讲 TCP 的三次握手和四次挥手,因为这就是网络传输的核心——传输层(废话)。

TCP(Transmission Control Protocol,传输控制协议)UDP(User Datagram Protocol,用户数据报协议) 协议是传输层的主要协议之一,UDP 协议比较简单,没有解决数据包重发等问题,增加的功能不多,因此速度也要比 TCP 快很多,但可靠性也会下降,一般适用于视频直播等场合,丢个一帧两帧的看不出来,但是卡是真的看得出来。

而 TCP 提供可靠的交付服务,出 bug 了就重传。典型的应用比如你的shi 山代码传给你谷的时候丢了哪怕一个字符也是不可接受的。

网络层、数据链路层只负责传输,但不能解决传错了怎么办、传乱了怎么办、传丢了怎么办……这些都是 TCP 协议负责的。

前四层负责对实际的数据传输,合称为“传输流层”。

后面的剧情可能会脱离主线(?),时间交给应用层,那就是下一篇的东西了。

总结

先上一张图镇楼:

开头的题也可以解决了。

查图得 ARP 是数据链路层的,UDP 是传输层的,ICMP 是网络层的,而 SMTP 是做邮件的属于应用层,故选 C。

另外,关于 TCP/IP 五层模型,就是把没讲到的会话层、表示层和应用层合并成应用层。四层模型则是将网络层改名网际层(其实也可以不改,反正差不多),同时将数据链路层和物理层合并为“网络接口层”。

本文讲到了很多协议,但协议本身都很复杂,这里都是简化的版本,也可能会有不符协议之处,希望大家指正。

更新记录

2025/08/12:新建文件。

2025/08/14:初稿过审全站推荐。

upd on 2025/08/15:更新图片并修复了一些错别字等 bug;修正 TCP 拥塞控制部分的错误表述。自动重新交审。

upd on 2025/08/16:为子网掩码部分图文矛盾增加说明。其实都是对的。然而还没有审核。

upd on 2025/08/19:润色一些文字,修正对 IPv5 的错误表述。然而新一周的管理都上任了仍然没有审核。

参考资料/推荐阅读

排名不分先后。文中部分图片来自这些资料,这里一并表示感谢。

写给管理员:直接复制的原文标题,因此没加空格,请无视这个 bug 并过审。