上一篇文章中,我们介绍了浏览器是如何生成消息的:
在浏览器生成消息以后,他就要通过调用 Socket 库中的系统调用,委托操作系统协议栈将消息发送出去了,这就是我们今天这篇文章的重点内容。
首先,浏览器要做的是调用 Sockect 库提供的 socket 系统调用,创建套接字,那么,什么是 socket 呢?
在操作系统协议栈中,维护了一块内存空间,专门用来存放用来控制通信操作的控制信息,比如 ip 地址、端口号、通信状态等等内容,Socket 库返回的 socket 就是用来索引这块内存空间的句柄。
每一个 socket 对应协议栈内一块独立的内存空间,因此,当需要让操作系统协议栈进行连接、读写等操作时,都需要在调用 Socket 系统调用时传递 socket 作为参数,从而让协议栈可以去对应的内存空间中查询当前连接的控制信息。
那么,这块内存空间是什么时候被初始化的呢?就是在我们调用 Socket 库的 connect 函数时进行的,此时,操作系统向服务端发出连接请求,并且收到服务端的相应数据,然后,按照客户端参数及服务端的相应信息对存储控制信息的内存区域进行初始化,这些控制信息主要包含:
除了 socket 对应的控制信息缓存外,协议栈还会为本次连接分配一块数据缓冲区,用来对通信过程中的数据进行缓存。
通过 netstat 命令,可以查看每一个套接字对应的具体控制信息,每个 socket 占用一行。
完成连接的创建与初始化后,我们就可以通过调用 write 系统调用在 socket 上写入数据实现数据的发送了。
发送数据的步骤如下:
操作系统协议栈的 IP 模块在收到数据后,会根据 IP 协议,在数据的基础上写入 20 字节的 IP 头部,其中最为核心的,是包含了目的 IP 地址。
然后,IP 模块会在 IP 包的基础上,拼接 MAC 头部,组成一个以太网包。
MAC 头部中包含的最为关键的信息就是当前报文将要发送到的下一个网络节点的物理地址,也就是下一跳路由器的 MAC 地址,那么,操作系统协议栈的 IP 模块是如何知道下一跳路由器的 MAC 地址是什么呢?
要想获得下一跳路由器的 MAC 地址,这就需要地址解析协议 – ARP 协议。
IP 模块拿到目的 IP 地址后,会首先在协议栈维护的一个缓存区域中查询这个 IP 地址对应的 MAC 地址,如果查询不到就会向所在子网中的每一台设备发送 ARP 广播,询问那台设备关心当前 ip 地址,此时,子网中可以处理这一 IP 地址的设备就会向发出广播的设备发送回应,报告自己的 MAC 地址,当协议栈 IP 模块接收到设备的回应时,就可以将这个 ip 地址与回应中的 mac 地址相关联,并存储在刚刚提到的那块缓存中。
这块缓存区域就是 ARP 缓存,协议栈每隔几分钟会自动清空一次 ARP 缓存,防止由于网络环境变化造成的数据不实时的问题。
当协议栈 IP 模块获取到下一跳主机的 MAC 地址以后,生成 MAC 头部拼接在 IP 包的前面,这样就生成了一个以太网包,经过网络传输,就可以到达下一跳主机的网卡,网卡收到电信号后,转换为 0/1 组成的数字信息串交给网卡驱动程序,网卡驱动程序调用操作系统协议栈的 IP 模块。
IP 模块首先丢弃以太网头部信息,获得 IP 数据包,通过对比目标 IP 与本机 IP 可以判断当前主机是否是最终接收者,如果不是,那么就继续按照上述过程如法炮制,使用下一跳 MAC 地址生成新的以太网头部拼接在 IP 数据包前,得到新的以太网包再发送出去,直到到达最终目的 ip 所在主机。
最终的这台主机协议栈的 IP 模块会丢弃 MAC 头部与 IP 头部,获得原始数据交给 TCP 模块,TCP 模块再将数据发送给监听指定端口的应用程序,完成数据的接收工作。
上文中,我们忽略了一个细节,那就是 IP 模块要使用本地的哪块网卡来进行通信呢?
这取决于协议栈内部维护的另一个缓存数据 -- 路由表。
通过 netstat -r 命令或者 route 命令,我们可以查看主机当前维护的路由表:
如上图所示,每一行代表了一个可用的路由选项,对于一个给定的路由项,可以打印出五种不同的标识(Flags):
通过目的 IP 地址在路由表中查询,IP 模块就可以获取到表中 Iface 项所指向的本地网卡设备。
网卡硬件设备的基本组成如图所示:
IP 模块在完成以太网包的拼装后,会将以太网包交给指定网卡的驱动程序,网卡驱动程序从 IP 模块获取到以太网包之后,就会复制到网卡内的缓冲区中,然后向网卡的 MAC 模块发出发送指令。
MAC 模块将以太网包从缓冲区中取出,在数据包的前后分别拼接上报头、分隔符和校验序列,从而形成一个互联网帧。
这里提到了报头、分隔符和校验序列:
在此之后,MAC 模块就负责通过硬件将这个完整的互联网帧转换为电信号或光信号,发送给 MAU 模块,由 MAU 模块将电信号或光信号在网线或光纤上传送出去。
网卡的接收工作可以看成是上述过程的逆向过程,当网卡硬件监测到互联网帧的报头和 SFD 到来时,网卡的 MAU 模块就会开始进入接收状态,他将接收到的信号发送给 MAC 模块,由 MAC 模块将电信号或光信号转换为 0/1 的数据,并存储在接收缓冲区中,当 MAC 模块完成一整个互联网帧的接收工作后,他就会检查 FCS 来确认包的内容没有在传输过程中发生紊乱,如果存在紊乱,则丢弃这个包,否则就会通过扩展总线接口中的硬件中断总线与级联在 CPU 上的中断控制器通信。
CPU 收到中断后,会立即停止当前所有的工作,根据中断号,获知这是一次网络中断,于是就会去调用协议栈中的 IP 模块接口,让 IP 模块调用网卡驱动程序,从而获取到缓冲区中的互联网帧,通过丢弃全部头部信息,获取到接收到的原始数据,一次完整的接收过程就这样完成了。