数据链路层位于TCP/IP五层结构自顶向下的第四层,它在最底层的物理层与网络层之间。开发者在进行网络编程时,很少与它打交道,能让大家知晓的无非就是MAC地址了。这样看来数据链路层的角色没有那么显眼,但事实上相比传输层和网络层,后者只是一层软件抽象,而数据链路层才是构成现实网络的基础,网络设备相互交流的原语。
数据链路层之上的应用层、传输层和网络层所具备的服务,数据链路层都能给予支持。比如:子网中的两台主机进行点对点通信,或者子网中的一个节点对其他设备进行广播,这些不同的通信形式,数据链路层都需要支持。事实上数据链路层不仅限于通信,还提供了更多服务,包括:成帧(变为数据链路层的通信原语),完成相邻两点的数据传递,流控,错误检测,差错纠正以及半双工和全双工通信。
数据链路层负责通过通信媒介实现设备之间的互联互通,这里的通信媒介就是指物理形式的存在,比如:双绞线、同轴电缆、光纤以及无线电波等。如果说把这些介质中传输的光电信号与二进制的0和1进行转换是物理层的工作,那数据链路层就是将需要发送或接收的比特数据转换为固定格式的(比特)集合,这个集合称为帧。帧是网络传输中的最小单位,因此我们说数据链路层上传输的实体(帧)才是网络交互的原语。
该集合就好像字节一样,开发人员不会去操纵位(bit)那种过低级别的类型,而是关注这种类似字节一样有含义的集合。
数据链路层的帧就如同网络层的分组一样,从一个端点通过介质传送到另一个端点,如果不去探寻其中细节,在有网络层IP技术的基础上,为什么还需要数据链路层呢?原因是TCP/IP是后期产物,在它出现前,不同的数据链路层技术面对各自使用的介质,都会规划和设计相应的传输形式以及基于这些形式的编解码策略。以太网和光纤,Wi-Fi和电话线,这些不同的传输介质在用于网络通信时会配套各自的驱动以确保主机系统能够使用这些介质,如果有软件跑在以太网上,它势必就会使用以太网这个数据链路层技术的SDK,导致这个软件就被绑定在了以太网上,并且很大的概率不同以太网厂商的驱动还相互不兼容。
早期的网络程序都有这样的问题,因此在不同的数据链路层技术上构建一套中立的通信传输标准就显得非常有必要。接下来就回到我们熟悉的故事,基于依赖转置原则,不同的数据链路层技术适配对接TCP/IP规范,而应用程序直接基于后者开发,就能做到软件的自由迁移。回顾整个发展史,虽然网络层和数据链路层都是负责将数据从一个端点传输到另一个端点,但它们解决的问题和作用是不一样的,因此不冲突。
和IP协议一样,如果要支持设备间的通信,首先就需要标识它们,在IP协议中用IP地址,在数据链路层中则用MAC地址。由于MAC地址需要面向物理层工作,所以它和IP地址的获取动作不一样,MAC地址被烧录在网卡设备的ROM中,当操作系统启动时,可以通过设备驱动加载到它。
IP地址的获取方式,可以参考笔者所写网络层的文章。
如果MAC地址是相对固定的,不像IP地址那样飘来飘去,那就需要它尽可能的不重复,这样在一个子网中就不会同时出现多个具有相同MAC地址的设备了。标识设备最简单的方式就是使用自增的数字,如果要标识设备的数量可能很大,那就可以采用8字节的长整型,一定管够,这玩意描述人类已知宇宙中的天体都够用了,只需要有一个机构做好数字的维护就行。
简单的事情一定要做复杂,要不然体现不出智慧,本着这个理念,一定不能用一维数字,要用多个位组合成的复杂标识。既然关注设备,那就将设备厂商编号纳入其中,未来说不定还可以向这些企业收费,然后在跟上一个厂商自己的识别码,这样重复与否的皮球又踢回去了,不仅如此,标识看起来就气派多了,MAC地址按位划分如下图所示:
如上图所示,第一位为0表示该地址为单播地址,否则为多播地址,第二位为0表示为全局地址,否则是本地地址,后面两段分别是代表厂商的标识以及厂商内部的识别码,这样就能保证没有相同的MAC地址。MAC地址的长度是48位,如果采用类似IP点分十进制的形式来描述,那就是6段数字,比如:128.128.128.128.128.128.128
,不过这样感觉太占位置了,如果使用16进制,每个字符描述4位,换成f0:18:98:1f:91:e4
的形式,感觉就帅气一些了。目前我们都是使用后者来描述MAC地址。
在类UNIX系统的控制台,输入ifconfig
命令,可以查看本机的网卡配置,如下图所示:
如上图所示,输出在控制台上的内容,形式是以设备名外加描述组成,例如:其中lo0
表示本地网卡,也就是localhost0
的简写,而en0
则是本机物理网卡的配置信息。en0
网卡的ether
(以太)一栏的内容为:f0:18:98:1f:91:e4
,这个就是en0
网卡的MAC地址。lo0
为什么没有MAC地址呢?因为它是本地网卡,其IP是本地回环地址,所以对它的访问,协议栈会将其传回给传输层,所以它不需要数据链路层的支持,因而没有MAC地址,当然这也是网络分层的好处之一,适应性是杠杠的。
访问https://itool.co/mac,可以查询MAC地址,如下图所示:
如果仔细观察这个MAC地址,对比MAC地址的格式说明,会发现有些问题,MAC地址的位没有按照规范来呀。f0:18:98:1f:91:e4
的查询结果显示它是一个全局唯一的地址,也就是说MAC地址的第1和2位需要是0
,但理解十六进制的我们,看到f0
就知道它的二进制形式是:11110000
,也就是前两位是1
,这不是矛盾了吗?其实没有,因为MAC地址采用了大端描述,因此实际内容应该是0f
,符合规范,可以参考下图:
上图来源:https://www.cnblogs.com/lsgxeva/p/13932262.html。MAC地址的描述形式这么不讲究,不怕有人闹吗?答案是不怕,因为关注的人不多。其实不论正反,它就是要表示一个标识,类似一个整数或者UUID,所以无所谓的。
使用MAC地址可以描述网络设备后,接下来和IP协议一样,设计一款用来进行通信的约定规范,也就是需要进行数据链路层通信协议的概念设计。通信协议常见的元素包括:双方地址、协议长度(定长或者是变长)、类型或者业务标识、数据载荷以及数据校验位,当然其中的类型以及业务标识要结合协议的用途来看。数据链路层的通信协议,主要用来完成两个MAC地址之间的数据传输,以及数据传输过去后需要用哪种逻辑进行处理,因此最简单的数据链路层通信协议可以设计成以下形式,如图所示:
如上图所示,数据链路层通信协议的协议头部包含了目标和来源MAC地址、类型、数据校验位和载荷数据长度,这是一种定长头的通信协议,简单且高效。事实上仔细分析这个协议,它和UDP协议非常类似,只保有了通信所需的最基本要求,而且它与IEEE 802.3规范中的以太网协议已经非常像了。
上述设计的数据链路层协议其实不是我们日常用的协议,甚至IEEE 802.3规范中定义的以太网协议也不是,DIX协议才是现在的以太网协议,DIX分别指代DEC、Intel和Xerox三家公司,其实主要是Xerox公司的功劳。因为以太网协议只是高效的完成数据传输,没有过多的参与到上层活动中,DIX由于其简单和低成本所以成为了事实标准。对于这种事实非标准的情况,很常见,就好像J2EE和Spring的关系一样,前者最后就成了吉祥物。
至于以太网协议,我们在后面的章节做介绍,因为它是数据链路层中的一种实现技术。说到这里,一会数据链路层,一会以太网,到底它们是什么关系?以太网是数据链路层的一种实现技术,就好像同轴电缆、电话线或者光纤技术一样。任何互联互通都离不开介质,而无论是双绞线还是无线电波,这些介质都可以用来通信,而附加在这些介质上的软件含义称为数据链路层,客观存在的物理介质称为物理层。早期计算机网络实际都在围绕着介质建设不同的数据链路层和物理层,以太网技术包含了软件层面的定义、驱动、协议以及RJ45接口、双绞线、集线器和交换机,因此它是一种联通两层的技术实现,或者说具体的数据链路层技术都会有一个物理层介质与之对应,而本章就在介绍它们在软件层面的共性。
现在应该能分清楚数据链路层和以太网之间的区别了。数据链路层定义了描述设备的方式,即MAC地址,围绕MAC地址会提供适合具体物理层的数据链路层通信协议。虽然不同的数据链路层技术采用的通信协议有所不同,但是它们都需要围绕着MAC地址来设计,离不开通信协议所需的必要元素,比如:来源和目标MAC地址,以及数据负载和标识负载类型的定义,至于具体的数据链路层协议,本质上都是在最小通信协议基础上增加一些其介质特有的属性而已。
形成帧的工作主要有两类,一类是从协议栈程序委派给数据链路层的数据发送工作,它要求将数据负载按照当前数据链路层实现的协议进行编码成帧,比如:以太网;另一类是从物理层接收到信号,信号经过物理层转为0和1的比特,然后由数据链路层接收比特流并解码和校验,最终形成当前数据链路层能够识别的帧。
应用程序可以通过使用socket接口向网络中写入数据,通过前面章节的介绍,我们知道,socket接口并没有独立向网络写数据的能力,该工作需要socket接口和操作系统协议栈程序配合完成。操作系统协议栈程序属于操作系统内核,socket接口将需要写入网络的数据放入内存中,然后将需求和数据地址传递给操作系统协议栈,由后者完成数据发送。操作系统协议栈不熟悉具体网卡硬件,还需要依靠网卡的驱动程序以及硬件网卡才可以实现数据的发送,使用以太网为例,如下图所示:
如上图所示,它按照流程描述了应用程序如何驱动网卡来发送数据的,这比我们想象中调用socket接口就完成数据发送要复杂的多,不过本章对它的介绍主要集中在数据链路层,也就是MAC模块,等未来介绍物理层时,你还会看到这张图。
首先网卡上有很多部件,包括:ROM、缓冲区、MAC、MAU以及RJ45端口等,其实它也可以看作是一个专用计算机。ROM中烧录了当前网卡的MAC地址,缓存区就相当于网卡的内存,外部需要网卡发送的数据会写到缓冲区中,而网卡上MAC和MAU模块会操作缓冲区中的数据,它们一般无法操作当前计算机的内存。MAC和MAU模块是网卡的CPU,而缓冲区就是网卡的内存。MAC模块会访问缓冲区中的数据,将这些比特数据编码成帧,然后按照一定的约束调用MAU模块,MAU模块接收MAC模块的请求,将字节数据转换成电信号,通过在RJ45接口上不同引线上施加电压,实现将数据以电信号的形式进行发送。
MAC模块就是数据链路层的物理存在,而网卡驱动程序会与MAC模块做交互,MAC模块从缓冲区中取出需要发送的数据,依据当前数据链路层的协议进行帧的生成,然后按照一定约束调用MAU模块完成帧的发送。
当对端计算机通过网线传回数据时,MAC模块将比特流数据转换为帧,并将帧给到协议栈,实现数据的接收,使用以太网为例,如下图所示:
如上图所示,它按照流程描述了应用程序如何通过网卡收到数据,该过程与数据发送相比,看起来更加复杂,不过确实如此。从双绞线进入的电信号,它需要进行解调,转成计算机能够识别的字节,同时还需要从网卡内存拷贝到计算机内存,从操作系统内核态进入到应用程序用户态,这一路别提多坎坷了。虽然很复杂,但这次也只是集中注意力在数据链路层,在后续介绍物理层时,你还会看到这张图。
MAC模块从MAU模块获取一定约束的字节数据,这些数据会以字节的形式存放在缓冲区中,因为它们还不是完整的帧。随着读取的字节数据变多,MAC模块读取到了结束帧(FCS)或下一个开始帧,这代表对端传递的帧已经接收完成。虽然操作系统上的不同应用可以并行的收发数据,但是在数据链路层的发送和接收过程中,帧是最小单位,在任意时刻,双绞线上的信号(发送或接收端)只属于某一个帧。MAC模块将缓冲区中蓄积好的帧做校验,通过校验并且目标MAC地址是当前网卡的帧是需要接收的帧,如果不是则直接丢弃。
符合预期的帧放在网卡的缓冲区中,操作系统没有能力从其中获取,因此MAC模块会发起中断,当处理器响应中断后,会按照中断请求的约定调用网卡驱动,而后者具备操作网卡的实际能力。网卡驱动会访问网卡缓冲区中的帧,并将其拷贝到内存,然后通知协议栈进行处理。操作系统协议栈程序只带有TCP/IP等软件层面的定义,对于以太网(或者其他数据链路层)协议格式并不清楚,因此,在网卡驱动中也会包含对当前数据链路层协议的软件定义,并支持将其转换为IP分组。
中断是有编号的,网卡在安装时就在硬件中设置了中断号,在中断处理程序中将硬件中断号与网卡驱动绑定。
因为网卡驱动反向依赖协议栈或者TCP/IP协议,如果帧数据中的类型是IP协议,则会将帧数据转换为IP分组,然后交给协议栈中的TCP/IP模块来进行处理,这样交给协议栈的数据就是标准的分组了。
MAC模块是数据链路层的核心,它通过与MAU模块的交互,将帧数据发送到网络中,同时也将接收到的帧数据交给协议栈。MAU模块会按照要求进行字节到信号(以及相反过程)的处理与发送,如果子网中所有的主机节点都在随意的发送数据,MAU模块就会向网络介质,比如:双绞线、Wi-Fi或者同轴电缆中发送电信号。可以想象这些信号会相互影响,导致接收方MAU模块无法正确的识别,这种情况肯定不是我们想要的。
如果说数据链路层的工作是帧的生成和错误检测,那么看起来操心物理层信号是否相互影响以及在影响发生时的规避策略就应该是MAU的事情,但情况没有这么简单。就像TCP要考虑传输的可靠性,而底层的IP虽然实现了主机到主机的数据传输,不过没有了TCP就显得用途不大一样,数据链路层也要为信号正确的传输努一把力。既然MAC模块能够从宏观上对MAU进行操作,那么偏向电信号传输的策略算法就由MAC模块来控制。
MAC模块进行控制的方式主要有三种:时分TDMA,频分FDMA和码分CDMA,其实它们就是着重在电磁波信号的划分上,是以时间片来做依据,还是以频率做依据。不论以时间还是频率做划分依据,其目的就是让主机发送信号时,能够有一个物理标识的维度。
以时间为例,也就是时分TDMA,它可以定义时间片,这肯定是比秒要小得多的单位。在一个时间片中,子网中只有一台主机能够发送信号,不过这不能绝对避免不同节点会一起发送的情况,但是它能保证每个主机都有一把对时间划分的尺子。当若干节点一起发送信号了,参与发送节点的MAU模块能够感知到接收线路有信号进来,这时就表明当前节点在发送时也有其他节点在同时发送,此刻只需要发送信号的多个节点执行相同策略即可。这个策略就是检测到上述冲突后,当前时间片不再发送信号,本地随机一个时间延迟,也就是在之后的P个时间片再次发送,这个P是一个随机值,不同节点会有不同的值。
通过随机延迟来解决不同节点之间的信号发送冲突问题,这个思路很简单,虽然延迟比较高,但这种先进行检测,然后发送中做好侦听,当发现冲突后进行规避的处理策略却是通用的。以太网常用的CSMA/CD,就是在发送信号前侦听是否有主机占用了信道,如果没有则进行发送的技术。
这种事前侦听,事中检测的发送方式显得很低效,仿佛在子网中加了一把全局锁,如果能够把锁的粒度变小,主机节点之间的相互干扰从物理链路上隔离开,网络的数据吞吐量就会显著增加,接下来介绍几种常见的数据链路层通信技术,看看它们是如何解决这个问题的。