大家好!我是大聪明-PLUS!

网上有大量关于基于 TCP/IP 协议栈的网络如何运作以及如何编写具有网络功能的计算机程序的资料。在讨论计算机网络时,人们通常会深入研究通过网络传输的数据的物理基础和结构,而网络编程主要关注互联网套接字。
但在学习和研究时,你希望做更多的事情,例如,尝试使用网络协议套件。许多网络协议是在操作系统内核中实现的,更改它们可能具有挑战性,因为这需要编写操作系统驱动程序的技能。但是,使用专门的库可以让你在用户空间的底层使用协议。
在撰写本文时,我编写了一个小应用程序,它将作为理解计算机网络和 TCP/IP 协议套件的起点。你可以尝试使用该应用程序来获得更多知识。
该应用程序简单易懂,我希望它能帮助你学习本文的内容。毕竟,初次成功的喜悦会激励你花更多时间研究这个主题。
本文介绍了我认为任何有计算机网络经验的程序员都应该了解的最重要的概念。因此,其中肯定包含一些理论背景。
内容
计算机网络简介
▍计算机网络
计算机网络是一组相互交互并共享资源的计算设备。网络的概念与图的概念相似。网络也由一组节点和一组链接组成。网络与图的区别在于,节点是有意义的实体(在本例中是计算设备),而链接代表这些设备之间的连接。
▍ 本地和全球计算机网络
根据覆盖范围,计算机网络包括:
- 个人------个人局域网(PAN)。
- 本地 --- 局域网 (LAN)。
- 城市------城域网(MAN)。
- 全球------广域网(WAN)。
连接到智能手机的各种传感器构成了 PAN。连接到家庭路由器的设备组成的计算机网络是 LAN,城市中 ISP 用户的网络是 MAN,而 ISP 提供的整个互联网连接是 WAN。
▍ 网络模型
网络模型是指标准化网络交互的概念框架,包括关键术语以及网络组件的用途和功能。网络模型将网络组件及其功能划分为多个层。网络模型的每一层都有特定的用途和功能。
目前,两种网络模型应用最为广泛:七层 OSI 模型和四层 TCP/IP 模型。
下图展示了这两个模型及其相互关系。

在大多数情况下,您将使用 TCP/IP 网络模型,但历史上,层编号也沿用了 OSI 网络模型。例如,当遇到术语"第 2 层"或"L2"时,它指的是 TCP/IP 模型的第 1 层(数据链路层)。
这种分层划分使得每一层的技术能够独立于其他层进行发展。例如,由于光纤的广泛应用,互联网速度提高了数十倍甚至数百倍,而互联网仍然基于 IP 协议。
▍ TCP/IP 模型
大多数当前的互联网标准和 TCP/IP 协议都受征求意见稿 (RFC) 文档的约束。计算机网络教科书旨在解释 TCP/IP 模型,但为了更准确地理解这些概念,最好参考 RFC。
详细介绍了 TCP/IP 网络模型。其他 RFC 也对该模型进行了解释和扩展,但我认为这两个 RFC 足以帮助您理解基础知识。 让我们重点介绍 TCP/IP 模型的核心概念:
- 主持人;
- 信息;
- IP数据报;
- 塑料袋;
- 框架;
- IP地址;
- MAC地址;
- TCP 段;
- UDP数据报;
- 最大传输单元。
为了避免让您对枯燥的定义感到厌烦,我就不提供这些定义,而只是简单地用上面的术语来解释 TCP/IP 网络的运行原理。
▍ TCP/IP 网络的工作原理
IP 网络由一组互连的主机组成。主机之间通过中继设备(路由器和交换机)直接或间接连接。
主机使用接口与网络发送和接收消息。物理接口发送和接收帧,而逻辑接口发送和接收 IP 数据包。物理接口由 MAC 地址标识,逻辑接口由 IP 地址标识。
传输的消息是 UDP 数据报或 TCP 数据段。该消息包含报头和有效负载。在 IP 网络中传输消息时,消息被放置在 IP 数据报中。特定的物理接口允许数据以具有特定最大传输单元 (MTU) 的块形式传输。如果 IP 数据报大小超过 MTU,则会对其进行分片并创建多个 IP 数据包;否则,整个 IP 数据报只会创建一个 IP 数据包。IP数据包会根据主机的路由表转发到选定的逻辑接口。
逻辑接口本身不能直接传输 IP 数据包;它使用物理接口。物理接口以帧的形式传输数据。帧包含报头和有效负载。帧头指定了接收方的 MAC 地址、发送方的 MAC 地址以及有效载荷数据所属的协议(以太网类型)。发送方的地址是已知的;它是发送主机接口的 MAC 地址。对于 IPv4 协议,以太网类型 = 0x0800。
物理接口的地址是通过向广播域发送 ARP 消息来确定的。该 ARP 消息封装在以太网类型 = 0x0806(ARP)的帧中。该消息指定了发送方的 MAC 地址、接收方的广播 MAC 地址以及目标 IP 地址。物理接口分配了此 IP 地址的主机会使用该物理接口的 MAC 地址进行回复。为了避免每次都发送 ARP 消息,IP 地址到 MAC 地址的映射存储在主机的缓存中。
当帧传输到另一个网络接口后,会从中提取 IP 数据包内容,如果主机逻辑接口的 IP 地址与目标 IP 地址匹配,则将其组装成 IP 数据报。从 IP 数据报中提取出 TCP 段或 UDP 数据报。实际数据从这些段中提取出来并传递给操作系统进程,操作系统进程随后会了解如何处理这些数据。
否则,IP 数据包要么被拒绝,要么根据主机的路由表进行转发。转发后,它会再次传输到逻辑接口,在那里被打包成帧并发送出去。
这只是一个简化的描述,因为我没有深入研究虚拟网络接口、虚拟专用网络、PPP 连接,或者 TCP 和 UDP 网络传输协议的工作原理。
▍ 寻址
寻址允许您指定数据的来源和目标。对于 L2 层,接收方和发送方通过 MAC 地址标识;对于 L3 层,通过 IP 地址标识;对于 L4 层,通过端口标识。
关于 MAC 寻址,您只需要知道在大多数情况下,一个物理接口具有一个由 6 个字节组成的唯一 MAC 地址即可。
至于 IP 寻址,事情会更加有趣。首先,IP 地址有 2^32 个,但有效的主机 IP 地址数量较少,全局地址(互联网上可见的 IP 地址)甚至更少。让我尝试解释一下为什么会这样。
互联网被设计为一组互连的计算机网络,其中的主机相互交互。
相同的地址空间用于标识网络和主机接口。这是如何实现的?
每个 IP 地址都是一个 32 位的序列。IP 地址的前 n 位包含有关该 IP 地址所属网络的信息,其余位是该网络内唯一的地址。但是,所有位为 0 或所有位为 1 的地址具有特殊含义。如果所有位均为 0,则为网络地址;如果所有位均为 1,则为广播地址。IP
地址的前几位包含网络信息,由网络掩码决定。如果将 IP 地址与网络掩码进行按位"与"运算,则可获得网络标识符。如果将 IP 地址与反转的网络掩码进行按位"与"运算,则可获得网络中唯一的 IP 地址。为了更轻松地表示 IP 地址的相关信息以及 IP 地址的哪一部分用于标识网络,我们使用了 CIDR 表示法。
例如,192.168.0.0/24 表示:网络标识符为 192.168.0.0,前 24 位用于标识网络,后 8 位用于对主机进行编码。一个网络上的最大主机数为 254(0 表示网络地址,255 表示广播地址)。
但这还不是全部。并非所有网络都能在网络上可见,有些网络还用于特殊用途。
我们可以看到,互联网上主机的 IP 地址数量并不是很多,因此正在向 IPv6 寻址过渡,其中 IP 地址的数量要大得多。
▍ 网段、子网、广播域、地址类型
在考虑网络时,了解其实现的层次至关重要。如果网络位于数据链路层,则由具有 MAC 地址的设备组成。如果网络位于网络层,则由具有 IP 地址的主机组成。
为了简化管理,网络在数据链路层被划分为多个段,在网络层被划分为多个子网。网段和子网都构成一个广播域。子网有时也称为段,但重要的是要理解这指的是第 3 层级别的网段。
网络设备或主机可以拥有以下类型的地址:
- 个体地址(单播地址);
- 广播地址;
- 多播地址。
单播地址是网段或本地(或全局)网络中的唯一地址。
广播地址由所有具有 MAC 地址的网络设备或子网中的所有主机共享。发送到广播地址的消息将被所有节点接收。
消息只能发送给对其感兴趣的节点。这可以通过使用多播地址来实现。然而,这是一个单独的主题,需要单独更详细的解释。
▍ IP网络中的路由
典型网络用户的路由流程如下。当需要将 IP 数据包发送到特定地址时,每次都会检查主机的路由表,并据此决定是将其发送到发送主机网络内的主机,还是转发到特定主机(路由器、网关),然后由后者决定如何处理数据包。在本地家庭网络中,此网关通常是 WiFi 路由器。WiFi
路由器具有多种功能,实际上由多个设备组成。WiFi 路由器的一项重要功能是网络地址转换 (NAT),它允许本地网络上的设备访问互联网。这种访问虽然受到一定限制,但对大多数用户来说已经足够了。NAT
的本质是,位于 NAT 后面的主机在全球网络中显示为具有与 NAT 外部地址相同 IP 地址的主机,从而可以更有效地利用全局 IP 地址。关于 NAT 的详细描述,即使不是一本书,也需要一篇单独的文章。
▍校验和计算
通过网络传输数据时,可能会出现各种情况,接收的数据可能与发送的数据不同。为了检测这种情况,校验和会随数据一起传输。通过在接收端计算此校验和并将其与接收的数据进行比较,可以确定数据是否传输正确或是否发生了传输错误。通常,校验和无效的消息会被拒绝,并被视为丢失。不同的协议栈级别使用不同的校验和计算选项。例如,CRC-32 用于以太网帧。二进制补码校验和用于计算 IP 报头、ICMP 消息、UDP 数据报和 TCP 段的校验和。为了避免浪费处理器时间来计算传输的以太网帧或 IP 数据包的校验和,使用了一种卸载机制,该机制由网络适配器计算校验和。
▍ 字节和位顺序
计算机中的数据以二进制代码存储和处理。二进制代码意味着使用两个值(0 和 1)对信息进行编码。单个 0 或单个 1 包含最小信息量,称为"位"。处理器通常不是对单个位进行操作,而是以 8、16、32、64 等为单位进行操作。最小的块是 8 位。8 位称为字节。内存中的信息存储为字节序列。但是,一个字节只能容纳 256 个值,这对于大多数数学运算来说显然不够。因此,字节被分组为字(2 个字节)、双字(4 个字节)等。这些字节组可以以不同的方式存储在内存中。以 4 字节双字 0x12345678 为例。它由 4 个字节组成,值为 0x12、0x34、0x56 和 0x78。它们可以按以下顺序存储在内存中:0x12、0x34、0x56、0x78 或 0x78、0x56、0x34、0x12。它们在内存中的存储方式取决于处理器架构。例如,在 x86 架构中,此顺序是将具有较低有效位的字节存储在具有较高有效位的字节之前。
从历史上看,通过 IP 网络传输的数据包中的数据一直按网络字节顺序存储,这意味着首先存储包含最高有效位的字节。
至于位顺序,如果我们不深入研究在信号级别对传输数据进行编码的问题,则顺序并不重要,因为网络适配器将在没有程序员干预的情况下完成所有工作。
TCP/IP协议栈
一切都基于许多协议:
- 以太网 II;
- IP --- 互联网协议;
- ICMP------互联网控制管理协议;
- UDP------用户数据报协议;
- TCP------传输控制协议;
- DHCP------动态主机配置协议;
- DNS --- 域名服务。
让我们仔细看看。数据以称为协议数据单元 (PDU) 的块形式传输。PDU 由标头和有效负载组成。一种协议的 PDU 可以在其有效负载中包含另一种协议的 PDU。这称为封装。根据网络协议所处的层级,PDU 可能具有不同的名称:
- 在数据链路层 - 帧;
- 在网络级别 - 数据包(IP,ICMP);
- 在传输层 - 数据段或数据报(TCP、UDP);
- 在应用程序级别 - 消息(DNS,DHCP)。
然而,不同的文献可能不遵循此规则;例如,你经常会遇到 IP 数据报、TCP 数据包或 UDP 数据包。而在网络流量分析程序中,所有 PDU 都被称为数据包。随着经验的积累,你会更加熟悉特定术语的含义。
以太网协议
通常,在描述以太网协议时,人们会略过细节,甚至会深入到网络上传输的比特,甚至是信号。我不会深入探讨这些细节,尤其是考虑到您很可能使用的是 WiFi 连接,各种网络流量拦截器通常会将其显示为封装在以太网 II 帧中的 IP 数据包。这些帧与 WiFi 帧几乎没有共同之处,但它们统一了数据链路层的操作。事实上,理解 WiFi 的工作原理值得另写一篇文章来阐述。
由网络适配器驱动程序发送或接收的以太网帧由帧头和有效负载组成。

您经常会看到有关最小以太网帧大小、前导码和校验和的信息。然而,这涉及到网络适配器驱动程序的实现或适配器本身的硬件实现,因此本文不做讨论。对我们来说,以太网帧包含接收方的 MAC 地址、发送方的 MAC 地址、帧类型以及数据本身。
▍ IP 协议
该协议的原始描述位于RFC 791 互联网协议 - DARPA 互联网程序协议规范。
▍ 包结构
IP数据包的结构如下所示。

▍ IP 数据包和 IP 数据报
IP 协议允许数据以块的形式传输。在文献中,有两个类似的术语有时互换使用:IP 数据报和 IP 数据包。我想澄清一下这种区别。IP 数据报是传输到网络层的数据,而 IP 数据包是在 IP 网络内传输的数据。IP 数据报的大小受"总长度"字段的最大值限制。IP 数据包的大小受 MTU(最大传输单元)限制,MTU 是指数据链路层单帧可传输的最大数据量。如果 IP 数据报包含的有用数据超过单个 IP 数据包的容量,则需要使用分片技术。
▍ IP 数据报的分片
IP 协议支持 IP 数据报分片。分片是基于这样一个事实:单个 IP 数据报中传输的数据最大大小为 65,535 字节(八位字节),而数据链路层协议数据单元 (MTU) 中可容纳的最大数据大小要小得多(通常约为 1,500 字节)。如果 IP 数据报的大小大于 MTU,它将被拆分成多个 IP 数据包,每个 IP 数据包都可以在数据链路层 PDU 中传输。接收到 IP 数据包后,它们将被重新组装成一个 IP 数据报,并在传输层进行解析。
单个 IP 数据报中的所有 IP 数据包的"标识"字段都具有相同的值,"偏移"字段包含 IP 数据报有效载荷中的偏移量。
虽然分片允许连接不同网络上的主机,但建议避免使用分片,因为它会使数据传输变得复杂,并由于创建新的 IP 数据包和重新计算校验和而增加网络负载。因此,最好 TCP 段或 UDP 数据报的大小不大于 IP 数据包路径上的 MTU。
▍ARP协议
ARP 协议用于根据主机的 IP 地址确定其物理接口的 MAC 地址。该协议在RFC 826 --- 以太网地址解析协议中进行了描述。MAC 地址不一定是以太网网络地址,但以下是以太网 ARP 消息的结构。

▍ ICMP 协议
该协议在RFC 792(互联网协议 DARPA 互联网程序协议规范) 中描述。
▍消息结构
协议描述中提供了许多不同的消息,但目前我们只需了解 Echo 消息和 Reply 消息就足够了。

虽然该协议使用的消息封装在 IP 数据报中,但 ICMP 与 IP 属于同一级别------网络级别。
▍UDP协议
该协议在RFC 768(用户数据报协议) 中描述。
该协议允许两个进程交换 UDP 数据报。每个 UDP 数据报包含源端口(Source Port)、目标端口(Destination Port)、数据报长度(Length)、校验和(Checksum)以及实际传输的数据。
计算校验和时,会添加一个伪报头,该伪报头不传输,仅参与校验和计算。
当传输层允许重复接收数据、省略数据或数据传送顺序不重要时,该协议可用作传输协议。
通常,这些情况的处理委托给应用层协议,或者根本不实现。例如,在流式传输视频或音频时,数据会被省略,因为在这种情况下重新传输数据毫无意义。但是,如果希望在传输层保证数据传输,则必须使用 TCP。
▍消息结构
用于计算校验和和UDP数据报的伪报头的结构如下所示。


▍ TCP 协议
TCP 协议是本文讨论的所有协议中最复杂的。其目的是在进程之间建立可靠的、虚拟的、全双工的连接。目前,该协议的最新描述位于RFC 9293 --- 传输控制协议 (TCP)中。
▍消息结构
TCP 协议中使用的消息称为 TCP 段。请不要将其与网络段混淆,因为它们没有任何共同之处。与 UDP 类似,计算 TCP 段的校验和时会使用伪报头。然而,UDP 的校验和计算是可选的,而 TCP 的校验和计算是必需的。
伪报头和 TCP 段的结构如下所示。


从报头结构可以看出,TCP 协议与 IP 协议类似,能够使用选项字段来扩展和改进协议。
理解 TCP 的关键概念:
- 部分;
- 序列号;
- 确认号码;
- TCP 窗口;
- TCP握手;
- MSS --- 最大段大小;
- TCP 标志和 TCP 选项;
- 窗口缩放;
- 选择性确认。
序列号、确认号和窗口大小用于可靠地传输数据。让我们看看它们是如何协同工作的。传输的数据被分成多个 TCP 段。
每个 TCP 段都被分配一个序列号。
序列号的范围从 0x00000000 到 0xffffffff。如果每个传输的字节都被分配一个编号并分成多个段,则序列号是每个段的第一个字节的编号。
序列号用于对乱序到达的段进行排序、确认收到的段以及重传丢失的段。
通过网络传输的每个 TCP 段都包含序列号和确认号字段。序列号标识正在发送的段,确认号指示预期的段。
序列号和确认号的值允许您跟踪 TCP 连接上的数据传输进度。每一端都会生成一个从 0 到 2^32 的随机数,称为 ISN。此数字用于生成已发送段的序列号。
只有那些位于窗口内的段才会被发送。窗口的大小在建立连接时会告知发送方,但接收端稍后可以更改。
随着从接收端收到确认,窗口会随之变化。
在传输数据之前,必须先建立连接。建立连接时,双方会交换未来连接的参数。任何一端都可以发起连接终止。
建立连接时,双方会交换序列号、确认号、窗口大小以及 TCP 选项字段中传输的参数(MSS,窗口比例)。
▍ TCP 标志
已发送 TCP 报文段中的标志用于控制 TCP 连接。其中最重要的标志包括:
- SYN------建立TCP连接时使用;
- ACK------表示该段已被接收方收到;
- FIN - 用于正常(优雅)关闭 TCP 连接;
- RST------当 TCP 连接异常关闭时使用。
▍ TCP 选项
TCP 协议设计为使用选项机制进行扩展。选项是在标头中传输的附加字段。例如,在建立连接时,双方会交换选项,例如窗口缩放和最大分段 (MSS)。根据协议栈的设置,TCP 时间戳选项可能会在 TCP 段中传输。如果协议栈不支持该选项,则会忽略它。
▍ 打开连接、传输数据、关闭连接
RFC 9293 提供了 TCP 连接的详细描述和状态图。我不会在这里复制它。如果您愿意,可以阅读它。但是,我想指出的是,它读起来比较困难。下面是 TCP 连接生命周期的序列图。

请注意传输的 TCP 段的 SequenceNumber 和 AcknowledgeNumber 字段值是如何变化的。
如果使用三次握手建立 TCP 连接,则关闭连接有多种不同的选项。该图显示了四次握手,如果服务器在收到 FIN、ACK TCP 段后尚未处理来自客户端的所有数据,则会发生这种情况。如果已处理所有数据,则执行三次握手(服务器仅发送 FIN、ACK 段)。
为了更好地理解 TCP 段的发送和接收机制,我绘制了一个示意图。

▍配置TCP/IP协议栈
TCP/IP 协议栈可以在操作系统中配置,但最好在明确了解要更改的内容及其原因后再进行配置。
在 Windows 中,可以使用 netsh 命令配置协议栈。在 Linux/MacOS 中,可以使用 sysctl 命令配置。可配置参数的列表和默认值可能因操作系统而异。例如,在 Windows 中,TCP 时间戳默认处于禁用状态。要启用它们,请运行以下命令:
`netsh int tcp `set` global `timestamps=`enabled`
相反,在 Linux 中,tcp 时间戳是启用的;如果要禁用它们,则需要运行以下命令:
`sysctl `-w` net.ipv4`.tcp_timestamps=0
在 MacOS 中,您无法启用/禁用 TCP 时间戳。
好了,关于本章节讨论的内容,我们在下一篇文章继续向大家介绍。