写在前面
首先我们举个例子,主机H1向主机H2发送数据。(本文图片来自网络)
主机H1向H2发送的数据经过三个路由器转发,到达目的地。
每个主机都有完整的五层协议栈。
每个路由器由于只转发分组,所以在结构上只有下面三层。
本小节我们讨论的是数据链路层,所以我们将其他的各个层看作是一个黑盒,也就透明的。
只关心数据在图中黄色水平线上各个数据链路层的传输。
想象数据是沿水平方向从左到右传递的~
数据链路层的三个基本问题
数据链路层的协议有很多种,但是数据链路层所讨论的基本问题是不变的,那就是封装成帧,透明传输,差错检测。下面分别来介绍。
封装成帧
有一点基础的人应该知道,数据链路层的上一层是网络层,网络层给到数据链路层的东西我们叫做IP数据报。
那么我们拿到到这个IP数据报之后呢,数据链路层就要开始发挥它的作用。
把IP数据报封装成帧就是一个数据链路层的基本问题之一。
帧是什么呢?你可以这么理解,网络层的数据我们称为IP数据报。到了链路层,这些数据又要被进一步加工,加工完的数据在数据链路层传输,这个数据就叫帧。
帧是数据链路层的传输单元。
封装成帧的过程就是数据加工的过程~
接下来我们看看数据链路层是怎么加工的。
在拿到数据链路层的数据之后,把IP数据报原封不动的放在中间,然后在两边分别添加一些数据,左边添加的数据叫首部,右边添加的数据叫尾部,合在一起就构成了帧。
两边的数据也叫帧首部和帧尾部。
首部和尾部有一个重要的作用就是帧界定。识别帧的开始和结束。
首部的最开始有一个字段叫帧开始字段,也叫SOH(start of header)
尾部的最末尾有一个字段叫帧结束字段,也叫EOT(end of transmission)
这样在媒体上进行传输的时候,就可以根据帧开始和帧结束来获得一个完整的帧啦。
帧的开始和结束标识在传输过程中出现差错时显得尤为重要。
接收端可以根据开始和结束字符来判断帧是否完整。
如果不完整的帧,就会丢弃。
原则上,从帧开始到帧结束就会被判定是一个完整的帧。也就是既有开始字段又有结束字段。
当然首部和尾部还包含了其他的一些信息,根据协议的不同会有差别。
透明传输
数据链路层的另一个基本工作是透明传输。
为什么会有透明传输的概念呢?上一节我们讲到了帧的开始和结束的字符。如果在帧的内容里边我们发现了帧的结束字符,那么这个帧就会提前被判断结束了。
所以正常情况下我们的帧中除了帧开始SOH和帧结束EOT,中间不允许再出现和这两个字符一样内容。
但是帧的数据内容IP数据报是由上层传下来的。难免会有与帧开始或者帧结束一样的字符。
所以透明传输的就是想说,不管你的内容是什么数据,哪怕你跟帧界定符是一样的,我也会想办法让你传输过去,最后拿到正确的完整的帧。
这里透明的意思就是哪怕你在,我也可以当你不在。
官方一点讲透明传输就是:无论什么养的比特组合的数据,都可以按照原样子没有差错的通过数据链路层。
那么想要达到透明传输,我们想到了什么办法解决这个问题呢?看下边。
字节填充(字符填充)
这里的填充和转义字符有异曲同工之妙。
有时候可能会遇到这种情况:换行时我们写\n,这个意思就是他不再是n了,进行了一个转义。然后如果我们要写,需要写成\
填充也是类似的,比如我们定义一个转义字符ESC,
我们在除去帧开始和帧结束的地方,只要遇到了帧开始或者帧结束字符,我们就往前边插入一个转义字符。
如果我们遇到了转义字符,我们也往前边插入转义字符。
如下图:
这样在接收端就可以正确的辨别帧开始和结束(前边没有转义字符)
接受之后在对数据进行解析:如果我们遇到了一个转义字符,我们就将他删除。如果遇到了两个连续的转义字符,就保留一个转义字符。
此时我们就把帧还原成了填充之前的样子。
这样一个帧,无论是什么样的数据,都可以正确的在数据链路层进行传输啦。
这也是透明传输的初衷:无论什么样的比特组合的数据,都可以按照原样子没有差错的通过数据链路层。
差错检测
数据链路层的第三个问题,差错检测。
数据的传输过程不一定是完全理想状态,有时候在传输的过程中0会变成1,1会变成0。
传输错误的比特比上全部的比特总数,就是误码率。
误码率与信噪比有很大的关系,在实际通信中不可能使误码率下降到零。
虽然误码率在当今已经非常小了。但仍然是不可避免的。
在数据链路层,我们是不可靠的交付,仅仅是尽最大的努力交付。
为了让数据传输更加的可靠,数据链路层所做的努力就是:差错检测!
目前使用最广泛的差错检测方法是:循环冗余检验(CRC)
循环冗余检验
循环冗余检验的一个关键就是要计算出即将要发送的这串数据的冗余码,也叫帧检验序列。
也就是说我们通过一串要发送的数据,计算出另一串数据,这串数据叫冗余码。
冗余码会存在帧尾部一个字段中,伴随着帧一起传输。
这样在接收端我们就能收到数据以及数据的冗余码。
发送端:冗余码的计算与发送
现假定待传输的数据M = 101001,一共是k位,(k = 6),除数p = 1101 ,一共是4位,(n = 3)比n多一位
这是我们在计算冗余码之前已知的数据。我们要传输的数据M、待传输数据的位数k,除数P,冗余码的位数n。
这里的P是由收发双方协商出来的,算法找到可以一个合适的除数P。有了除数之后除数的位数减一就是冗余码的位数n。
下面开始计算咯~
此时M = 101001, k = 6 ,P = 1101,n=3
(1) 用二进制的模2运算进行(2^n)乘M的运算,相当于在M后面添加n个0。即M后面添加3个0为101001000
简化:(1) M后边添加n个零 得到101001000
(2) 得到101001000(k+n = 9)位的数除以除数P(n = 3)位,得到商是Q(不关心),余数R =001(n位)R就是冗余码FCS
简化:(2) 用(1)的结果除以除数P,得到余数就是冗余码FCS。
现在加上FCS后发送的帧是101001001
也就是说这个帧依然是:帧首部、数据、帧尾部组成。只不过帧尾部中多了一个字段:帧检验序列,FCS,也叫冗余校验码,一起发送给服务端。
接收端:冗余码的检验
在接收端把接收到的数据M = 101001001以帧为单位进行CRC检验,除以除数P(1101),然后检查得到的余数R。
如果在传输过程中没有差错,那么经过检验后得到余数R肯定是0。
总之,在接收端对接收到的每一个帧经过CRC检验后,有两种情况:
(1)余数R = 0,则判断这个帧没有问题,就接受
(2)余数R != 0,则判断这个帧有差错,就丢弃。
因此,通过这种方法,我们可以知道哪些帧是有差错的,那些帧是正确的。
我们在链路层只能做到无差错的接收帧,但是没有办法校验是哪几位错了,因此也没有办法纠错。
因此在这一层,我们只能保证接收端接受的帧,是没有比特差错的。
如果没有接受,可能是冗余校验未成功,被丢弃了。
当然了,传输过程中并不是只有比特差错,也可能出现传输差错,如帧丢失了、帧重复了、帧失序了等等,这都不会归数据链路层去管。
数据链路层只管我接受到的数据完不完整,有没有比特差错。要么完整且没有差错直击交给上层,否则就丢弃了。
其他的问题交给上层来解决。
总之呢还是要明白数据链路层是不可靠的交付,它仅仅是尽最大努力交付。