从物理层到CSMA/CD:以太网通信原理与故障排查

发布时间:2026/11/3 3:00:08
从物理层到CSMA/CD:以太网通信原理与故障排查

简介:面向网络初学者、嵌入式开发者以及需要复习网络基础的人群,这份物理层讲解文档从数据封装与解封装入手,串联电路交换、报文交换和分组交换的差异,帮助读者理解应用层数据如何逐层加上报头,并经网卡与传输介质变为比特流。压缩包内共1个docx文件,约3.46MB,以图文讲解方式组织,适合按章节阅读和整理笔记。内容结合快递包裹类比说明封装过程,并展开同轴电缆、双绞线、光纤等传输介质,以及10BASE5、共享型网络、冲突域、CSMA/CD载波侦听与退避重发等以太网基础概念;同时说明分组交换通过分片、报头标识和固定传输大小提升链路复用效率,并提到最小网络所需的网线、网卡与TCP/IP协议栈三要素。已有106人学习,可作为课程补充、实验预习或面试前查漏补缺的材料,帮助建立较完整的物理层知识框架。

1. 物理层:为什么“只传 0 和 1”的一层反而最难排查

很多人学网络基础,是从 TCP/IP 四层模型开始背的,物理层永远排在最后,像是“剩下的都归它管”的垃圾桶。可真到了设备现场,Ethernet 链路起不来、ping 时通时断、抓包一片空白,才会发现物理层是整个协议栈里唯一不提供补救机会的一层。上三层都在做逻辑上的补救——重传、校验、拥塞控制、超时重发——而物理层如果没把比特按正确的幅度、时序和编码规则送到对端,上面所有机制都只是在错误的输入上做无谓的运算。抓包软件看不到任何帧,不是因为它坏了,而是因为帧根本没被正确识别出来。理解物理层,本质上是理解“可靠通信的假设是从哪一刻开始成立的”。

2. 三种交换方式:分组交换为什么赢,代价又落在谁身上

2.1 电路交换:先占一条线,独占到底

电路交换的核心动作发生在通信之前:源端发出信令,沿途每个节点为这条连接预留一段固定资源(一个时隙、一段频段、一个端口),整条路径逐跳建立完成之后,数据才开始流动。这条通路一旦建成,就是端到端独占的,别人的数据进不来,你的数据也不会被别人挤掉。

这种设计带来两个直接后果。第一,传输阶段几乎没有排队——路径固定、资源预留,数据只需要承担传播时延,时延抖动极小,这正是语音业务最看重的性质。第二,资源在整个会话期间被锁死,哪怕你一句话不说,那段带宽也空占着。人说话时大约有 40% 到 60% 是静默,这部分容量在电路交换里就是纯浪费。除此之外,建链需要额外时间,中途任何一个节点故障,整条连接直接断掉,没有绕路的余地。

2.2 报文交换:用标识取代独占

报文交换把“独占”换成了“排队”。发送方把整段数据作为一个报文交给第一个节点,节点先完整收下来,校验无误后查表决定下一跳,再发出去——这就是存储转发。报文头部携带目的地址,所以同一条链路可以被多个报文分时复用,谁先到谁先走。

省掉了建链过程,链路利用率立刻上来了。但新的问题也很直接:报文长度没有上限。一个几十兆的报文占着链路发,后面等着的一串短报文全部被堵住,这就是最原始的队头阻塞;同时每个节点都必须准备足够大的缓冲区来装下最长报文,成本压不下去;最麻烦的是出错重传,末尾一个比特翻了,前面已经正确传输的部分全部要重来。

2.3 分组交换:把报文切成有上限的块

分组交换在报文交换的基础上只加了一条约束:单个数据块的长度必须有上限。发送前先把原始数据切成若干个不超过上限的分片,每片加上头部,头部里带源地址、目的地址和序号,接收端按序号重组。

这条“上限”是整个设计的支点,它同时解决了四件事:

约束带来的效果机制解释
单跳占用时间有界一个分组最长只占用链路L/R(长度/速率)时间,后面的分组最多等这么久
节点缓冲需求可控缓冲区按最大分组长度设计,不随应用数据量增长
重传粒度变小出错只重传一个分组,不用重传整个报文
流水线成为可能第 1 个分组在第 2 跳转发时,第 2 个分组已经在第 1 跳发送

流水线这一点最容易被低估。报文交换里,整个报文必须完整到达节点才能转发;分组交换里,节点收到第一个分组就能立刻往下一跳送,整条路径上同时有多个分组在“飞”。端到端吞吐不再受最慢一跳的完整传输时间限制,而是接近瓶颈链路的速率。

代价同样清晰。每个分组都要背一份头部开销,按 IPv4 + TCP 各 20 字节、数据段 1460 字节算,头部占比约 2.7%,链路层还有 14 字节帧头和 4 字节尾部校验。更要命的是排队:分组到达是突发的,出口速率固定,队列长度随时在变,于是时延抖动、乱序、丢包全都出现了。后面那些复杂的传输层机制——滑动窗口、拥塞控制、快速重传——本质上都是在给分组交换的这个副作用打补丁。

2.4 不公平性从哪来:统计复用没有预留

题目正文里点出一个容易被忽略的现象:数据大小不一致时,转发会出现不公平。这个不公平不是实现缺陷,而是统计复用的必然结果。链路带宽不做任何预留,谁在这段时间内提交的分组多,谁就占走更大比例的输出能力。一条大流量流可以把队列填满,让交互式小流的分组排在后面,时延从毫秒级涨到几百毫秒。

我一般会这么理解“规定一个固定大小”这件事:它的第一目的不是公平,而是给单次占用时长封顶,让排队时延有一个可计算的上界。真正的公平需要额外的调度器——加权公平队列按流分配权重,令牌桶给每条流限速整形,这些都是在分组交换之上再叠一层策略。物理层和链路层的长度上限(MTU)只保证“每个分组最多占用这么久”,不保证“每条流拿到相同的份额”。把这两件事分开看,很多网络行为的因果关系就顺了。

提示:分组长度上限是一个全局参数,改小能降低排队时延、提升交互性,但头部开销占比会上升,吞吐下降。这个取舍在嵌入式场景里尤其明显,小包高频的场景和批量传输场景的最优值并不相同。

3. 封装与解封装:从应用数据到线路上的比特流

3.1 快递包裹模型:每层只加自己关心的头

封装的过程可以类比成寄快递。你把要送的小物件放进盒子,快递点在外面套一层防水袋、贴一张面单,中转站再加一张分拣标签,最终运输车上看到的是贴着各种标签的包裹,而收件人拆到最里面,要的只是那个小物件。每一层只关心自己贴的那张标签,不关心里面装了什么。

对应到协议栈,一次发送的完整结构是这样的:

[以太网头 14B][IP 头 20B][TCP 头 20B][应用数据 N B][以太网尾 FCS 4B] 目的MAC/源MAC 源IP/目的IP 源端口/目的端口

链路层加的是 MAC 地址和帧校验,负责“这一跳交给谁”;网络层加的是 IP 地址,负责“最终送到哪”;传输层加的是端口号,负责“交给哪个进程”。三个头部依次嵌套,任何一层都不需要解析上层的负载内容。

用代码把这个过程具象化会更清楚:

import struct def build_frame(payload: bytes, src_mac: bytes, dst_mac: bytes, src_ip: bytes, dst_ip: bytes, src_port: int, dst_port: int) -> bytes: # 传输层:源端口 + 目的端口 + 序号 + 校验和占位,共 20 字节 tcp_hdr = struct.pack('!HHIIHHHH', src_port, dst_port, 1, 0, 5 << 12, 65535, 0, 0x0000) # 网络层:版本+IHL、总长度、标识、TTL=64、协议号 6 表示 TCP total_len = 20 + len(tcp_hdr) + len(payload) ip_hdr = struct.pack('!BBHHHBBH4s4s', 0x45, 0, total_len, 0, 0, 64, 6, 0, src_ip, dst_ip) # 链路层:目的 MAC + 源 MAC + 类型 0x0800 表示上层是 IPv4 eth_hdr = dst_mac + src_mac + struct.pack('!H', 0x0800) frame = eth_hdr + ip_hdr + tcp_hdr + payload # 尾部 FCS 由网卡硬件计算并追加,软件侧不重复计算 return frame

build_frame依次拼接三层头部,最后返回的是交给网卡的字节串。几个参数值得说明:0x45拆开看是版本 4 加首部长度 5(单位 4 字节,即 20 字节),这两个字段被压在一个字节里;5 << 12是 TCP 首部长度字段,同样是 4 字节为单位;struct.pack的!前缀表示网络字节序,也就是大端。真实的协议栈不会这样一次性拼完,而是让每层维护自己的缓冲区,只在需要时把头部预留出来,避免数据被反复拷贝。

3.2 解封装:逐层剥离与逐层校验

接收方向是反过来的,但多了一件事:每一层都要校验,且校验失败的处理方式不同。链路层收到比特流后先做帧同步,找到前导码和帧起始符,算出 FCS 比对,不匹配直接丢弃,上层毫不知情;网络层检查版本、首部校验和、目的地址是否属于自己;传输层检查端口有没有对应进程监听,没有就回一个重置。

这里有个关键的不对称性:链路层的校验只覆盖“这一跳”,任何一跳的中继设备都会重新生成新的帧头和 FCS,原始帧的校验值不会端到端保留。所以链路上一切正常,也不代表数据没被改坏——中间某个节点的内存错误可以被下一跳的 FCS 完全掩盖。端到端的完整性要靠传输层校验和,而传输层校验和本身也只是 16 位反码求和,强度有限。

物理层在这个链条里做的事情最“笨”:把帧的每一个字节按位展开,按编码规则变成电平变化,送上介质。它不看头部、不查地址、不判断内容,只保证时序和电平符合规范。也正因为如此,物理层的错误全部以“帧根本没被正确接收”的形式体现,而不是以“收到了一个错误的帧”的形式体现。

3.3 最小网络三要素:网线、网卡、协议栈

把任何两台设备连成一个能通信的最小系统,缺一不可的是三样东西。传输介质负责把信号从一端送到另一端,网线、光纤、空气都算;网卡负责数字域和模拟域之间的转换,把并行的字节变成串行的比特流,做编码、做 CRC、做电气隔离;协议栈负责解释这些比特的含义,决定发什么、什么时候发、收到之后交给谁。

这三者的分工在今天的硬件上已经被高度整合。绝大多数网络芯片把 MAC 和 PHY 集成在一起,协议栈也固化进芯片或操作系统内核,买来的设备只要能联网,基本都自带完整的 TCP/IP 实现。这种整合省掉了开发者大量工作,但也带来一个副作用:物理层变成了彻底的黑匣子,出问题时你既看不到电平,也看不到编码过程,只能从上层现象倒推。理解三要素的边界,就是知道该在哪一层去找证据。

4. 冲突域与 CSMA/CD:共享介质上的规则

4.1 同轴电缆与 10BASE5:一根线承载所有设备

早期的以太网用同轴电缆组网,一根粗缆贯穿整个楼层,每台设备通过一个耦合器“刺”进这根缆的护套,直接接触到内导体。所有设备共享同一段导体,任何一个时刻发出的信号都会沿着缆向两个方向传播,被所有设备听到。这就是共享型网络的物理基础。

以太网标准的命名方式把关键参数都编码进去了,以 10BASE5 为例:10表示每秒 10 兆比特,BASE表示基带传输,即整个频带只传一路数字信号,编码方式为曼彻斯特编码,5表示单段最大传输距离 500 米。曼彻斯特编码的特点是每个比特周期中间必有一次电平跳变,接收方可以直接从跳变中提取时钟,不需要额外的时钟线——这在一条共享的总线上非常重要,因为所有设备必须严格同步才能正确采样。

共享型网络的问题在设备数量增加后迅速暴露。所有设备抢同一根线,同一时刻只能有一台在发,其余全在等;信号在线上传播有延迟,两端设备可能同时判断“线是空闲的”然后一起发;缆上任何一处断了,整个网段瘫痪。这根缆还很贵,接头工艺要求高,安装和维护都是一笔不小的开销。

4.2 CSMA/CD:先听后发、边听边发、冲突停发、随机重发

冲突无法通过协议彻底消除,只能被检测和化解。CSMA/CD 就是这套化解规则,四个动作依次进行。

先听后发。设备在发送前先监听介质上有没有电压摆动。如果线是安静的,说明当前没有人在发,可以开始。这一步依赖物理层的载波侦听电路,响应时间必须足够快,否则等它判断完,别人已经发了。

边听边发。发送过程中持续监听,因为冲突可能在发送中途才发生——信号传播需要时间,两台相距较远的设备完全可能在不同时刻各自判断“线空闲”。发送方必须能把自己的发送信号和收到的信号做比较,一旦发现不一致,就说明有别人在同时发。

冲突停发。检测到冲突立刻停止发送,转而发送一段强化冲突的干扰信号,长度足够让总线上所有设备都感知到这次冲突,避免有设备因为信号衰减而漏检。

随机延迟后重发。这是整套机制里最关键的一步。如果所有冲突方都等同一个固定时间再发,它们会再次同时发送,冲突永远无法收敛。所以每台设备从[0, 2^n - 1]中随机取一个整数k,等待k个时间片后再试,n是这条帧已经经历的冲突次数,封顶到 10,重试 16 次仍失败则丢弃并上报。冲突次数越多,可选窗口越大,退避时间越长,网络负载高时自动降低发送频率。这个策略叫二进制指数退避。

import random def backoff(attempt: int, slot_time: float, max_attempt: int = 16): """attempt: 当前是第几次冲突(从 1 开始)""" if attempt > max_attempt: return None # 放弃,向上层报错 n = min(attempt, 10) # 窗口指数封顶在 2^10 k = random.randint(0, (1 << n) - 1) # 从 [0, 2^n - 1] 中取随机数 return k * slot_time # 实际等待时间

slot_time是以太网的时间单位,10 兆速率下等于 512 比特时间,也就是 51.2 微秒。min(attempt, 10)这一行是收敛性的保证:如果不封顶,冲突次数一多,窗口会指数膨胀到不可接受的程度,网络恢复时间被拉得过长。封顶之后,高负载下退避窗口固定在 1024 个时间片,最坏等待时间可控。

4.3 最小帧为什么是 64 字节:冲突检测的时间窗口

以太网规定最小帧长 64 字节,这个数字不是随手定的,它直接由冲突检测的物理约束推出来。

设想最坏情况:A 在缆的一端,B 在另一端,两者相距最远。A 开始发送,信号需要一段时间才能传到 B。如果 B 在 A 的信号到达之前刚好判断线空闲并开始发送,冲突就发生了。这个冲突信号要传回 A,A 才能检测到。所以 A 从开始发送到收到冲突信号,最长需要等一个往返传播时延。

问题在于:如果 A 发送的帧太短,在冲突信号回来之前就已经发完了,A 会认为这次发送成功,根本不知道发生过冲突,也不会重传。这一帧就彻底丢了,而且没人负责。

于是约束变成:最小帧的发送时间必须大于等于两倍的最大单向传播时延。10 兆速率下,512 比特的发送时间是 51.2 微秒,对应往返传播时延上限 51.2 微秒,扣除中继器引入的延迟后,网络最大直径约 2500 米,正好是五段 500 米粗缆通过中继器串起来的长度,其中只有三段可以接设备。速率提升到 100 兆时,同样的 512 比特只需要 5.12 微秒,网络直径必须缩小到 200 米左右——这就是为什么高速以太网不能简单地把线拉长。

注意:这条约束意味着“短帧加长距离”是一个组合禁区。现场如果出现大量小于 64 字节的残帧(runts)计数增长,除了怀疑线缆质量,也要检查网段直径是否超出了当前速率允许的范围。

4.4 双绞线与光纤:冲突域是如何被缩小的

双绞线取代同轴,表面上是换了种线,实质上是把“一条总线”变成了“一条点对点链路”。每台设备用一根独立的线连到中心设备,物理上不再共享导体,冲突只可能发生在中心设备内部。用集线器时,中心设备把收到的信号向所有其他端口广播,逻辑上仍然是共享介质,仍然是半双工,冲突域只是被集中到了一处,规模反而更容易失控。

交换机改变了这件事。它内部维护一张 MAC 地址表,收到帧后只从目标端口转发出去,每个端口各自构成一个独立的冲突域,而且支持全双工——发送和接收各用一对线,同时进行,根本不存在争抢。冲突检测失去了存在意义,CSMA/CD 在实际网络中逐步退场,只在半双工配置下保留。

光纤把这条路走到了终点。点对点、全双工、不受电磁干扰、传输距离以公里计,冲突域这个概念在光纤链路上彻底消失。代价是成本和工艺:光纤不能随便弯折,接头需要熔接或冷接,端面污染会造成衰减,这些都属于物理层的“手艺活”。

双绞线这边,类别决定了能跑多快。Cat5 支持百兆,Cat5e 在串扰指标上做了改进,也常用于千兆,Cat6 增加了十字骨架隔离四对线,Cat6a 支持更宽频带。千兆速率下八根线全部投入工作:四对线同时双向传输,每对承担 250 兆,靠回声消除技术把发送信号从接收信号里减掉,从而实现同一对线上的全双工。百兆只需要两对——一对发、一对收,另外四根空闲。这个差异在现场排查时非常有用,后文会再提到。

5. 排查:物理层那些“玄学”故障的定位路径

5.1 五条踩坑记录

现象一:链路显示 up,速率协商成百兆,怎么都上不去千兆。原因通常在线序上。千兆需要四对线全部参与,如果水晶头只压了 1、2、3、6 四根,或者 4、5、7、8 接触不良,自协商会退回到只需两对的百兆模式,链路照样 up,误码计数也正常,只有速率不对。解决方式是先用测线仪逐芯测通断,再确认两端水晶头都按同一线序压接,最后看接口协商结果里的速率和双工字段。

现象二:小流量一切正常,一跑大流量就丢包,延迟飙高。大概率是双工不匹配。一端被手工强制成全双工,另一端还在自协商,自协商机制被破坏后对端只能退回半双工。半双工那一端会做载波侦听和冲突退避,而全双工那一端不管不顾地发,结果就是大量冲突和丢弃。证据是接口计数器里的冲突计数和晚冲突计数持续增长。解决方式是两端统一:要么都开自协商,要么都手工强制成相同的速率和双工。

现象三:链路偶发中断,CRC 错误计数缓慢上涨。这类问题最像玄学,因为它是渐进式的。常见原因是线缆长度超过 100 米、线缆走过强电区域、水晶头氧化、或者用了铜包铝的劣质线。信号衰减和近端串扰会让某些比特在采样时判错,FCS 校验失败,帧被丢弃,上层表现为零星重传。解决方式是缩短距离、换屏蔽或更高类别的线、避开动力电缆,然后用误码计数验证是否归零。

现象四:光纤链路起不来,或者起来了但光功率偏低。两个高频错误:单模和多模混用,以及收发没有交叉。单模纤芯直径约 9 微米,多模是 50 或 62.5 微米,两端模块类型必须一致,否则耦合损耗巨大。收发方面,本端的发送必须接到对端的接收,同一对纤不能 TX 对 TX。排查工具是光功率计,先测发送功率,再测接收功率,对比模块的灵敏度指标,能快速区分是模块坏了还是接错了。

现象五:老同轴网段冲突率异常高,小包大量丢失。同轴总线两端必须各接一个 50 欧姆终端电阻,用来吸收到达端点的信号能量。少接一个,信号在端点反射回来,和原始信号叠加,形成驻波,接收方采样到的波形严重畸变。解决方式很朴素:检查两端终端电阻是否在位、阻值是否正确,中间任何一段缆断开都会让整个网段失效。

5.2 从现象反推层级:先看计数器再看协议

物理层故障的排查顺序应该反过来:不要从协议开始抓包,先从接口计数器开始看。

现象优先看的证据常见指向
链路 down光功率、协商结果、线序介质或模块
链路 up 但速率不对协商速率、双工字段线序缺芯、自协商被关
零星丢包、延迟抖动CRC 错误、runts、冲突计数线缆质量、双工不匹配
完全收不到帧收发是否交叉、VLAN 与端口状态物理连接或配置

这套顺序的价值在于成本:看计数器只需要一条命令,换线缆要停机,抓包要复现。先把范围压到物理层,再去怀疑协议栈,能省掉大量无效排查。

6. 设计取舍:把复杂度放在哪一层

物理层的所有设计,本质上是同一个问题的不同答案:复杂度该放在哪里。

以太网选择了“简单网络、智能终端”。物理层只承诺尽力把比特送出去,不承诺送达、不承诺顺序、不承诺不重复,正确性交给上层的校验和重传。这个选择让物理层可以极度简化——不维护状态、不建连接、不做确认,任何一段介质坏了只影响局部,换掉就行。代价是端系统必须承担全部可靠性工作,协议栈变复杂,头部开销变大,重传带来额外延迟。

分组交换选择了“牺牲确定性换利用率”。电路交换能给出确定的时延上界,代价是静默期浪费带宽;分组交换把带宽利用率推到接近饱和,代价是排队时延不可预测、需要拥塞控制来防止崩溃。今天几乎所有数据网络都选后者,因为绝大多数应用能容忍抖动,却无法容忍带宽浪费。

硬件卸载则是在“谁来算”上做取舍。CRC 计算、校验和验证、帧的收发搬运,全部下沉到网卡,CPU 只处理有效载荷。嵌入式设备里这个取舍更极端:协议栈直接固化在芯片里,开发者调用几个接口就能收发数据,换来的是对底层行为的不可见。想调一个重传超时、想看一次冲突退避,往往发现根本没有这个旋钮。

我踩过一次印象很深的坑:某台设备批量传输时吞吐只有预期的一半,抓包看一切正常,应用层没有任何异常,日志干净得像新装的。查了两天才发现是网线只压了四根,链路一直跑在百兆,而所有上层指标都是健康的——没有任何报错,只是慢。从那以后我每次接手一个网络问题,都强制先走一遍物理层:看协商速率、看双工、看 CRC 和冲突计数、看线序,把这一层排除干净再往上查。这个习惯至少帮我省掉了一半的排查时间。

这份资料把交换方式、封装解封装、传输介质、以太网标准和 CSMA/CD 这几块串成了一条完整的线,从“数据怎么被切成块”一直讲到“块怎么变成线上的电平”,中间没有跳跃。想要把网络基础的底层逻辑一次理顺,可以顺着这份文档把物理层这一段彻底过一遍,后面再看 IP 和 TCP 的很多设计,会突然变得理所当然。希望帮到你。

本文还有配套的精品资源,点击获取