
计算机网络学到第二篇恭喜你熬过了概述和物理层那些相对比较“干”的内容总算摸到一点真正协议的门道了。这篇笔记我结合了当时上课的讲义、教材和课后实践把第二站的内容整理成一份能直接照着学的路线图。上一篇我们聊了分层模型、电路交换和分组交换这些基础这一篇进入数据链路层也就是协议栈里“第一层有脑子”的地方。数据链路层这层我刚开始学的时候总觉得它很“憋屈”——上面有网络层发号施令下面有物理层干搬比特的粗活它夹在中间像极了一个传话的秘书。后来自己动手抓包、用交换机做实验才意识到这层才是整个网络通信里最讲究“规矩”的地方帧怎么封、错怎么查、冲突怎么解决、消息怎么点名确认全在这一层体现。这篇笔记适合两类人看一类是正在自学历计算机网络、被各种概念搞得头大的初学者另一类是已经工作、想回头系统补一遍基础理论的开发者或运维。相信我把数据链路层啃透后面学网络层和传输层会轻省很多。1. 这一篇到底在学什么从整体定位拆解学习主线1.1 数据链路层在网络里的真实角色先从一个最简单的问题入手物理层已经把比特流从一个设备搬到了另一个设备上传输过程是不是就完事大吉了不是。物理层只负责“把0和1送出去”但它并不知道这一串比特该从哪里开始读、到哪里结束也不知道中途有没有被噪声干扰传错更不知道这条链路上同时有多少个设备在说话。数据链路层干的事就是把物理层送来的原始比特流“切成一段一段的”给每一段加上头部和尾部变成一个一个的“帧”然后交给物理层发送。这个过程叫作封装成帧。帧的概念可以类比快递包裹你要寄一件衣服不会直接把衣服扔给快递员而是先套一个袋子填上寄件人、收件人、物品说明然后打包。帧结构里的“地址”“类型”“校验码”干的就是这些事。除了封装数据链路层还要保证“接收方能看清楚每个包的边界在哪里”这叫透明传输保证传出去的数据能发现错误这叫差错检测如果链路是多个设备共享的还要决定“谁先说话”的问题这叫介质访问控制。这几件事就是整层学习的主线。1.2 用一条逻辑线串起整层知识很多同学学这层会学成一盘散沙今天背帧格式明天看CRC后天搞CSMA/CD最后发现什么都记不清。我的经验是把数据链路层当成一个“点对点投递系统”来理解所有知识点都能串起来先解决包工包料的问题——发送方怎么把网络层下来的IP数据报包成能在链路上跑的帧这是封装成帧。再解决边界识别的问题——数据内容里如果恰好出现了和帧首尾一样的标志字节怎么避免接收方误判这是透明传输。然后解决传错的问题——帧发出去了接收方怎么知道这帧有没有被干扰这是差错检测CRC循环冗余校验。接着解决多设备抢道的问题——如果几个设备共享一条链路同时发数据不就撞车了吗这是介质访问控制典型代表是CSMA/CD。最后是接口问题——MAC地址和IP地址到底怎么配合工作的这涉及ARP协议也是理解“二层转发”和“三层路由”差异的关键。把这五个问题按顺序想清楚整章内容就自然串成一条线了。我学的时候习惯画一张流程图发送端从网络层拿到数据报加帧头帧尾、算FCS、发出去接收端收到后先查FCS、再确认边界、最后拆帧把数据交给网络层。学习时你自己动手画一遍这张图比看十遍课本都有用。2. 帧结构、CRC校验与差错检测最容易卡住的三个细节2.1 以太网帧结构深度拆解我们平时接触最多的就是以太网所以先从以太网帧说起。以太网帧的标准结构是前导码加帧体帧体一共至少64字节分为几个字段字段长度作用前导码8字节同步时钟不属于帧本身发送时由硬件自动添加目的MAC地址6字节收件人地址指明帧要送给哪个网卡源MAC地址6字节寄件人地址标明这帧是从哪块网卡发出的类型/长度2字节例如0x0800表示上层是IPv40x0806表示上层是ARP数据载荷46~1500字节真正的数据内容通常是一个IP数据报FCS帧校验序列4字节对整个帧体做CRC校验得到的校验码前导码很多人容易忽略它不算正式帧的一部分但非常关键。它是一串固定的101010...序列最后两位是11接收方的网卡靠它来锁定比特同步时钟。这就是为什么你说“物理层把比特流送来了”接收方却必须靠前导码才能知道“从哪一位开始算一个字节”。有个典型的理解误区总有人问“既然数据载荷写的是46~1500字节那我传一个很小的数据不足46字节怎么办”答案是填充。发送方会在载荷不够长度时填充一些字节让帧长凑够64字节的最小值。为什么非要至少64字节这跟3.2节要讲的CSMA/CD冲突检测机制强相关先记住结论后面展开。2.2 CRC校验手把手手算一遍CRC是循环冗余校验的缩写也是这章最容易让人一知半解的点。很多教材喜欢直接给“多项式除法”的概念把初学者吓退。其实说人话就是发送方利用一个生成多项式对数据做一个二进制除法得到的余数就是FCS附在数据后面一起发出去。接收方拿着整个帧再做一次同样的除法如果余数是0说明没有检测到错误如果不是0帧就是错的。我建议你亲手算一遍比看任何公式都管用。假设我们要发送的数据M是10位1010001101生成多项式是P110101对应x^5x^4x^21。计算流程是先在数据M后面补5个0因为生成多项式是6位、最高次是5余数就是5位然后做模2除法也就是按位异或不借位不进位。我把除法的关键过程写出来被除数101000110100000 除数 110101 第1步从最高位开始101000对110101做异或首位1够除 101000 ^ 110101 011101 第2步把下一格0拉下来变成111010首位1继续异或 111010 ^ 110101 001111 第3步继续把后面的0拉下来变成011110此时首位是0不够除商0 第4步再拉一个0下来变成111100首位是1异或 111100 ^ 110101 001001 第5步再拉0下来变成010010首位0商0 第6步再拉0下来变成100100首位1异或 100100 ^ 110101 010001 第7步最后再拉一个0下来变成100010首位1异或 100010 ^ 110101 010111 余数010111我这里稍微简化了过程实际手算时记得每一位都要拉到底、对齐处理。计算出来的余数R010111这5位就是FCS。发送方真正发出去的帧数据是原数据1010001101 FCS 010111合起来是1010001101010111。接收方拿到这14位后用同样的生成多项式110101再做一次模2除法如果余数为0就判定正确。我第一次手算CRC时犯了一个典型错误补零的个数搞错了。补零的个数不是“生成多项式的位数减一”而是“生成多项式最高次方”那么多位例如P110101最高次是5所以补5个0。这个细节很容易错做题时务必先写清楚生成多项式的阶数。2.3 关于“可靠传输”这点千万别理解偏了数据链路层的差错检测只能“检错”不能“纠错”。收到一个FCS校验失败的帧接收方的做法很简单直接丢弃不做任何通知。至于丢掉的帧怎么办那是上层协议的事通常是TCP的确认重传机制来负责。这一点让很多人困惑既然数据链路层能算出FCS为什么不对错误帧进行修复原因是修复的成本远高于重传。CRC只能告诉你“这帧不对”但不能告诉你“是哪几位被改错了”。想纠错就得用海明码那样的编码方式会大幅增加冗余数据对高速以太网来说代价太大。网络通信的设计哲学是“把可靠性的问题交给专业层解决”链路层保证“尽量不传错”并快速丢弃错误帧由传输层统一把关。我在实际分析抓包数据时发现一个特别有意思的现象有时抓包里能看到“TCP retransmission”的提示但对应的链路帧FCS并没有报错。这说明数据是在中间某条链路上丢的根本传不到接收端而发送端只能靠TCP超时重传发现。链路层重传在以太网这种高可靠的介质上收益不大但在无线局域网802.11里由于信道环境差链路层就加了确认帧的重传机制。学到这里你就能明白为什么同是“链路层”有线无线差别这么大——因为介质的可靠性完全不一样。3. CSMA/CD、MAC地址与交换机弄懂它们物理层知识才算真正贯通3.1 CSMA/CD是如何解决“多人共用一条路”的早期以太网是总线型的一根同轴电缆上挂了很多台电脑大家共用一条信道。这就好比一个房间只有一部电话但住了十个人所有人同时想打电话就会互相打断。CSMA/CD协议就是一套“先听后说、边说边听”的规则全称是载波监听多路访问/碰撞检测。规则分几步理解很清楚第一步发送方发送前先监听信道信道空闲才发忙就等第二步发送过程中要同时监听如果检测到信号幅度异常、说明有碰撞立即停止发送第三步停下来后发送一个强化冲突信号告诉所有人“刚才撞车了”第四步采用二进制指数退避算法随机等一段时间再重试。你可能会问为什么要“边说边听”因为在总线拓扑里信号从一端传到另一端是有延迟的发送方发出第一个比特后的短时间内其他设备可能还没收到信号如果它们这时也发数据就会撞车。碰撞是有窗口期的这个窗口期就是信号在总线上来回传播的最长时间叫冲突窗口数值上等于2倍的单程传播时延。这里引出那个经典的“最小帧长”计算。以太网的冲突窗口大约是51.2微秒在10Mbps的速率下发送255位只需要25.5微秒但如果发送方发出的帧太短可能在它检测到碰撞之前整帧就已经发完了——它根本不知道自己“撞了”。为了让发送方在发送过程中能感知到碰撞必须让一个帧的发送时间大于等于冲突窗口。算一下10Mbps × 51.2微秒 512比特正好64字节。这就是以太网最小帧长64字节的来历。所有小于64字节的帧都被认为是“冲突碎片”直接丢弃。还需要知道的是随着以太网交换机的普及现在的全双工链路已经不需要CSMA/CD了因为交换机的每个端口和主机之间是独立链路不存在共享信道竞争问题。但学这个协议的价值依然很大它教会你一套理解“共享介质访问控制”的思维模型后面学习无线局域网的CSMA/CA时你会觉得似曾相识。3.2 MAC地址与IP地址的分工很多人初学时搞不清一个问题既然每个设备都有IP地址为什么数据链路层还要搞一个MAC地址我的理解方式是做一个类比IP地址是“门牌号”MAC地址是“门牌编号对应的那块门牌本身”。门牌号可以因为搬家而改变但网卡上的MAC地址在出厂时通常固化了。MAC地址是数据链路层使用的物理地址长度为6字节前3字节是厂商编号OUI后3字节是网卡序列号。它解决的是“在同一个物理链路内帧该送给谁”的问题。而IP地址是网络层的逻辑地址解决的是“在互联网这个巨大网络中数据报该发往哪个目的网络”的问题。一个包要从一台电脑发到另一台城市的电脑中间跨越很多条链路在每一条链路上数据帧的源MAC和目的MAC是随路由器转发而变化的但IP数据报里的源IP和目的IP在一整段旅程中保持不变。这就是“MAC地址逐跳变化、IP地址端到端不变”的规律。把这两个地址搞混的人会在配置网络时出各种奇怪的问题。比如我做实验时曾经手动改了网卡的“本地连接”里的MAC地址结果同一局域网内能通信但出外网就断了——因为路由器上绑定了原MAC地址的ARP表项这就是MAC层和IP层交织的典型案例。3.3 交换机是怎么“自学成才”的交换机是数据链路层的核心设备它的“聪明”之处在于不需要人工配置路由而是通过不断学习帧中的源MAC地址建立一张MAC地址表。我常在课上强调交换机转发帧的依据就是这张MAC地址表记录的是“哪个MAC地址出现在哪个端口”。具体过程可以分三种情况说明第一种目的MAC地址在表中存在并且对应端口就是帧进入的端口那交换机就把帧“丢弃”了因为在同一个端口内两个设备通信没必要在交换机内部转发第二种目的MAC地址在表中存在且对应端口不是入口就只往那个端口转发第三种目的MAC地址不在表中也就是“未知单播”交换机就向除了入口之外的所有端口泛洪。通过学习交换机不需要人为设置转发表插上设备就能自动构建。我做过一个小实验用两台电脑接同一个交换机先互相ping一次然后在交换机上和电脑上分别查MAC地址表会发现电脑A的MAC地址出现在交换机连接电脑A的那个端口下。这就是交换机在学习过程最直观的证据。交换机的引入改变了“冲突域”的概念。传统集线器下所有端口共用一个冲突域任意两台设备同时发送都会碰撞而交换机的每个端口都对应一个独立的冲突域全双工模式下甚至没有冲突。但要注意交换机的所有端口仍然在一个广播域里广播帧会转发到所有端口。这也是“冲突域被交换机隔离广播域没有被隔离”这句口诀的出处。4. 动手验证与排错抓包、算码、拆包4.1 用抓包软件看真实以太网帧学数据链路层最忌讳只看书不动手我强烈建议你打开Wireshark亲手抓一个真实的以太网帧。操作不复杂找一台电脑打开Wireshark选一个正在通信的网卡比如已经联网的无线网卡或有线网卡设置过滤器为“arp”或“ip”然后去ping同网段的另一台设备比如网关等抓到包后展开Ethernet II那一段就能看到真实的帧头目的MAC、源MAC、Type字段。看这个帧头时你会发现自己对帧结构的理解立刻具象化了。我建议重点看三个信息一是Type值0x0800表明上一层是IPv40x0806表明是ARP二是目的MAC如果它是ff:ff:ff:ff:ff:ff这样的全F地址说明这是一个广播帧三是源MAC和你在命令行里用ipconfig /all或ifconfig查到的网卡物理地址是否一致。很多人在这一步才发现实际抓包看到的帧头和课本上画的示意图几乎没有差别。观察时要注意一个坑如果用无线网卡抓自己的ping包可能看到源MAC不是本机网卡的MAC而是路由器或AP的MAC。原因是无线网卡在接入无线网络后很多驱动默认开启了“无线扩展”模式部分抓包软件会看到AP转发的数据。遇到这种情况不用慌说明你的数据确实经过了无线链路的中继换一个有线网卡抓包会更干净。4.2 写一段代码对照CRC计算结果光手算一遍还不够我建议你再用代码快速验证一下把前面2.2节的例子跑通。CRC的代码其实不难核心就是模拟模2除法。我用Python写过一段最简单的实现def crc_remainder(data: str, generator: str) - str: # 在数据末尾补 (生成多项式位数 - 1) 个0 g_len len(generator) dividend data 0 * (g_len - 1) dividend list(dividend) generator_bits list(generator) for i in range(len(data)): if dividend[i] 1: for j in range(g_len): dividend[i j] str( int(dividend[i j]) ^ int(generator_bits[j]) ) return .join(dividend[-(g_len - 1):])用前面例子跑一下data 1010001101 generator 110101 remainder crc_remainder(data, generator) print(remainder) # 和手算结果 010111 一致接收端校验就更简单了把“数据 余数”整体作为输入再调用这个函数得到的余数如果是全0就说明校验通过。我第一次写这段代码的时候犯了个低级错误循环范围用的是len(dividend)把FCS后面的部分也算进去了结果校验结果一直是错的。正确做法是只对原始数据位做消位操作因为FCS本身只是“除法的余数”不需要再拿来参与消位。这个细节自己写一遍代码就能记住。4.3 初学者最容易踩的三个认知坑这一章里我踩过不少坑也帮别人排查过不少网络问题总结出三个高频误区。第一个误区是把“冲突域”和“广播域”当成同一个概念。我刚学完时也这么以为后来做了个小实验才彻底区分开用一台交换机连接三台电脑其中一台发广播包另外两台都能收到说明整个交换机组在一个广播域里但两台电脑互相传大文件时第三台电脑的网速几乎不受影响说明它们不在一个冲突域。简单说冲突域是“物理上可能发生碰撞的范围”广播域是“逻辑上广播能到达的范围”。第二个误区是“以为CRC能够纠正错误”。这个想法很诱人实际上链路层FCS只能检错不能告诉你是哪一位错了。网络中为了纠错付出的额外带宽成本很高链路层选择“检错并丢弃”可靠传输交给上层。理解这个设计哲学之后看很多网络协议的设计取舍都会更有感觉。第三个误区是“把FCS的作用范围搞错”。FCS校验的对象是整个帧从目的MAC开始一直到数据载荷的部分不包括前导码。有人抓包时发现帧数据内容改动但FCS没变就怀疑校验算法有误实际上是抓包工具会自动重新计算FCS原始线路上的FCS已经被网卡剥离或更新了。排查问题时可别被这个现象带偏。我在实际排查中还有一个小技巧当怀疑是链路层问题导致丢包时优先看交换机的端口统计里的CRC错误计数如果这个数字在持续增长大概率是网线质量差、接触不良或者端口双工模式不匹配。这个排查方向比反复ping、traceroute高效得多。很多人遇到丢包第一时间就去查网络层忘了看看链路层的物理错误计数器这是我们做网络排障时最常忽略、却也最有效的一步。这套笔记我前后整理了好几轮最大的心得体会是学习数据链路层一定不能只停留在背概念动手抓一次包、亲手算一遍CRC、用交换机做一次转发实验胜过看十遍教材。如果你正在学这章建议先别急着往后翻花一晚上把帧结构、CRC、CSMA/CD、交换机这几件事对着实验捋一遍你会发现后面学网络层时很多“路由器怎么转发”“ARP怎么工作”的问题都会变得顺理成章。本篇文章就是我在这一阶段最想留给自己的那份操作手册也是我想分享给同样卡在这里的朋友的一份避坑指南。