第四章 行情数据链路层协议解析
各位同学,今天我们进入一个非常关键的环节——链路层协议解析。说白了,就是搞清楚数据在网线上是怎么跑的。我在做FPGA行情加速卡的时候,这部分可是踩过不少坑,今天一并分享给你们。
4.1 以太网MAC帧结构
先看最基础的MAC帧。你想想看,数据从网口进来,第一个遇到的就是MAC层。它的结构其实不复杂,但细节很多。
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 前导码 | 7 | 101010... 用于同步 |
| 帧起始定界符 | 1 | 10101011 标识帧开始 |
| 目的MAC地址 | 6 | 接收方地址 |
| 源MAC地址 | 6 | 发送方地址 |
| 类型/长度 | 2 | 0x0800为IP,0x0806为ARP |
| 数据 | 46-1500 | 上层协议数据 |
| 帧校验序列 | 4 | CRC32校验 |
嗯,这里要注意:前导码和SFD在FPGA接收时通常被硬件过滤掉了。我刚开始做的时候,还傻乎乎地等前导码,结果发现RGMII接口直接就给吃了。所以你在写解析逻辑时,直接从目的MAC开始处理就行。
关键点:MAC帧的最小长度是64字节(含CRC),不足时会自动填充。行情数据包通常很小,这个填充机制要特别留意。
4.2 ARP协议解析与实现
ARP协议,说白了就是"谁有这个IP?告诉我你的MAC"。在FPGA里实现ARP,我建议用状态机来做,清晰又高效。
ARP报文结构是这样的:
// ARP报文格式(28字节)
typedef struct {
uint16_t htype; // 硬件类型,以太网为1
uint16_t ptype; // 协议类型,IPv4为0x0800
uint8_t hlen; // 硬件地址长度,MAC为6
uint8_t plen; // 协议地址长度,IP为4
uint16_t oper; // 操作码:1请求,2应答
uint8_t sha[6]; // 发送方MAC
uint8_t spa[4]; // 发送方IP
uint8_t tha[6]; // 目标MAC
uint8_t tpa[4]; // 目标IP
} arp_packet_t;
我在项目中遇到过一个问题:ARP请求来了,FPGA回复了,但PC就是不认。查了半天,发现是MAC地址字节序搞反了。以太网用的是大端序,而我的FPGA代码里直接按小端处理了。这个坑,你们一定要记住。
实战技巧:FPGA实现ARP时,建议维护一个ARP缓存表。用BRAM实现,深度256就够用了。每次收到ARP请求,先查表,有就直接回复,没有才广播。
4.3 IP协议头解析
IP头解析,这是行情数据链路层解析的重头戏。为什么?因为你要从IP头里拿到源IP、目的IP,还要检查校验和。
标准IP头是20字节(不含选项):
// IP头结构
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|版本| 首部长度 | 服务类型 | 总长度(字节) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 标识(16bit) |标志| 片偏移(13bit) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 生存时间(TTL) | 协议号 | 首部校验和 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源IP地址 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 目的IP地址 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
我个人习惯在FPGA里这样处理IP头:
- 先检查版本号,必须是4,IPv6直接丢弃
- 计算首部长度,一般是5(20字节)
- 校验和验证,这个必须做
- 检查目的IP是否匹配
- 检查协议号,UDP是17
注意:IP校验和计算是16位反码求和。很多新手直接用补码加法,结果校验永远过不了。正确的做法是:每16位相加,如果有进位就加回低位,最后取反。
4.4 UDP协议解析
UDP就简单多了。行情数据绝大多数走UDP,因为快啊!TCP那三次握手,对行情来说太慢了。
UDP头只有8字节:
// UDP头结构
typedef struct {
uint16_t src_port; // 源端口
uint16_t dst_port; // 目的端口
uint16_t length; // UDP长度(含头)
uint16_t checksum; // UDP校验和(可选)
} udp_header_t;
我曾经踩过一个坑:UDP校验和。有些系统把校验和设为0,表示不校验。但有些交换机或网卡会强制校验。所以我的建议是:FPGA接收时,如果校验和为0就跳过,不为0就验证。发送时,最好把校验和算对,省得麻烦。
核心要点:行情数据链路层解析,本质上就是逐层剥头的过程。MAC头→IP头→UDP头,每剥一层就检查一次,全部通过后才把数据交给上层处理。
4.5 整体解析流程
下面我用SVG画一个完整的解析流程图,你们可以对照着看:
这个流程,说白了就是一条流水线。数据从左边进来,经过MAC、IP、UDP三层过滤,最后才到应用层。我在做FPGA实现时,每一级都用独立的流水线阶段,这样吞吐量能跑到线速。
我的经验:在FPGA里做协议解析,建议用流水线架构。每一级只做一件事,比如第一级剥MAC头,第二级验IP校验和,第三级查UDP端口。这样时钟频率能跑得高,而且逻辑清晰好调试。
好了,链路层解析就讲到这里。记住一句话:协议解析的本质就是"剥洋葱",一层一层来,每层做好自己的事。下一节我们讲传输层,到时候会看到更精彩的内容。
公众号:蓝海资料掘金营,微信deep3321