第十四章 W55MH32 TFTP示例

目录

1 TFTP协议简介

2 TFTP协议特点

3 TFTP协议应用场景

4 TFTP协议基本工作流程

5 TFTP协议报文解析

6 实现过程

7 运行结果

8 总结


本篇文章,我们将详细介绍如何在W55MH32芯片上面实现TFTP协议。并通过实战例程,为大家讲解如何使用TFTP客户端模式向服务器获取文本文件。

该例程用到的其他网络协议,例如DHCP请参考相关章节。有关W55MH32的初始化过程,也请参考Network Install章节,这里将不再赘述。

1 TFTP协议简介

TFTP(Trivial File Transfer Protocol)协议是一种轻量级的文件传输协议,它通常用于需要快速、简单的文件交换场景,尤其是在网络设备启动和配置过程中。与FTP(文件传输协议)不同,TFTP设计得非常简单,仅提供基本的文件读写功能,并且使用UDP作为传输层协议,因而不具备TCP的复杂性和重传机制。

2 TFTP协议特点

简单性:TFTP协议设计简单,它的报头格式简洁,操作命令种类少,这使得实现起来相对容易,对资源的需求也较低。

轻量级:TFTP协议不需要复杂的连接建立和管理过程,开销小,因此适合在一些对性能要求不高、资源有限的环境中使用。

基于UDP:TFTP使用UDP作为传输层协议,利用了UDP的快速传输和无连接特性,从而能够快速地传输数据。不过,这也意味着TFTP本身不提供可靠的传输保证,需要在应用层实现可靠性机制。

端口固定:TFTP使用固定的端口69来监听客户端的请求。数据传输使用的端口是动态分配的,每次传输会在此基础上选择一个临时端口。

数据块大小限制:每个数据报文最多只能传输512字节的数据,如果文件较大,会分多次传输,每次发送一个512字节的数据块。最后一个数据块可能小于512字节,表示文件的结束。

3 TFTP协议应用场景

接下来,我们了解下在W55MH32上,可以使用TFTP协议完成哪些操作及应用呢?

  1. 固件升级:对于路由器、交换机等网络设备,TFTP协议常用于将固件传输到这些设备以进行固件更新。TFTP协议能够确保固件文件快速、准确地传输到目标设备。
  2. 配置文件传输:TFTP协议也常用于管理网络设备的配置文件。将配置文件传输到网络设备以进行     配置更新,或者从网络设备下载配置文件进行备份或分析。
  3. IOT设备固件升级:TFTP协议因其简单性和高效性,成为IOT设备固件升级的一种常用协议。

4 TFTP协议基本工作流程

  1. 请求发送:客户端向服务器发送读请求(RRQ,Read Request)或写请求(WRQ,Write Request)。这些请求包含了要读取或写入的文件名以及传输模式(如二进制或ASCII码)。
  2. 建立连接:服务器接收到客户端的请求后,根据请求中的文件名和传输模式,打开相应的文件(对于写请求)或准备发送文件数据(对于读请求),并向客户端发送确认信息,从而建立连接。
  3. 数据传输:在写请求的情况下,客户端开始发送文件数据到服务器,服务器接收并写入文件。数据以数据块的形式发送,每个数据块的大小通常为512字节(但可以根据网络状况调整)。

在读请求的情况下,服务器开始发送文件数据到客户端,客户端接收并保存文件。同样,数据也是以数据块的形式发送的。

  1. 回应与确认:每当客户端或服务器发送一个数据块后,接收方会发送一个回应包(ACK,Acknowledgment)来确认接收到了该数据块。这个回应包包含了接收到的数据块的编号,以确保数据的顺序和完整性。
  2. 继续传输或结束:根据回应包,发送方会继续发送下一个数据块,直到整个文件传输完成。如果传输过程中出现错误,服务器会向客户端发送错误信息包(ERROR),中断传输过程。
  3. 关闭连接:文件传输完成后,客户端和服务器会关闭连接。

5 TFTP协议报文解析

常见的操作码:

1:读请求(RRQ),用于请求读取服务器上的文件。

2:写请求(WRQ),用于请求向服务器上写入文件。

3:数据(DATA),用于传输文件数据。

4:回应(ACK),用于确认接收到的数据块。

5:错误信息(ERROR),用于报告传输过程中发生的错误。

常见操作码的报文格式如下:

报文类型

报文格式

操作码

其他关键字段及说明

读请求(RRQ)

总长可变,由2字节操作码、可变长文件名(以1字节0结尾)、可变长传输模式(以1字节 0 结尾)组成

1

文件名:明确要读取的文件名称
传输模式:“netascii” 表示ASCI码模式,“octet”表示二进制模式

写请求(WRQ)

总长可变,由2字节操作码、可变长文件名(以1字节0结尾)、可变长传输模式(以1字节 0 结尾)组成

2

文件名:明确要读取的文件名称
传输模式:“netascii” 表示ASCII码模式,“octet”表示二进制模式

数据(DATA)

2字节操作码、2字节数据块编号、最多512字节数据组成

3

数据块编号:从1开始,用于标识数据块顺序
数据:实际传输的文件内容

确认(ACK)

由2字节操作码和2字节确认的数据块编号组成

4

数据块编号:与接收到的数据块编号一致,用于确认接收

错误(ERROR)

由2字节操作码、2字节错误码、可变长错误信息(以 1字节0结尾)组成

5

错误码:明确错误类型
错误信息:具体描述错误情况

报文示例:

客户端读请求报文

|报文解析|

Trivial File Transfer Protocol

    Opcode: Read Request (1)            (操作码为01,读请求报文)

    Source File: tftp_test_file.txt      (明确要读取的文件名为tftp_test_file.txt)

    Type: octet                         (传输模式为octet)

    Option: timeout = 5

|报文原文|

00 01 74 66 74 70 5f 74 65 73 74 5f 66 69 6c 65 2e 74 78 74 00 6f 63 74 65 74 00 74 69 6d 65 6f 75 74 00 35 00

服务器响应报文

|报文解析|

Trivial File Transfer Protocol

    Opcode: Option Acknowledgement (6)   (操作码为06,扩展操作码)

    [Destination File: tftp_test_file.txt]

    [Read Request in frame 125]

Option: timeout = 5

|报文原文|

00 06 74 69 6d 65 6f 75 74 00 35 00

客户端响应报文

|报文解析|

Trivial File Transfer Protocol

    Opcode: Acknowledgement (4)         (操作码为04,回应报文)

    [Destination File: tftp_test_file.txt]

    [Read Request in frame 125]

    Block: 0                            (数据块标号为00 00)

    [Full Block Number: 0]

|报文原文|

00 04 00 00

服务器响应报文

|报文解析|

Trivial File Transfer Protocol

    Opcode: Data Packet (3)             (操作码为03,数据报文)

    [Destination File: tftp_test_file.txt]

    [Read Request in frame 125]

    Block: 1                            (数据块标号为00 01)

    [Full Block Number: 1]

Data (36 bytes)

    Data: 736461666173646661736466617364666666666666666641617364666173666166736466                                       (数据)

[Length: 36]

|报文原文|

00 03 00 01 73 64 61 66 61 73 64 66 61 73 64 66 61 73 64 66 66 66 66 66 66 66 66 41 61 73 64 66 61 73 66 61 66 73 64 66

客户端响应报文

|报文解析|

Trivial File Transfer Protocol

    Opcode: Acknowledgement (4)         (操作码为04,回应报文)

    [Destination File: tftp_test_file.txt]

    [Read Request in frame 125]

    Block: 1                            (数据块标号为00 01)

    [Full Block Number: 1]

|报文原文|

00 04 00 01

6 实现过程

接下来,我们在W55MH32上实现TFTP协议读取文件。

注意:测试实例需要PC端和W55MH32处于同一网段。

在主函数中调用do_tftp_client()函数不断检查和处理 TFTP 客户端的状态,并根据读取的结果(成功或失败)进入相应的处理状态。

do_tftp_client(SOCKET_ID, ethernet_buf);

do_tftp_client()函数如下:

void do_tftp_client(uint8_t sn, uint8_t *buff)
{
    uint32_t tftp_server_ip        = inet_addr(TFTP_SERVER_IP);
    uint8_t  tftp_read_file_name[] = TFTP_SERVER_FILE_NAME;


    TFTP_init(sn, buff);
    while (1)
    {
        if (tftp_read_flag == 0)
        {
            printf("tftp server ip: %s, file name: %s\r\n", TFTP_SERVER_IP, TFTP_SERVER_FILE_NAME);
            printf("send request\r\n");
            TFTP_read_request(tftp_server_ip, TFTP_SERVER_FILE_NAME);
            tftp_read_flag = 1;
        }
        else
        {
            tftp_state = TFTP_run();
            if (tftp_state == TFTP_SUCCESS)
            {
                printf("tftp read success, file name: %s\r\n", tftp_read_file_name);
                while (1)
                {
                }
            }
            else if (tftp_state == TFTP_FAIL)
            {
                printf("tftp read fail, file name: %s\r\n", tftp_read_file_name);
                while (1)
                {
                }
            }
        }
    }
}

进入do_tftp_client()函数后开始进行TFTP客户端处理,步骤如下:

步骤一:TFTP初始化

调用TFTP_init()函数TFTP客户端进行初始化,参数snbuff分别是socket号,socket缓存。

1. void TFTP_init(uint8_t socket, uint8_t *buf)

2. {

3.     init_tftp();

4.  

5.     g_tftp_socket  = open_tftp_socket(socket);

6.     g_tftp_rcv_buf = buf;

7. }
static void init_tftp(void)
{
    g_filename[0] = 0;

    set_server_ip(0);
    set_server_port(0);
    set_local_port(0);

    set_tftp_state(STATE_NONE);
    set_block_number(0);

    // timeout flag
    g_resend_flag  = 0;
    tftp_retry_cnt = tftp_time_cnt = 0;

    g_progress_state = TFTP_PROGRESS;
}

步骤二:发送 TFTP 读请求

tftp_read_flag 0 时,表示尚未发送读取请求。此时,打印 TFTP 服务器的 IP 地址和要读取的文件名,然后调用TFTP_read_request()函数向服务器发送读取请求。发送请求后,将tftp_read_flag设置为 1,表示已发送请求。

void TFTP_read_request(uint32_t server_ip, uint8_t *filename)
{
    set_server_ip(server_ip);
#ifdef __TFTP_DEBUG__
    DBG_PRINT(INFO_DBG, "[%s] Set Tftp Server : %x\r\n", __func__, server_ip);
#endif

    g_progress_state = TFTP_PROGRESS;
    send_tftp_rrq(filename, (uint8_t *)TRANS_BINARY, &default_tftp_opt, 1);
}

步骤三:运行 TFTP 协议并处理结果

tftp_read_flag 1 时调用 TFTP_run()函数处理 TFTP 协议操作,依据其返回的 tftp_state 判断结果:若为 TFTP_SUCCESS 则打印成功信息并进入无限循环,若为 TFTP_FAIL 则打印失败信息并进入无限循环。

TFTP_run()函数如下:

int TFTP_run(void)
{
    int      len;
    uint16_t from_port;
    uint32_t from_ip;

    /* Timeout Process */
    if (g_resend_flag)
    {
        if (tftp_time_cnt >= g_timeout)
        {
            switch (get_tftp_state())
            {
            case STATE_WRQ:
                break;

            case STATE_RRQ:
                send_tftp_rrq(g_filename, (uint8_t *)TRANS_BINARY, &default_tftp_opt, 1);
                break;

            case STATE_OACK:
            case STATE_DATA:
                send_tftp_ack(get_block_number());
                break;

            case STATE_ACK:
                break;

            default:
                break;
            }

            tftp_time_cnt = 0;
            tftp_retry_cnt++;

            if (tftp_retry_cnt >= 5)
            {
                init_tftp();
                g_progress_state = TFTP_FAIL;
            }
        }
    }

    /* Receive Packet Process */
    len = recv_udp_packet(g_tftp_socket, g_tftp_rcv_buf, MAX_MTU_SIZE, &from_ip, &from_port);
    if (len < 0)
    {
#ifdef __TFTP_DEBUG__
        DBG_PRINT(ERROR_DBG, "[%s] recv_udp_packet error\r\n", __func__);
#endif
        return g_progress_state;
    }

    recv_tftp_packet(g_tftp_rcv_buf, len, from_ip, from_port);

    return g_progress_state;
}

在处理接收到的TFTP数据包时,首先调用recv_tftp_packet()函数。

recv_tftp_packet()函数如下:

static void recv_tftp_packet(uint8_t *packet, uint32_t packet_len, uint32_t from_ip, uint16_t from_port)
{
    uint16_t opcode;

    /* Verify Server IP */
    if (from_ip != get_server_ip())
    {
#ifdef __TFTP_DEBUG__
        DBG_PRINT(ERROR_DBG, "[%s] Server IP faults\r\n", __func__);
        DBG_PRINT(ERROR_DBG, "from IP : %08x, Server IP : %08x\r\n", from_ip, get_server_ip());
#endif
        return;
    }

    opcode = ntohs(*((uint16_t *)packet));

    /* Set Server Port */
    if ((get_tftp_state() == STATE_WRQ) || (get_tftp_state() == STATE_RRQ))
    {
        set_server_port(from_port);
#ifdef __TFTP_DEBUG__
        DBG_PRINT(INFO_DBG, "[%s] Set Server Port : %d\r\n", __func__, from_port);
#endif
    }

    switch (opcode)
    {
    case TFTP_RRQ: /* When Server */
        recv_tftp_rrq(packet, packet_len);
        break;
    case TFTP_WRQ: /* When Server */
        recv_tftp_wrq(packet, packet_len);
        break;
    case TFTP_DATA:
        recv_tftp_data(packet, packet_len);
        break;
    case TFTP_ACK:
        recv_tftp_ack(packet, packet_len);
        break;
    case TFTP_OACK:
        recv_tftp_oack(packet, packet_len);
        break;
    case TFTP_ERROR:
        recv_tftp_error(packet, packet_len);
        break;

    default:
        // Unknown Mesage
        break;
    }
}

进入该函数后,第一步验证接收到的数据包的源IP地址,只有当它与服务器IP地址一致时才继续处理,若不一致则直接返回。接着,从数据包中获取操作码(opcode)。根据获取到的操作码,调用相应的处理函数:如果是TFTP读请求(RRQ),则调用 recv_tftp_rrq()函数;若是写请求(WRQ),则调用recv_tftp_wrq()函数;对于接收到的数据数据包,调用 recv_tftp_data()函数;确认数据包则由recv_tftp_ack()函数处理;OACK 数据包由recv_tftp_oack()函数处理;若遇到错误数据包,调用recv_tftp_error()函数来解析错误代码和错误信息。最后,返回g_progress_state,以此表示当前TFTP操作的状态。

7 运行结果

烧录例程运行后,首先进行了PHY链路检测,然后是DHCP获取网络地址结果,最后打印服务器IP和文本名称,读取文本内容,如下图所示:

8 总结

本文讲解了如何在 W55MH32 芯片上实现 TFTP 协议,通过实战例程详细展示了使用 TFTP 客户端模式从服务器获取文本文件的过程,涵盖 TFTP 初始化、发送读请求、运行协议并处理结果等核心步骤。文章还对 TFTP 协议的简介、特点、应用场景、基本工作流程和报文解析进行了分析,帮助读者理解其在文件传输中的实际应用价值。

下一篇文章将聚焦 SNMP 协议,解析其核心原理及在网络管理中的应用,同时讲解如何在相关设备上实现 SNMP 功能,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值