第二讲:物理层与数据链路层实战
各位同学,今天我们聊点实在的。
网卡怎么工作?MAC地址到底干嘛用?交换机怎么知道把数据往哪扔?还有——怎么用Python把原始数据包抓出来看看?
这些问题,我当年刚入行时也一头雾水。后来在交易系统项目里,被一个网络延迟问题折磨了三天,才真正搞明白这些底层原理。嗯,今天我把这些经验掰开揉碎了讲给你听。
一、网卡工作原理:从比特到帧的魔法
网卡这东西,说白了就是电脑和网线之间的翻译官。
电脑里跑的都是0和1,但网线上传的是电信号(或者光信号)。网卡的工作就是:
- 发送方向:把内存里的数据包,转成能在网线上传输的比特流
- 接收方向:把网线上的电信号,还原成电脑能识别的数据帧
我个人习惯把网卡理解成一个「硬件级的协议栈」。它不光做信号转换,还负责:
- 检查帧的合法性(比如CRC校验)
- 过滤非本机的MAC地址(除非开启混杂模式)
- 处理中断,通知CPU“数据到了”
关键点:网卡工作在物理层和数据链路层的交界处。它既处理比特流(物理层),也处理帧结构(数据链路层)。
我在项目中遇到过一个问题:某台服务器的网卡收包速率突然下降。排查了半天,发现是网卡的接收队列被占满了,导致新来的数据包直接被丢弃。嗯,这就是所谓的「网卡丢包」,跟软件没关系,纯硬件层面的事。
二、MAC地址与帧结构:数据链路层的身份证
MAC地址,就是网卡的身份证。全球唯一,出厂烧录。
但注意——这个唯一性只在局域网内有意义。出了局域网,你靠的是IP地址。
数据链路层的帧结构长什么样?我直接给你看一个典型的以太网帧:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 前导码 | 7 | 同步时钟用,接收方靠它对齐 |
| 帧起始定界符 | 1 | 标志帧的开始 |
| 目标MAC地址 | 6 | 接收方网卡的MAC |
| 源MAC地址 | 6 | 发送方网卡的MAC |
| 类型/长度 | 2 | 标识上层协议(如0x0800表示IPv4) |
| 数据载荷 | 46-1500 | 实际传输的数据(IP包) |
| 帧校验序列(FCS) | 4 | CRC校验,检测传输错误 |
你想想看,一个数据包在网络上跑,每经过一层,就加一层头。到了数据链路层,就套上这个MAC帧的外壳。
避坑指南:我曾经在调试一个交易系统的网络问题时,发现数据包偶尔会损坏。查了半天,原来是网线质量太差,导致FCS校验失败,交换机直接丢弃了这些帧。所以——别小看物理层,网线、光模块这些“硬件”往往是最容易出问题的地方。
三、交换机转发逻辑:MAC地址表是核心
交换机怎么知道把数据包往哪个端口送?
答案就藏在它的MAC地址表里。
交换机的转发逻辑其实很简单:
- 学习阶段:收到一个帧,记录源MAC地址和入端口号
- 查表阶段:根据目标MAC地址,查找对应的出端口
- 转发/泛洪:查到就单播转发,查不到就广播到所有端口(除了入端口)
说白了,交换机就是个「智能转发器」。它不像集线器那样无脑广播,而是精准地把数据送到目标设备。
重要:MAC地址表是有时效的。默认老化时间通常是300秒。如果设备长时间不发数据,表项会被删除,下次就要重新学习。
我记得有一次,交易系统的网络拓扑里加了一台新交换机,结果部分交易指令延迟飙升。排查后发现,新交换机的MAC地址表老化时间设置得太短,导致频繁触发泛洪,增加了网络负载。嗯,这种细节,文档上不会写,但实战中就是坑。
四、用Python抓取原始数据包
理论讲完了,来点实战。
Python里抓包,最常用的库是scapy。它可以直接操作数据链路层的帧。
先安装:
pip install scapy
然后写个简单的抓包脚本:
from scapy.all import *
def packet_handler(pkt):
# 检查是否有以太网层
if Ether in pkt:
src_mac = pkt[Ether].src
dst_mac = pkt[Ether].dst
# 检查是否有IP层
if IP in pkt:
src_ip = pkt[IP].src
dst_ip = pkt[IP].dst
print(f"源MAC: {src_mac} -> 目标MAC: {dst_mac}")
print(f"源IP: {src_ip} -> 目标IP: {dst_ip}")
print("-" * 50)
# 开始抓包,count=0表示持续抓取
sniff(prn=packet_handler, count=0)
这个脚本会持续抓取经过本机网卡的所有数据包,并打印出MAC地址和IP地址。
如果你想看原始帧的十六进制数据:
from scapy.all import *
def raw_packet_handler(pkt):
# 获取原始字节
raw_bytes = bytes(pkt)
# 打印前64字节
print(raw_bytes[:64].hex())
print("=" * 50)
sniff(prn=raw_packet_handler, count=5)
注意:抓包需要管理员/root权限。在Linux下用sudo运行,Windows下以管理员身份运行。
我个人习惯在抓包时先过滤一下,避免数据太多。比如只抓特定端口的包:
sniff(filter="port 80", prn=packet_handler, count=10)
这个filter语法和tcpdump一样,支持BPF表达式。
实战技巧:我曾经用scapy写过一个工具,专门监控交易系统的网络延迟。原理很简单——记录每个数据包的时间戳,然后计算发送和接收的时间差。但要注意,scapy的抓包性能有限,高并发场景下会丢包。生产环境还是得用libpcap或者DPDK。
五、知识体系总览
下面这张图,把本章的核心逻辑串起来了:
这张图把物理层、数据链路层和交换机的关系画清楚了。你仔细看——物理层在最底层,负责比特传输;数据链路层在它上面,负责帧的封装和MAC寻址;交换机则横跨这两层,既处理物理信号,又维护MAC地址表。
实战中,这三层是密不可分的。你抓包看到的是数据链路层的帧,但帧能传过来,靠的是物理层的网线和网卡。
好了,这一讲的内容就到这里。物理层和数据链路层是网络协议的基石,搞懂了它们,上层协议(IP、TCP、UDP)才能站得稳。
下一讲我们会深入网络层,聊聊IP路由和子网划分。到时候我会分享一个我在交易系统里做网络分层的真实案例,保证让你有收获。