3. TFTP协议详解:从原理到U-Boot实战

TFTP这个协议,说白了就是简化版的FTP。它没有认证,没有目录浏览,甚至连可靠传输都得靠应用层自己重传。但正是因为它简单,才成了嵌入式网络升级的标配。

我记得刚入行那会儿,第一次用TFTP给板子烧系统,死活传不过去。后来才发现,是防火墙把69号端口给拦了。嗯,这种坑我踩过不少,今天咱们就把它彻底讲透。

3.1 TFTP工作原理

TFTP(Trivial File Transfer Protocol)基于UDP,端口号69。它只做一件事:从服务器读文件,或者往服务器写文件。没有列表,没有删除,没有权限控制。

它的工作流程其实很简单:

  1. 客户端向服务器的69端口发请求
  2. 服务器分配一个临时端口(通常是随机高位端口)
  3. 双方在这个临时端口上完成数据传输
  4. 传输结束,连接关闭

核心要点: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读请求的完整交互过程。你想想看,从客户端发请求到文件传输完成,每一步都环环相扣。

TFTP读请求(RRQ)完整交互流程 TFTP客户端 TFTP服务器 ① RRQ (读请求, 端口69) ② ACK + 分配临时端口 ③ DATA块1 (512字节) ④ ACK块1 ⑤ DATA块2 (512字节) ⑥ ACK块2 ...... ⑦ DATA最后一块 (<512字节) ⑧ ACK最后一块 传输结束,关闭连接 关键说明: • 每个数据块最大512字节,最后一块小于512字节表示传输结束 • 超时未收到ACK则重传,默认超时5秒,最多重试5次

3.5 常见问题与调试技巧

在实际项目中,TFTP出问题的情况我见得太多了。这里总结几个最常见的:

问题现象 可能原因 解决方法
连接超时 防火墙拦截69端口 检查防火墙规则,开放UDP 69端口
下载到一半卡住 网络丢包或驱动问题 调大tftptimeout,检查网卡驱动
文件内容错误 块号回绕或内存地址不对 确认loadaddr正确,检查文件大小
服务器拒绝连接 服务器配置或权限问题 检查tftp服务器配置文件

我曾经踩过的坑:有一次在产线上批量烧录,发现10%的板子TFTP下载失败。排查了半天,最后发现是交换机的端口协商问题——百兆和千兆混用时,TFTP的UDP包会被交换机丢弃。解决方案是把所有端口强制设为百兆全双工。

调试TFTP最有效的方法就是抓包。在服务器端用tcpdump或者Wireshark,过滤条件设成 port 69 or port tftp,就能看到完整的交互过程。我一般会关注三点:

  • 客户端是否成功发送了RRQ
  • 服务器是否分配了临时端口
  • 数据块和ACK是否一一对应

如果发现数据块重复发送,说明ACK丢了,需要检查网络质量。如果服务器根本没响应,那多半是防火墙的问题。