是本好书,引导效果很好。
应用层消息生成
从解析浏览器中输入的网址开始,到生成 HTTP 请求消息、委托操作系统发送消息等步骤
1.1 HTTP消息
在书中主要是一个引入作用,作为切入点来说明后续的网络交互流程。
几个重点:
-
URL 统一资源定位符

-
HTTP基本思路:请求(方法+URL)和 响应(状态码)

1.2 DNS查询
通过调用socket库来实现,socket库是网络功能程序组件合集。
socket库DNS解析器原理:

DNS服务器基本工作原理:

class的IN代表互联网,固定
DNS 服务器会从域名与 IP 地址的对照表中查找相应的记录,并返回 IP 地址:
- 递归查询:
客户端只发一次请求,剩下全部交给本地 DNS 服务器去搞定,最后一次性返回最终结果给客户端 - 迭代查询:客户端(或者本地 DNS)每拿到一个中间提示,自己再发起下一次查询,一层一层自己问,服务器不会替你跑后续,只告诉你下一步该找谁。
1.3 委托协议栈发送消息
向操作系统内部的协议栈发出委托时,需要按照指定的顺序来调用 Socket 库中的程序组件。

收发数据的操作分为若干个阶段,可以大致总结为以下 4 个:
- 创建套接字(创建套接字阶段)
- 将管道连接到服务器端的套接字上(连接阶段)
- 收发数据(通信阶段)
- 断开管道并删除套接字(断开阶段)
1.3.1 委托协议栈发送消息 - 创建套接字 socket()
这 4个操作都是由操作系统中的协议栈来执行的,浏览器等应用程序并不会自己去做连接管道、放入数据这些工作,而是委托协议栈来代劳
调用 socket 之后,控制流程会转移到 socket 内部并执行创建套接字的操作,完成之后控制流程又会被移交回应用程序。

应用程序是通过“描述符”这一类似号码牌的东西来识别套接字的
1.3.2 委托协议栈发送消息 - 连接套接字 connect()
需要委托协议栈将客户端创建的套接字与服务器那边的套接字连接起来,当调用 connect 时,需要指定描述符、
服务器IP地址和端口号这 3 个参数。

描述符:connect 会将应用程序指定的描述符告知协议栈,然后协议栈根据这个描述符来判断到底使用哪一个套接字去和服务器端的套接字进行连 接,并执行连接的操作- 服务器IP地址
- 端口号:端口号就是用来让通信的另一方能够识别出套接字的机制,描述符是和委托创建套接字的应用程序进行交互时使用的
描述符:应用程序用来识别套接字的机制
IP 地址和端口号:客户端和服务器之间用来识别对方套接字的机制
1.3.3 委托协议栈发送消息 - 通信阶段 write() & close()、read()

主要依赖 write 函数来实现数据的发送,调用 write时,需要指定描述符和发送数据,然后协议栈就会将数据发送到服务器;
调用close函数实现断开,Web 使用的 HTTP 协议规定,当 Web 服务器发送完响应消息之后,应该主动执行断开操作,浏览器调用 read 执行接收数据操作时,read 会告知浏览器收发数据操作已结束,连接已经断开。浏览器得知后,也会调用close 进入断开阶段。
2. 用电信号传输TCP/IP数据
开始从协议栈的角度切入
2.1 套接字
套接字的实体就是通信控制信息
2.1.1 TCP/IP协议栈

2.1.2 套接字实体
套接字是一个命名概念
在协议栈内部有一块用于存放控制信息的内存空间,这里记录了用于控制通信操作的控制信息,例如通信对象的IP 地址、端口号、通信操作的进行状态等,可以说这些控制信息就是套接字的实体,或者说存放控制信息的内存空间就是套接字的实体
协议栈是根据套接字中记录的控制信息来工作的。
2.1.3 socket调用操作
创建套接字时,首先分配一个套接字所需的内存空间,然后向其中写入初始状态。

2.2 连接服务器
2.2.1 连接的含义
连接实际上是通信双方交换控制信息
- 客户端:初始化套接字后,需要将服务器信息传入协议栈
- 服务器端:初始化套接字后,等待客户端传入
2.2.2 控制信息
通信操作中使用的控制信息分为两类。
- 头部中记录的信息(如 tcp头)

这些字段是固定的,在连接、收发、断开等各个阶段中,每次客户端和服务器之间进行通信时,都需要提供这些控制信息。
在连接阶段,由于数据收发还没有开始,网络包中没有实际的数据,只有控制信息。这些控制信息位于网络包的开头,因此被称为头部。

- 套接字(协议栈中的内存空间)中记录的信息
2.2.3 连接操作
连接操作的第一步是在 TCP 模块处创建表示连接控制信息的头部,通过 TCP 头部中的发送方和接收方端口号可以找到要连接的套接字,之后通过IP模块发送到服务器。

之后便是TCP三次握手过程。
2.3 收发数据
当控制流程从 connect 回到应用程序之后,接下来就进入数据收发阶段

2.3.1 http请求交给协议栈
协议栈并不关心应用程序传来的数据是什么内容。应用程序在调用 write 时会指定发送数据的长度,在协议栈看来,要发送的数据就是一定长度的二进制字节序列而已。
协议栈并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并等待应用程序的下一段数据。
一次将多少数据交给协议栈是由应用程序自行决定的,协议栈并不能控制这一行为,要积累多少数据才能发送,不同种类和版本的操作系统会有所不同。
-
根据MTU判断,当从应用程序收到的数据长度超过或者接近 MSS 时再发送出去,就可以避免发送大量小包的问题了

-
根据时间判断,当应用程序发送数据的频率不高的时候,如果每次都等到长度接近 MSS 时再发送,可能会因为等待时间太长,为此协议栈的内部有一个计时器,当经过一定时间之后,就会把网络包发送出去
2.3.2 发送时的数据拆分
发送缓冲区中的数据超过MSS时,发送缓冲区中的数据会被以MSS长度为单位进行拆分,拆分出来的每块数据会被放进单独的网络包中。

在每一块数据前面加上 TCP 头部,并根据套接字中记录的控制信息标记发送方和接收方的端口号,然后交给 IP 模块来执行发送
2.3.3 发送时的ACK确认
实际上初始发送的序号值为随机值,在将 SYN 设为 1 的同时,还需要同时设置序号字段的值,而这里的值就代表序号的初始值

TCP是双向的,因此客户端和服务器双方都需要各自计算序号,因此双方需要在连接过程中互相告知自己计算的序号初始值
SYN 设为 1 并告知初始序号这一操作仅在连接过程中出现

在得到对方确认之前,发送过的包都会保存在发送缓冲区中。如果对方没有返回某些包对应的 ACK 号,那么就重新发送这些包。(重传机制)
2.3.4 调整 ACK 号等待时间
该部分涉及到网络的错误检测和补偿机制
超时时间:即返回 ACK 号的等待时间
ACK 号的返回时间也会产生很大的波动,可能会发生已经重传了包之后,前面的 ACK 号才姗姗来迟的情况。
TCP 会在发送数据的过程中持续测量 ACK 号的返回时间,如果 ACK 号返回变慢,则相应延长等待时间;相对地,如果 ACK 号马上就能返回,则相应缩短等待时间
使用窗口有效管理 ACK 号,也就是滑动窗口协议。所谓滑动窗口,就是在发送一个包之后,不等待 ACK 号返回,而是直接发送后续的一系列包。这样一来,等待 ACK 号的这段时间就被有效利用起来了
滑动窗口的具象化体现:

滑动窗口模式下,当接收方的 TCP 收到包后,会先将数据存放到接收缓冲区中。然后,接收方需要计算 ACK 号,将数据块组装起来还原成原本的数据并传递给应用程序,如果这些操作还没完成下一个包就到了也不用担心,因为下一个包也会被暂存在接收缓冲区中。如果数据到达的速率比处理这些数据并传递给应用程序的速率还要快,那么接收缓冲区中的数据就会越堆越多,最后就会溢出。缓冲区溢出之后,后面的数据就进不来了,因此接收方就收不到后面的包了,这就和中途出错的结果是一样的,也就意味着超出了接收方处理能力,所以需要通过接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制,这就是滑动窗口方式的基本思路
在不考虑拥塞的情况下,滑动窗口的工作流程如下:

发送的操作也是双向进行的,这个接收数据的量也就是窗口大小,一般收发双方的窗口大小一致。
如果接收方的性能高,处理速度比包的到达速率还快,缓冲区马上就会被清空,并通过窗口字段告知发送方。
更新窗口大小的时机应该是接收方从缓冲区中取出数据传递给应用程序的时候,当接收方将数据传递给应用程序,导致接收缓冲区剩余容量增加时,就需要告知发送方,这就是更新窗口大小的时机。
关于ACK,当需要连续发送多个 ACK 号时,只要发送最后一个 ACK 号就可以了,中间的可以全部省略,这是因为 ACK 号表示的是已收到的数据量,也就是说,它是告诉发送方目前已接收的数据的最后位置在哪里。当需要连续发送多个窗口更新时也可以减少包的数量,这种情况和 ACK 号一样,可以省略中间过程,只要发送最终的结果
就可以了。
比如收到字节
1‑100、再收到101‑200:不需要回
ACK=101,再回ACK=201;直接回复 ACK=201 就代表 0‑200 全部收到,中间 ACK 可以省略。
2.3.5 接收HTTP响应
浏览器在委托协议栈发送请求消息之后,会调用 read 程序来获取响应消息,和发送数据一样,接收数据也需要将数据暂存到接收缓冲区中。
协议栈会检查收到的数据块和 TCP 头部的内容,判断是否有数据丢失,如果没有问题则返回 ACK 号。然后,协议栈将数据块暂存到接收缓冲区中,并将数据块按顺序连接起来还原出原始的数据,最后将数据交给应用程序。具体来说,协议栈会将接收到的数据复制到应用程序指定的内存地址中,然后将控制流程交回应用程序。将数据交给应用程序之后,协议栈还需要找到合适的时机向发送方发送窗口更新
2.3.6 数据发送完毕后断开连接
- 控制面断开
无论哪种情况,完成数据发送的一方会发起断开过程,以服务器断开为例(四次挥手):

当收到服务器发来 FIN=1 的 TCP 头部时,客户端内核协议栈立即回复一个 ACK 确认报文,套接字进入 CLOSE_WAIT 状态;此时对端服务器已经不再发送数据,客户端调用read会返回 0(EOF)。
由客户端应用程序代码手动调用 close,内核协议栈才会生成 FIN=1 的 TCP 报文发送给服务器;服务器收到该 FIN 后回复 ACK。 服务器收到 ACK 后套接字直接释放进入 CLOSED;而客户端套接字进入 TIME_WAIT 状态,等待 2MSL 时长之后,客户端才最终完全释放套接字资源,整条 TCP 连接正式结束。这就是 TCP 四次挥手。
- 套接字删除 套接字不会马上被删除,因为要保证收到服务器发来的 ACK 号。如果断开太快,新端口被新服务所使用,服务器重发的 FIN 会到这个错误的服务上导致异常。

NOTE总结上述流程,在面向连接的场景下,经过 HTTP请求构造、委托协议栈发送、socket通道的建立、数据的拆分发送、发送中的流控、发送完后的释放。
- 数据收发操作的第一步是创建套接字
- 创建套接字之后,客户端会向服务器发起连接操作(三次握手)
- 数据收发时的窗口变换(滑动窗口)
- 数据发送完毕后的断开连接(四次挥手)
2.4 IP 与以太网的包收发操作
2.4.1 网络包基础
网络包的构造图如下,发送方的网络设备会负责创建包,创建包的过程就是生成含有正确控制信息的头部,然后再附加上要发送的数据。

IP和以太网是分工的,以太网也可以换成其他承载网络例如无线局域网、ADSL、FTTH 等。
2.4.2 包收发操作概览
包收发操作的起点是 TCP 模块委托 IP 模块发送包的操作,TCP 模块还需要指定通信对象的 IP 地址,也就是需要写清楚“将什么内容发给谁”。
IP 模块负责添加如下两个头部。
- MAC 头部:以太网用的头部,包含 MAC 地址
- IP 头部:IP 用的头部,包含 IP 地址

TCP 模块在收发数据时会分为好几个阶段,并为各个阶段设计了实现相应功能的网络包,但 IP 的包收发操作都是相同的,并不会因包本身而有所区别。
2.4.3 IP头部生成
IP 地址是由 TCP 模块告知的,而 TCP 又是在执行连接操作时从应用程序那里获得这个地址的,因此这个地址的最初来源就是应用程序。IP 模块不会判断这个地址是否正确。
IP 头部格式:

在发送包的网卡选择上,会优选路由匹配的网卡,如果没有匹配的路由,则通过默认路由所在的网卡发送。
2.4.3 MAC头部生成
IP 模块在生成 IP 头部之后,会在它前面再加上 MAC 头部。MAC 头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息。

在 IP 层面,能够通过路由表知道该发给谁,而在 MAC 层面,需要执行根据 IP 地址查询 MAC 地址的操作(ARP)。
TIP只有在操作系统启动过程中对网卡进行初始化的时候才会读取MAC 地址,读取出来之后会存放在内存中,每次执行收发操作时实际上使用的是内存中的值。此外,读取 MAC 地址的操作是由网卡驱动程序来完成的,因此网卡驱动程序也可以不从网卡 ROM 中读取地址,而是将配置文件中设定的 MAC 地址拿出来放到内存中并用于设定 MAC 头部,或者也可以通过命令输入 MAC 地址。
2.4.4 ARP查询MAC
查询 MAC 地址需要使用 ARP,ARP 是一种基于以太网的协议,利用以太网的广播机制,如果对方和自己处于同一个子网中,那么就可以通过携带的 IP 得到对方的 MAC 地址,并将其 MAC 地址信息缓存,不是这个 IP 地址的设备会忽略广播,什么都不回答

以太网基本结构:

2.4.5 将包转换成电或光信号发送
数据从应用层到网卡转换为电/光信号的流程如下:

网卡的 ROM 中保存着全世界唯一的 MAC 地址,这是在生产网卡时写入的。网卡中保存的 MAC 地址会由网卡驱动程序读取并分配给 MAC 模块。
TIPMAC 头部(以太网帧头)的构建、解析和逻辑处理主要由操作系统内核的网络协议栈负责;网卡硬件自动加上以太网头部 (DMAC+SMAC+Type 字段) + FCS 校验尾;
- 简化的发送流程如下:
- 协议栈层层封装(TCP/UDP 头 → IP 头 → MAC 头),通过网卡驱动将完整的以太网帧交给网卡
- 网卡驱动从 IP 模块获取包之后,会将其复制到网卡内的缓冲区中,然后向 MAC 模块发送发送包的命令。
- MAC 模块会将包从缓冲区中取出,并在开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列

- 电信号的转换:
-
按 802.3 以太网帧结构来看,前 7 字节为前导字段,后 1 字节为起始帧分界符。

-
用电信号来表达数字信息时,需要让 0 和 1 两种比特分别对应特定的电压和电流,为区分连续的 0 和 1,在数据信号之外再发送一组用来区分比特间隔的时钟信号,如下图所示,将数据信号和时钟信号叠加在一起,可以根据任意一个信号来反推(异或)

-
通过 FCS 校验序列来检测数据是否被损坏,它是一串 32 比特的序列,是通过一个公式对包中从头到尾的所有内容进行计算而得出来的。
- 发送网络包
网卡的 MAC 模块生成通用信号,然后由 PHY(MAU)模块转换成可在网线中传输的格式,并通过网线发送出去
TIPMAU: Medium Attachment Unit,介质连接单元
PHY: PHY(Physical Layer Device,物理收发器
- 接收返回包
- 首先,PHY(MAU)模块会将信号转换成通用格式并发送给 MAC 模块,MAC 模块再从头开始将信号转换为数字信息,并存放到缓冲区中。
- 当到达信号的末尾时,还需要检查 FCS,将从包开头到结尾的所有比特套用到公式中计算出 FCS,然后和包末尾的 FCS 进行对比。
- 接下来就要看一下 MAC 头部中接收方 MAC 地址与网卡在初始化时分配给自己的 MAC 地址是否一致,以判断这个包是不是发给自己的(混杂模式下可以全部接收)。
- 网卡会通知计算机收到了一个包,会使用 中断机制 ,网卡驱动被中断处理程序调用后,会从网卡的缓冲区中取出收到的包,并通过 MAC 头部中的以太类型字段判断协议的类型。网卡只管把包给协议栈,不关心相关内容。
TIPMSI‑X 中断:现代网卡,每个 RX 队列分配独立中断号,可以把不同队列的中断调度到不同 CPU 核(RSS),负载分担
在网卡执行接收包的操作的过程中,计算机并不是一直监控着网卡的活动,而是去继续执行其他的任务,我们需要一种机制能够打断计算机正在执行的任务,让计算机注意到网卡中发生的事情,这种机制就是中断机制。
首先,网卡向扩展总线中的中断信号线发送信号,该信号线通过计算机中的中断控制器连接到 CPU。当产生中断信号时,CPU 会暂时挂起正在处理的任务,切换到操作系统中的中断处理程序。然后,中断处理程序会调用网卡驱动,控制网卡执行相应的接收操作。
中断是有编号的,网卡在安装的时候就在硬件中设置了中断号,在中断处理程序中则将硬件的中断号和相应的驱动程序绑定
- 响应包向上层传递
若以太网帧携带的类型为 0800 ,则代表是个 IP 包,会传递到 IP 模块进行处理。首先检查格式,之后确认接收方是否是自己的 IP 地址,如果不是,则通过 ICMP 返回对应错误信息。(先不考虑分片)
3. 信号在网线的传输
本章说明网线传输出去的包是如何经过集线器、交换机和路由器等网络设备,最终进入互联网的
3.1 信号在局域网内的传输
所有的包在传输到目的地的过程中都是独立的,相互之间没有任何关联。
3.1.1 防止网线中的信号衰减
以太网信号的本质是正负变化的电压,可以认为网卡的 PHY(MAU)模块就是一个从正负两个信号端子输出信号的电路。通信错误的原因本质上是信号的失真。
- 双绞线对噪声的抑制
双绞线两根导线紧密绞合,外界干扰在两根线上产生大小相等、相位相同的共模噪声,接收端做差分相减即可抵消干扰信号。
双绞线种类补充:

3.1.2 集线器发送信号
集线器在每个接口的后面装有和网卡中的 PHY(MAU)功能相同的模块,但如果它们像网卡端一样采用直连式接线,是无法正常接收信号的。要正常接收信号,必须将“发送线路”和“接收线路”连接起来才行。
MDI(Media‑Dependent Interface):标准引脚定义,线序不交叉,1‑2 发送,3‑6 接收
MDI‑X(MDI‑crossover):交叉引脚,芯片内部自动把收发线对交叉,1‑2 接收,3‑6 发送 :::
MDI:收发线对不交叉;MDI‑X:PHY 内部完成收发线对交叉,解决相同线序设备直连不通的问题;Auto‑MDI‑X 支持自动适配两种模式。

物理层交给 PHY 芯片自动适配 MDI/MDI‑X,日常普通直通双绞线,不用再管收发线序交叉问题。
3.2 交换机的包转发操作
3.2.1 交换机根据地址表进行转发
- 信号到达网线接口,并由 PHY(MAU)模块进行接收,这一部分和集线器是相同的
- PHY(MAU)模块会将网线中的信号转换为通用格式,然后传递给 MAC 模块。MAC 模块将信号转换为数字信息,然后通过包末尾的FCS 校验错误,如果没有问题则存放到缓冲区中。
TIP交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有 MAC 地址
交换机整机的管理模块拥有一个(或少数几个)MAC 地址,用于交换机自身协议报文与管理通信。
3. 当交换机发现一个包要发回到原端口时,就会直接停止转发这个包,如集线器下挂的 A B 两台设备通信时,A 到 B 的包会被广播到交换机端口,此时目标 MAC 是 B 的 MAC 地址,交换机查询地址表之后发现记录中的目标端口和这个包的源端口是同一个端口,就会停止转发
3.2.2 全双工和自动协商
交换机的全双工模式可以同时发送和接收信号,发送和接收的信号线是各自独立的。
在以太网中,当没有数据在传输时,网络中会填充一种被称为连接脉冲的脉冲信号。在没有数据信号时就填充连接脉冲,这使得网络中一直都有一定的信号流过,从而能够检测对方是否在正常工作,或者说网线有没有正常连接。自动协商功能就是基于此设计出具有特定排列的脉冲信号,将自己能够支持的工作模式和传输速率相互告知对方。
3.3 路由器的包转发操作
3.3.1 路由器基本特性
路由器转发模块和端口模块的关系,就相当于协议栈的 IP 模块和网卡之间的关系

路由器端口会接收发给自己的以太网包,然后查询转发目标,再由相应的端口作为发送方将以太网包发送出去。这一点和交换机是不同的,交换机只是将进来的包转发出去而已,它自己并不会成为发送方或者接收方,因此路由器的各个端口都具有 MAC 地址和 IP 地址
3.3.2 地址映射
源NAT场景:

目的NAT场景:
