3. TFTP协议详解:从原理到U-Boot实战
TFTP这个协议,说白了就是简化版的FTP。它没有认证,没有目录浏览,甚至连可靠传输都得靠应用层自己重传。但正是因为它简单,才成了嵌入式网络升级的标配。
我记得刚入行那会儿,第一次用TFTP给板子烧系统,死活传不过去。后来才发现,是防火墙把69号端口给拦了。嗯,这种坑我踩过不少,今天咱们就把它彻底讲透。
3.1 TFTP工作原理
TFTP(Trivial File Transfer Protocol)基于UDP,端口号69。它只做一件事:从服务器读文件,或者往服务器写文件。没有列表,没有删除,没有权限控制。
它的工作流程其实很简单:
- 客户端向服务器的69端口发请求
- 服务器分配一个临时端口(通常是随机高位端口)
- 双方在这个临时端口上完成数据传输
- 传输结束,连接关闭
核心要点:TFTP使用"停等协议"——发一个数据块,等一个确认,再发下一个。超时没收到确认就重传。
为什么会这样设计?因为TFTP最初是为无盘工作站设计的,那时候网络环境差,内存也小。停等协议虽然效率低,但实现简单,占资源少。
3.1.1 报文格式
TFTP报文头只有4个字段:
| 字段 | 长度 | 说明 |
|---|---|---|
| 操作码 | 2字节 | 1=读请求, 2=写请求, 3=数据, 4=确认, 5=错误 |
| 文件名/块号 | 可变 | 请求包是文件名,数据包是块号 |
| 模式 | 可变 | netascii/octet/mail,嵌入式常用octet |
| 数据 | 0-512字节 | 最后一个包小于512字节表示传输结束 |
注意这个512字节的限制。我见过有人传大文件时没处理好块号回绕,结果文件后半部分全乱了。TFTP的块号是16位,最大65535块,算下来单个文件最大约32MB。超过这个大小,就得自己实现扩展。
3.1.2 传输流程
读请求(RRQ)的完整流程是这样的:
- 客户端发RRQ到服务器69端口
- 服务器回ACK,同时分配临时端口
- 服务器发DATA块1(512字节)
- 客户端回ACK块1
- 重复直到服务器发完最后一块(小于512字节)
- 客户端回ACK最后一块,传输结束
个人经验:我习惯在服务器端抓包确认临时端口号。很多网络问题其实是临时端口被防火墙拦截了,而不是TFTP本身的问题。
3.2 U-Boot中TFTP客户端的实现
U-Boot的TFTP客户端代码主要在 net/tftp.c 里。说实话,这代码写得挺朴素的,没有花哨的优化,但胜在稳定可靠。
核心数据结构就两个:
/* TFTP会话状态 */
struct tftp_session {
uchar server_ip[4]; /* 服务器IP */
uchar our_ip[4]; /* 本机IP */
int server_port; /* 服务器临时端口 */
int our_port; /* 本机端口 */
int block; /* 当前块号 */
int total_blocks; /* 总块数 */
int total_size; /* 文件总大小 */
int last_block; /* 最后一块大小 */
int state; /* 会话状态 */
};
/* 全局TFTP控制块 */
static struct tftp_session tftp_cur;
U-Boot的TFTP实现有几个关键点:
- 超时重传:默认5秒超时,重试5次。这个值可以在配置中改。
- 块号处理:16位块号,从1开始计数。收到小于512字节的包就认为传输结束。
- 错误处理:收到错误包(操作码5)立即终止,打印错误信息。
避坑指南:我曾经遇到过U-Boot TFTP下载到一半卡死的情况。后来发现是网卡驱动的中断处理有问题,导致UDP包丢失。解决办法是调大超时时间,或者换一个网卡驱动。
3.3 U-Boot中TFTP的配置
配置TFTP其实就是在U-Boot里设置几个环境变量。我列一下最常用的:
| 环境变量 | 说明 | 示例值 |
|---|---|---|
| serverip | TFTP服务器IP地址 | 192.168.1.100 |
| ipaddr | 本机IP地址 | 192.168.1.10 |
| netmask | 子网掩码 | 255.255.255.0 |
| gatewayip | 网关IP(跨网段时需要) | 192.168.1.1 |
| loadaddr | 下载到内存的地址 | 0x82000000 |
| tftptimeout | 超时时间(毫秒) | 5000 |
| tftpport | 本机端口(0表示随机) | 0 |
配置步骤很简单:
# 设置网络参数
setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.10
setenv netmask 255.255.255.0
# 设置下载地址
setenv loadaddr 0x82000000
# 保存配置
saveenv
# 测试TFTP下载
tftp ${loadaddr} u-boot.bin
我的习惯:我会在板子上电后先ping一下服务器,确认网络通了再执行tftp命令。这样可以快速定位问题是出在网络层还是应用层。
3.4 TFTP协议流程图
下面这张图展示了TFTP读请求的完整交互过程。你想想看,从客户端发请求到文件传输完成,每一步都环环相扣。
3.5 常见问题与调试技巧
在实际项目中,TFTP出问题的情况我见得太多了。这里总结几个最常见的:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 连接超时 | 防火墙拦截69端口 | 检查防火墙规则,开放UDP 69端口 |
| 下载到一半卡住 | 网络丢包或驱动问题 | 调大tftptimeout,检查网卡驱动 |
| 文件内容错误 | 块号回绕或内存地址不对 | 确认loadaddr正确,检查文件大小 |
| 服务器拒绝连接 | 服务器配置或权限问题 | 检查tftp服务器配置文件 |
我曾经踩过的坑:有一次在产线上批量烧录,发现10%的板子TFTP下载失败。排查了半天,最后发现是交换机的端口协商问题——百兆和千兆混用时,TFTP的UDP包会被交换机丢弃。解决方案是把所有端口强制设为百兆全双工。
调试TFTP最有效的方法就是抓包。在服务器端用tcpdump或者Wireshark,过滤条件设成 port 69 or port tftp,就能看到完整的交互过程。我一般会关注三点:
- 客户端是否成功发送了RRQ
- 服务器是否分配了临时端口
- 数据块和ACK是否一一对应
如果发现数据块重复发送,说明ACK丢了,需要检查网络质量。如果服务器根本没响应,那多半是防火墙的问题。