网络是怎样连接的-IP与以太网的包收发操作(四)
2.5.9 向集线器发送网络包
发送信号的半双工和全双工模式
加上报头、起始帧分界符和 FCS 之后,我们就可以将包通过网线发送出去了。
发送信号的操作分为两种,一种是使用集线器的半双工模式,另一种是使用交换机的全双工模式。
发送和接收同时并行的方式叫作“全双工”,相对地,某一时刻只能进行发送或接收其中一种操作的叫作“半双工”。
半双工模式下的工作模式
在半双工模式中,为了避免信号碰撞,首先要判断网线中是否存在其他设备发送的信号。
如果有,则需要等待该信号传输完毕,因为如果在有信号时再发送一组信号,两组信号就会发生碰撞。
当之前的信号传输完毕,或者本来就没有信号在传输的情况下,我们就可以开始发送信号了。
MAC模块将数字信息转换为电信号
首先,MAC 模块从报头开始将数字信息按每个比特转换成电信号,然后由 PHY,或者叫 MAU 的信号收发模块发送出去。
将数字信息转换为电信号的速率就是网络的传输速率,例如每秒将 10 Mbit 的数字信息转换为电信号发送出去,则速率就是 10 Mbit/s。
MAU和PHY的区别
根据以太网信号方式的不同,有些地方叫 MAU(Medium Attachment Unit,介质连接单元),有些地方叫 PHY(Physical Layer Device,物理层装置)。在速率为 100 Mbit/s 以上的以太网中都叫 PHY。
PHY模块将信号转换为可在网线上传输的格式
接下来PHY(MAU)模块会将信号转换为可在网线上传输的格式,并通过网线发送出去。
以太网规格中对不同的网线类型和速率以及其对应的信号格式进行了规定,但 MAC 模块并不关心这些区别,而是将可转换为任意格式的通用信号发送给 PHY(MAU)模块,然后 PHY(MAU)模块再将其转换为可在网线上传输的格式。
大家可以认为 PHY(MAU)模块的功能就是对 MAC 模块产生的信号进行格式转换。
网线中实际传输的信号
当然,以太网还有很多不同的派生方式,网线传输的信号格式也有各种变化。
此外,实际在网线中传输的信号很复杂,我们无法一一介绍,但是如果一点都不讲,大家可能对此难以形成一个概念,所以就举一个例子,大家感受一下就好。
下图就是一个例子,网线中实际传输的信号就是这个样子的。
数据信号和时钟信号叠加而成的信号,就是 10BASE-T 方式所使用的信号,这也是一个网线中实际传输的信号的例子。
PHY发送信号的同时还负责监控是否有信号进来
PHY在开始发送信号之前,需要先确认没有其他信号进来,这时才能开始发送。
如果在信号开始发送到结束发送的这段时间内一直没有其他信号进来,发送操作就成功完成了。
以太网不会确认信号是否被收到
以太网不会确认发送的信号对方有没有收到。
根据以太网的规格,两台设备之间的网线不能超100米,在这个距离内极少会发生错误。
万一发生错误,协议栈的 TCP 也会负责搞定,因此在发送信号时没有必要检查错误。
发生信号碰撞时所有发送操作停止
在发送信号的过程中,接收线路不应该有信号进来,但情况并不总是尽如人意,有很小的可能性出现多台设备同时进行发送操作的情况。
如果有其他设备同时发送信号,这些信号就会通过接收线路传进来。
在使用集线器的半双工模式中,一旦发生这种情况,两组信号就会相互叠加,无法彼此区分出来,这就是所谓的信号碰撞。
这种情况下发送操作会终止。为了通知其他设备当前线路已发生碰撞,还会发送一段时间的阻塞信号,然后所有的发送操作会全部停止。
阻塞信号
以太网中发生碰撞时,为了告知所有设备而发送的一种特殊信号。
等待时间根据MAC地址计算随机生成
等待一段时间之后,网络中的设备会尝试重新发送信号。
但如果所有设备的等待时间都相同,那肯定还会发生碰撞,因此必须让等待的时间相互错开。
具体来说,等待时间是根据 MAC 地址生成一个随机数计算出来的。
当网络拥塞时,发生碰撞的可能性就会提高,重试发送的时候可能又会和另外一台设备的发送操作冲突,这时会将等待时间延长一倍,然后再次重试。
以此类推,每次发生碰撞就将等待时间延长一倍,最多重试 10次,如果还是不行就报告通信错误。
全双工模式不会发生信息碰撞
另一种全双工模式我们会在第 3 章探索交换机时进行介绍,在全双工模式中,发送和接收可以同时进行,不会发生碰撞。
因此,全双工模式中不需要像半双工模式这样考虑这么多复杂的问题,即便接收线路中有信号进来,也可以直接发送信号。
2.5.10 接收返回包
在使用集线器的半双工模式以太网中,一台设备发送的信号会到达连接在集线器上的所有设备。
这意味着无论是不是发给自己的信号都会通过接收线路传进来,因此接收操作的第一步就这些信号全都收进来再说。
PHY模块处理信号
PHY模块将信号转成通用格式发送给MAC模块。
MAC模块处理信号
开始转换数字信息
信号的开头是报头,通过报头的波形同步时钟,然后遇到起始帧分界符时开始将后面的信号转换成数字信息。
检查FCS帧校验序列
当到达信号的末尾时,还需要检查 FCS。
具体来说,就是将从包开头到结尾的所有比特套用到公式中计算出 FCS,然后和包末尾的 FCS 进行对比。
正常情况下两者应该是一致的,如果中途受到噪声干扰而导致波形发生紊乱,则两者的值会产生差异,这时这个包就会被当作错误包而被丢弃。
比对MAC地址
如果 FCS 校验没有问题,接下来就要看一下 MAC 头部中接收方MAC 地址与网卡在初始化时分配给自己的 MAC 地址是否一致,以判断这个包是不是发给自己的。
我们没必要去接收发给别人的包,因此如果不是自己的包就直接丢弃,如果接收方 MAC 地址和自己 MAC 地址一致,则将包放入缓冲区中。
网卡通知计算机
到这里,MAC 模块的工作就完成了,接下来网卡会通知计算机收到了一个包。
通知计算机的操作会使用一个叫作中断的机制。
在网卡执行接收包的操作的过程中,计算机并不是一直监控着网卡的活动,而是去继续执行其他的任务。
如果网卡不通知计算机,计算机是不知道包已经收到了这件事的。
网卡驱动也是在计算机中运行的一个程序,因此它也不知道包到达的状态。
在这种情况下,我们需要一种机制能够打断计算机正在执行的任务,让计算机注意到网卡中发生的事情,这种机制就是中断。
中断机制
具体来说,中断的工作过程是这样的。
首先,网卡向扩展总线中的中断信号线发送信号,该信号线通过计算机中的中断控制器连接到 CPU。
当产生中断信号时,CPU 会暂时挂起正在处理的任务,切换到操作系统中的中断处理程序。
然后,中断处理程序会调用网卡驱动,控制网卡执行相应的接收操作。
中断是有编号的,网卡在安装的时候就在硬件中设置了中断号,在中断处理程序中则将硬件的中断号和相应的驱动程序绑定。
例如,假设网卡的中断号为 11,则在中断处理程序中将中断号 11 和相应的网卡驱动绑定起来,当网卡发起中断时,就会自动调用网卡驱动了。
现在的硬件设备都遵循即插即用规范自动设置中断号,我们没必要去关心中断号了,在以前需要手动设置中断号的年代,经常发生因为设置了错误的中断号而导致网卡无法正常工作的问题。
判断协议类型把包交给对应的协议栈
网卡驱动被中断处理程序调用后,会从网卡的缓冲区中取出收到的包,并通过 MAC 头部中的以太类型字段判断协议的类型。
现在我们在大多数情况下都是使用 TCP/IP 协议,但除了 TCP/IP 之外还有很多其他类型的协议,例如 NetWare 中使用的 IPX/SPX,以及 Mac 电脑中使用的 AppleTalk等协议。
这些协议都被分配了不同的以太类型,如 0080(十六进制)代表IP 协议,网卡驱动就会把这样的包交给 TCP/IP 协议栈。
如果是 809B 则表示 AppleTalk 协议,就把包交给 AppleTalk 协议栈,以此类推。
前提是操作系统内部存在以太类型所对应的协议栈。
如果不存在相应的协议栈,则会视作错误,直接丢弃这个包。
协议栈判断将这个包交给那个应用程序
按照探索之旅的思路,大家可能会认为向 Web 服务器发送包之后,后面收到的一定是 Web 服务器返回的包,其实并非如此。
计算机中同时运行了很多程序,也会同时进行很多通信操作,因此收到的包也有可能是其他应用程序的。
不过,即便如此也没问题,网卡不会关心包里的内容,只要按照以太类型将包交给对应的协议栈就可以了。
接下来,协议栈会判断这个包应该交给哪个应用程序,并进行相应的处理。
2.5.11 将服务器的响应包从 IP 传递给 TCP
IP模块处理接收的包
假设 Web 服务器返回了一个网络包,服务器返回的包的以太类型应该是 0800,因此网卡驱动会将其交给 TCP/IP 协议栈来进行处理。
检查IP头部的格式
接下来就轮到 IP 模块先开始工作了,第一步是检查 IP 头部,确认格式是否正确。
检查IP地址
如果格式没有问题,下一步就是查看接收方 IP 地址。
如果接收网络包的设备是一台 Windows 客户端计算机,那么服务器返回的包的接收方 IP 地址应该与客户端网卡的地址一致,检查确认之后我们就可以接收这个包了。
如果接收方 IP 地址不是自己的地址,那一定是发生了什么错误。客户端计算机不负责对包进行转发,因此不应该收到不是发给自己的包。
如果是服务器就不一定了,服务器可以像路由器一样对包进行转发。
通过ICMP消息将错误告知发送方
当发生这样的错误时,IP 模块会通过 ICMP 消息将错误告知发送方(图 2.1)。
ICMP 规定了各种类型的消息,如表 2.4 所示。
当我们遇到这个错误时,IP模块会通过表 2.4 中的 Destination unreachable 消息通知对方。
检查完毕后还原网络包
如果接收方 IP 地址正确,则这个包会被接收下来,这时还需要完成另一项工作。
IP 协议有一个叫作分片的功能,具体的内容我们将在第 3 章探索路由器时进行介绍。
简单来说,网线和局域网中只能传输小包,因此需要将大的包切分成多个小包。
如果接收到的包是经过分片的,那么 IP 模块会将它们还原成原始的包。
保存分片的包的ID
分片的包会在 IP 头部的标志字段中进行标记,当收到分片的包时,IP 模块会将其暂存在内部的内存空间中,然后等待 IP头部中具有相同 ID 的包全部到达,这是因为同一个包的所有分片都具有相同的 ID。
对包进行分片重组
此外,IP 头部还有一个分片偏移量(fragment offset)字段,它表示当前分片在整个包中所处的位置。根据这些信息,在所有分片全部收到之后,就可以将它们还原成原始的包,这个操作叫作分片重组。
TCP模块处理接受的包
TCP模块根据IP和端口查找对应的套接字
到这里,IP 模块的工作就结束了,接下来包会被交给 TCP 模块。
TCP模块会根据 IP 头部中的接收方和发送方 IP 地址,以及 TCP 头部中的接收方和发送方端口号来查找对应的套接字。
找到对应的套接字之后,就可以根据套接字中记录的通信状态,执行相应的操作了。
如果包的内容是应用程序数据,则返回确认接收的包,并将数据放入缓冲区,等待应用程序来读取
如果是建立或断开连接的控制包,则返回相应的响应控制包,并告知应用程序建立和断开连接的操作状态。
TCP模块需要获取IP模块中的IP地址
当包交给 TCP 模块之后,TCP 模块需要查询 IP 头部中的接收方和发送方 IP 地址来查找相应的套接字。
但是严格来说,TCP 模块和 IP 模块有各自的责任范围,TCP 头部属于 TCP 的责任范围,而 IP 头部属于 IP 模块的责任范围。TCP 模块去查询它等于是越权了。
如果根据这种严格的划分来开发程序的话,IP 模块和 TCP 模块之间的交互过程必然会产生成本。
而且 IP模块和 TCP 模块进行类似交互的场景其实非常多,总体的交互成本就会很高,程序的运行效率就会下降。
不妨将责任范围划分得宽松一些,将 TCP 和 IP 作为一个整体来看待,这样可以带来更大的灵活性。
此外,关于为什么查找套接字同时需要接收方和发送方的 IP 地址和端口号,我们会在第 6 章介绍端口号机制时一起讲解。