一、数据链路层概念
数据链路层夹在物理层和网络层之间:
- 他需要物理层提供的 “比特传输” 的服务,先拿到比特流数据
- 然后要为网络层提供检查好的数据,还要把网络层传下来的 “分组信息” 封装成 “数据帧”
这里讲一下一个概念
- 物理链路(或“链路”):就是数据实际传输路程,是由【第0层的传输介质】+【第1层的物理层】构成的
- 逻辑链路(或“数据链路”):就是【第2层数据链路层】,仅依靠一个 “物理链路” 跟他自己就可以实现无差错的数据传输,我们在学习数据链路层的时候可以抽象把数据链路层理解成直接互通的
二、数据链路层任务
数据链路层要做的事主要就是:
(其中数据链路层有很多协议,有3个基本问题是共同的:封装成帧、透明传输、差错检测)
看图看不明白就简单解释一下:
- 1、【封装成帧】数据链路层传输的数据单位是【帧】,每个帧会有一个帧头和帧尾(这也方便区分每个帧的界限);另外“透明传输”这个概念后面会经常提到,这个“透明”不是它传输的过程透明可以被看见,相反,是透明掉、不被网络层看到,网络层不知道这个数据是怎么传输的
;
- 2、【差错控制】和【可靠传输】都是表示数据链路层具有检查数据是否有误、传输正常数据的功能。只不过
- 【差错控制】是针对物理层传上来的比特流的【位错】,换句话说就是“我可能写了一坨错别字一大堆的狗屎文章,要更正这些错别字”(针对这种错误2种方案:丢弃重传、或者接收方自己修改)
- 【可靠传输】是针对自己数据链路层传输的帧的【帧错】,换句话说就是“虽然我写了一托狗屎,但是至少我写了几页就传几页,不能少页、多页、页数乱”(会有4种错误:帧丢失、帧重复、帧失序、帧错误)
;
- 3、【流量控制】就是让发送方别发那么快,自己一下子接收不了那么多
;
- 4、【介质控制】主要针对 “广播式” 传输,因为一般要用 “总线性结构”,涉及总线争用;“点对点”就没有对传输介质的争用情况
三、组帧
那么来看数据链路层的第一个任务——【组帧】
组帧的意义:在数据出错的时候只用重新传输错误的【帧】,而不是重新传整个【分组】,效率高
组帧细看要完成的就是2个事:【帧界定】和【透明传输】
【帧界定】就是怎么把物理层传过来的一长串比特流分割成一个个帧,分割的原理就是将协议里的数据部分的前后加上【帧头】、【帧尾】
;
;
【透明传输】就是怎么让每一个接收数据的数据链路层明白这是一个帧,从而去掉【帧头】、【帧尾】这些网络层不关心的东西,从而达到数据到了网络层是个“透明干净”的分组信息
那么对于这两个任务,归根结底本质就是做一件事:怎么鉴别一长串比特流里哪些是帧头、哪些是帧尾,要解决这个问题,就涉及4种组帧方式:
- 字符计数法
- 字节填充法
- 零比特填充法
- 违规编码法
1、字符计数法
总结:规则就是每一帧由【帧头】+【数据部分】组成,【帧头】决定这一帧多长;缺点是帧头一错后面就全错
规定:每一帧由【帧头】+【数据部分】组成,帧头二进制数转换成十进制的结果,就代表了这一帧总共多长(一共几个字节)
(注意是字节不是比特,每个字节固定就8比特)
缺点:只要有一个帧的【帧头】信息出现“位错”,就会导致后面所有帧的【定界】都错!
2、字节填充法
总结:规则就是每一帧由【帧头】+【数据部分】+【帧尾】组成,【帧头】、【帧尾】来切割这一帧的界限;缺点是数据部分也会出现和【帧头】【帧尾】【转义字符】一样数值的内容,解决办法就是在数据部分的这些地方前面加【转义字符】来标识这一块只是普通数据
【字节填充法】和【零比特填充法】都差不多,本质都是:规定好用一个特殊值,分别作为【帧头】和【帧尾】,可以理解为一根长绳为了区分线段,给每个线段前后涂上颜色
具体区别是,【字节填充法】是规定好直接分别用2个Ascll码的数值,来分别代表一个数据帧的【帧头】、【帧尾】,这样就能区分每一帧了(帧头的值ascll字符值是SOH[Start Of Header],16进制是01H;帧尾的值ascll字符值是EOT[End Of Transmission],16进制是04H)
(这里因为太久没学进制知识点老子傻逼了,把H当成了符号13,这里补充回忆:
十六进制的符号是:H或0x,例如【01H】或【0x01】
八进制的符号是:O或0o,例如【01O】或【0o01】
二进制的符号是:B或0b,例如【01B】或【0b01】
)
缺点:
其本质还是一个字节的比特数,那么【数据部分】也可能会出现这样的数值,但是数据链路层会误认为这个数据部分的内容就是【帧头】或【帧尾】
这里我用java的字符串代码规则会好理解,比如学过java的都知道一个字符串要用双引号""包起来,但是如果我这段字符串里面本身还想表示""怎么办?比如下面,java解释器会误认为" 我叫"是一段字符串、"岑梓铭"是一段、然后后面还缺一半"
;
;
那就再搞一个转义字符(ESC,Escape Character),就像java里的 “\” 一样。这个转义字符是00011011,它会标识在数据部分里和【帧头】或【帧尾】数值一样的地方,来告知数据链路层:“这只是一个普通数据部分,要保留”
用java代码解释就是在一些特殊字符前加 “\”,就会知道后面这个字符没有特殊意义,只是一个普通的字符
;
;
然后还有问题!如果【数据部分】还有跟【转义字符】数值一样的怎么办?就像java代码里的字符串里还想表示普通的 “\” 字符怎么办?
;
;
那就直接在【数据部分】里还有跟【转义字符】数值一样的地方前面再加一个【转义字符】,再次标识这个只是一个普通数据部分,要保留
就像java里想表达普通的 “\”,那就只能 “\\”
3、零比特填充法(应用于HDLC、PPP协议)
总结:规则就是每一帧由【特殊比特串】+【数据部分】+【特殊比特串】组成,【特殊比特串】来切割这一帧的界限;缺点是数据部分也会出现和【特殊比特串】一样数值的内容,解决办法就是在数据部分的这些地方前面加【转义字符】来标识这一块只是普通数据
跟【字节填充法】是类似的,也是用特殊数值作为一个帧的界限,但是区别是:
- 1、【字节填充法】是定好了一个帧的【帧头】和【帧尾】;对于缺点的解决方法是用【转义字符】来标识区分数据部分的跟特殊字符一样数值的部分
- 2、【零比特填充法】是直接指定好一个【特殊比特串(标识号)】,即代表开头也代表结尾,反正就是两个这样的标识包一个帧。【特殊比特串(标识号)】的值是01111110,十进制是126!
跟【字节填充法】同理,缺点都是在数据部分会含有和特殊界限符号一样数值的部分,但是区别是:
- 1、【字节填充法】是在数据部分那用【转义字符】标识,是一整个字节,而且在该数据部分前
- 2、【零比特填充法】是因为可以发现特殊界限符号是【2个0包着6个1】,那么只要数据部分不让它出现连续的6个1不就不会出现 “乌龙” 的情况了嘛;所以数据部分只要连续出现了5个1就要往后面填一个0,作为一个标识符
- 然后下一次数据链路层读到了5个1看到后面是【0】,就知道这个是数据部分,就会去掉这个只作为标记的【0】,然后接着往下读;如果连续读到5个1后面还是【1】,那就是连着6个1,那当前这个01111110字节就肯定是特殊界限符号了,一整个字节全部拿掉
- 也就是说区别就是,【零比特填充法】是只用1位0来标识数据部分的 “乌龙数据”,而且在数据部分后面(字节填充法是在该数据前面标识)
4、违规编码法(其实是利用物理层的检错)
总结:规则搭配【物理层】合作,用曼切斯特编码来组帧、识别帧,不跳变的这个信号周期就是帧的开头或结尾,跳变的就是数据部分
这个相对来说会简单一些,就是直接用曼切斯特编码,曼切斯特编码的特点是每个信号周期一定会跳变,那么只需要让每一帧的开头和结尾用不跳变的信号来标识,当检测到这一信号周期没有跳变,就会发现这是一个违规编码,也就是这一帧的界限(头或尾)了
那既然要涉及曼切斯特编码,就要搭配【物理层】的配合了,物理层会配合发送信号以及识别曼切斯特编码
注:
1、局域网IEEE 802 标准采用了;
2、因为不使用任何填充技术,所以只适合采用【冗余编码的特殊编码环境】,具体是啥先不用理解,记住就行
5、总结
注意:这4种不同的组帧方法是对应不同协议的规定,那么其中【HDLC】和【PPP】这两协议使用的是【零比特填充法】
还有:1、因为【零比特填充法】在硬件层面实现得更简单,所以【零比特填充法】优于【字节填充法】;2、因为【字符计数法】易错、【字节填充法】复杂又难兼容,所以目前常用的是【零比特填充法】和【违规编码法】
四、差错控制
1、要解决的问题和方案
首先这个任务要解决的问题是接收方要检查出每一个帧内部的“位错”,因为在数据传输的过程中因为电平变弱变强可能会出现比特错误,也就是0变成1,1变成0
2个解决方法,要么接收方使用“检错编码”发现帧错了,打回叫发送方重新来;要么接收方用“纠错编码”发现错,然后自己幸苦点自己修改
针对【检错编码】和【纠错编码】有各自的技术,下面细讲
这里还需要留意几个知识点:
- 误码率:BER,表示 “错误比特位 / 传输比特总数”
- FCS:每个协议里,帧尾会有一个字段FCS就是用来用 “差错检测码” 来检查位错误的,用 “奇偶校验法”、“CRC校验码(循环冗余码)”还是...
2、检错编码
【检错编码】又分为【奇偶校验法】和【CRC校验码(循环冗余码)】
- 奇偶校验法:
- CRC校验码(循环冗余码):
1)奇偶校验法
简单说就是会对每一个帧添加一个【校验位】,根据统计这个帧里的【1】的个数来判断正确错误
规则就是:奇偶校验法还分为【奇校验】和【偶校验】,发送方跟接收方要规定好用的是哪一种,(就好比我们比力气选择用掰手腕比,然后还可以规定是用握拳掰、还是握手式掰)
然后根据这两种方式的规则如下:
以上规则是针对我们人类思维的推算,正常的我们人类假设看到的奇偶校验法是可以通过直接计算【1】的个数来比对【奇校验的校验位】或【偶校验的校验位】判断对错
但是这其实并不能精准检查出差错,通常只能检查出【奇数】个比特位出现错误,如果是【偶数】个比特发生错误就检查不出来了
打比方:
(奇数个比特错误)
发送方想发【101101】这么一帧数据,用人眼计算是4个1 (偶数个1),那么【奇校验的校验位】是1,【偶校验的校验位】是0;
然后到了接收方后有任意1个(奇数个)比特位跳变了,比如变成了【101100】,那么正确的【奇校验的校验位】应该是0,【偶校验的校验位】应该是1,和原来的【奇校验的校验位】1,【偶校验的校验位】0都不一样
那就说明这段帧肯定有错!能检查出错误(简单来说本质就是【奇数+奇数】会变成偶数,就会被发现)
;
(偶数个比特错误)
发送方想发【101101】这么一帧数据,那么【奇校验的校验位】是1,【偶校验的校验位】是0;
然后到了接收方后有任意2个(偶数个)比特位跳变了,比如变成了【101110】,那么正确的【奇校验的校验位】应该是1,【偶校验的校验位】应该是0,和原来的【奇校验的校验位】1,【偶校验的校验位】0还是一样
那就没办法发现错误!!但是事实就是有两比特发生错误了!!!简单来说本质就是【奇数+偶数】还是奇数,不会被发现)
;
不喜欢看文字解释可以看下图:
另外上面是人脑计算的结果,计算机并不会直接计算出【1】的个数,而是是用【异或门】的电路来实现,这是回顾计算机组成原理的知识点
异或算法就是:相同(11或00)结果就0,不同(10或01)结果就1
那么发送方会把原数据通过【异或运算】求出【校验位】,然后把校验位和数据本身全都发给接收方;而接收方不再是只对数据部分计算,而是包括【校验位】在内整个数据进行【异或运算】,如果结果是0就是正确,如果是1就说明发生了比特跳变错误
;
下面用偶校验的方式来演示异或运算原理
(编程里通常表示0是没有,1是有,那这里就理解成0是没错,1是有错)
那么也可以发现,当偶数个比特发生错误时,不会被发现
总结
2)CRC校验码(循环冗余码)
【1】CRC循环冗余校验码的思想
简单抽象来看,其实就是发送方和接收方互相约定好【数据信息 + 校验位】是一个【被除数】,然后约定好一个【除数】,发送方会根据这个约定好的除数调整好校验位,以使得被除数最终能够除数完整地整除;
因此假如接收方收到信息之后重新用【被除数】➗【除数】,能够完整整除代表信息对的,有余数就说明信息有变有错!
【2】CRC循环冗余校验码具体规则
循环冗余码(CRC校验码)的规则可先简单看下图:
那么对于上图不理解的,我这里做详细解释:
首先看【除数】
;
首先发送方和接收方具体的约定其实是一个生成多项式G(x),比如G(x)=
;
那么我们用2进制转10进制的方法来看这个式子,其实
G(x) =
=
(系数只有1或0)
;
因此【除数】就是这G(x)的系数:1101
然后再看【被除数】,现在假设数据信息是:101001
CRC编码约定【数据信息+校验位】是一个【被除数】
【信息位】就是我们已知的信息数据101001
【校验位】还没确定,是要算的,但是CRC编码规定了校验码的长度是G(x)的最高次幂项的幂数,那么
就代表校验码是3位
;
那么现在先暂定让信息位左移3位,也就是补000在右边校验位,暂且当作 “初始”【被除数】是:101001000
(因为说过了,校验位还没算出来,现在要做的事就是算出校验码,所以这不是最终的【被除数】)
接下来要用【模二除】的运算方法来计算出【校验码】
【模二除】就是二进制的除法,但是跟十进制的除法的区别是:
- 1、每除一次的时候只看第一位,第一位是0就写0,是1就写1
- 2、然后两数相减的时候每一位是用的是【异或】计算,然后后面补1位作为下一次的被除数
看图,101001000除1101最终得到结果是110101,余数是0001,然后这个余数就是【校验码】,因为【校验码】是3位,所以余数最终结果是001(去掉最高位的0)
那么最终确定好的【被除数】就是【信息位 + 最终确定的校验码】,也就是101001001
;
这样的意义何在呢?
其实就是因为【信息码】和【除数】是约定好的不会变的数了(信息本身就是要传的不变的数据,除数的G(x)的最高次幂),然后假如直接用 【信息码】÷【除数】是不一定能保证会被整除没有余数的
而发送方、接收方判断传输数据是否有误的依据就是————要让【被除数】÷【除数】都为0的情况才为数据正确;
;
那么只好根据已定的【除数】,利用【信息码】再加【校验码】进行推算,就可以动态地凑出一个可以被【除数】整除的【被除数】,这样发送方和接收方才能根据【被除数】➗【除数】是否等于0来检验数据。
最后再看几一个问题:CRC编码能否【纠错】?
;
【检错】的意思是只能知道这一串比特流出错了
【纠错】的意思是不但发现错,还能知道具体错在哪一位!
;
那么CRC编码既不能完全纠错,也不是不具备纠错能力,他具有纠错能力但是要有前提条件!!
比如上面那个例子:101001001,整个CRC编码有9位,而校验码只有3位,也就是3位的二进制余数,最多只能表示2^3=8种可能,所以8种校验码没办法精确判断8位以上的CRC编码具体哪一位错了
注意:省略了【000】的正确情况
;
但是假设把信息位或者校验位调整一下,比如信息位4个,校验位3个,那么整个CRC编码就是7位,而2^3=8 > 7,这下校验码就可以一一对应上具体哪一位出错了
所以,我们知道若让CRC编码具有纠错的前提是:当信息码K位,校验码为R位,那么2^R-1(排除000正确情况)要大于K+R,也就是校验码的所有除了0的组合情况要大于整个CRC编码的位数,才能纠错
;
另外再记一下:可以检测奇数个错误、可以检测所有双比特错误、可以检测所有小于等于校验位长度的连续错误,这三点,不用管为啥我也懒得知道,反正可能会考......
总结
做道题练练手:
3、纠错编码(海明码)
这玩意巨特么抽象,在我花了很长时间去尝试理解王道课程的讲解之后总结了一个方法:没办法理解,他这个玩意就是前人花了几年时间自己研究出来的,我们直接记使用规则然后用就行,别尝试去理解原理是为什么!!!
1)原理理解
先解释一下我们能理解的部分:海明码的原理就是在【奇偶校验码】的基础上,把信息位分成多个分组,对每一个分组都再进行奇偶校验法
那么假设原本要发的信息一共n位,那么我们把它拆分成k组,每一组都对应有1个奇偶校验的校验位,那么我们就有了 【2^k】 种校验状态!(注意误区:这些分组的信息位是不固定的!并不是1位或2位,要根据情况而定,后面会讲怎么确定)
2)规则套路牢记
怎么知道校验位需要几位?
那么这里需要记住一个条件公式,是必备条件,用它可以计算分组的位数:2^k >= n+k+1
;
至于为什么我也没明白,王道说的是因为 【这2^k种校验状态包括了n+k整个海明码每一位都可能出错的情况、还有1种全正确的状态 】......不懂是啥意思,没算明白,反正记住就行
你可以选择记下面这个表,或者你自己算:
;
例子:假设现在我们要发送1101这个信息,信息位n一共有4位,根据2^k >= n+k+1公式可得出,校验位k = 3位,用上面表格可以得出校验位k需要3位
怎么知道信息位、校验位要怎么组成一个海明码?
海明码的组成很奇葩,正常别的校验码都是校验位在前面、信息位在后,海明码是 “混插”:
;
校验位顺序遵循2进制规则:假设校验位是"abc",a在2^0=1位、b在2^1=2位、c在2^2=4位
信息位排序规则:在校验位排好之后,按低到高位 “找缝” 排进去即可
;
例子:
假设要发的信息位是【1010】,信息位是4、校验码计算得出是3位
那么校验码(还未知是啥):"xxx",就按2进制规则应从低到高排到:1、2、4位
然后信息位:"1010",就应该在校验码拍好之后从低到高排到:3、5、6、7位
怎么分组整个信息?怎么算出校验码?
在王道的讲解不是很清楚,这里我用的b站另一个博主的讲解:
首先先把海明码每一个位的位序的先用二进制列出来,因为校验码是3位,就用3位的二进制表示
然后我们假设第一个校验码是p1、第二个校验码是p2、第三个校验码是p3
既然3个校验码对应的分组就分别是:
——【二进制位序的最后一位是1】的“校验码”和“信息码”是一组:
——【二进制位序的中间一位是1】的“校验码”和“信息码”是一组:
——【二进制位序的第一位是1】的“校验码”和“信息码”是一组:
可以发现其实分组信息并不是单独拆开整个原信息,而是分组信息还会有重合的地方,比如p1和p2的分组就重合了3、7位
;
接下来对每个分组的【信息码】进行【奇偶校验法】就可以得出【校验码】了
(以奇校验为例)
(以偶校验为例),偶校验常用,因为适合用【异或运算】
怎么检错?又怎么纠错?
用刚刚的例子,假设发送中出现了一位信息码错误
那么对这个接收方信息进行【分组】,然后奇偶校验(这里以奇校验为例)
可以发现p1、p3得出的校验码是1,和发送方发来的海明码里p1、p3不一样,就算出错了,【检错】步骤完成
;
接下来【纠错】,可以发现p2没错,那么对应这一分组里的各个位分别是:2、3、6、7 都没错
然后因为我们可以发现这些分组是有重合的位的,那么p2已知所有位都没有出错,那就可以到p1、p3里把和p2重合的那些位先排除掉错误的可能
然后我们再对比一下p1 和 p3,既然已知p1和p3都有错的位,那么直接看他两重合的位就知道是哪几位错了!!
我打个比方:小红和小明有一个人说谎、小红和小张有一个人说谎、小张和小李都没有说谎,那么是谁说谎了......想明白了就知道p1、p3怎么纠出错的位置了吧
;
;
当然还有更简单的一张方法:
我们把接收到的海明码信息【分组】后,把包括校验码在内整个分组进行【奇偶校验法】
得到一个“新的校验码”,然后逆置:p1到最右、p2到中间、p3到最左,得到一个二进制数
;
这个新的二进制数就是出错的位置......
3)补充
这里还有一些知识点王道没讲,书上的题目又有,我真是服了
首先正常情况下,海明码默认具有【1位的纠错能力、2位的检错能力】
- 那么如果假设题目非要说要实现D位的纠错能力,那么【海明距(就是校验码的长度)】应该是2D+1(不知道为什么,懒得了解了,记住就行)
- 那么如果假设题目非要说要实现C位的检错能力,那么【海明距(就是校验码的长度)】应该是C+1(不知道为什么,懒得了解了,记住就行)
最后一个,正常情况下海明码没法纠正2位的错误,那么还需要在整体海明码前再再再再加一个【全校验位】,然后再次进行整体奇偶校验法(通常用偶校验),然后根据 “全体校验的成功失败” + “各个分组校验的成功失败”,可以区分是1位还是2位出错
1位出错,就自己纠正;2位出错,就整个打回去让发送方重传