全部例程

W55MH32作为FTP客户端

W55MH32

更新于 2026年9月16日

本篇总结

W55MH32 实现 FTP 客户端功能,可访问 FTP 服务器进行文件上传、下载和目录管理。

🔑 KEIL MDK、UART调试、硬件TOE引擎、FTP、DNS、DHCP

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示如何在 W55MH32 上实现 FTP(文件传输协议)的客户端模式,通过实战例程讲解 FTP 文件上传、下载和目录管理操作。该例程用到的其他网络协议,例如 DHCP,请参考相关章节。

前置工具准备

开始本例程前,请确认以下工具已安装:

  • FileZilla Server:FTP 服务器软件,用于在 PC 端搭建 FTP 服务器:点击下载
  • WIZ UartTool V1.0:串口调试工具:点击下载

FTP 协议简介

FTP(File Transfer Protocol,文件传输协议)是应用层的文件传输协议,采用客户端-服务器架构,基于 TCP 双连接工作:控制连接(端口 21)用于传输命令和响应,数据连接(端口 20 或 PASV 模式动态端口)用于传输文件数据。FTP 支持主动模式(PORT)和被动模式(PASV),提供用户认证、文件上传/下载、目录操作等功能。响应码以三位数字表示,1xx 为信息性响应、2xx 为成功、3xx 需补充信息、4xx 为暂时错误、5xx 为永久错误。

命令说明
USER提供用户名进行身份验证
PASS提供密码进行身份验证
RETR从服务器下载文件
STOR上传文件到服务器
LIST列出目录下的文件和子目录
CWD更改当前工作目录
PWD显示当前工作目录
DELE删除指定文件
MKD创建新目录
PASV启用被动模式
PORT指定数据连接端口(主动模式)
QUIT终止会话并退出

W55MH32 的优势

W55MH32 SDK 内置了完整的 FTP 客户端库(ftpc.c/h),命令组包/解包、状态机管理、文件操作全部由库函数处理。你只需要:调用 ftpc_init() 初始化客户端 → 在主循环中调用 ftpc_run() 驱动双状态机 → proc_ftpc() 自动解析服务器响应码。

FTP 获取目录的报文交互示例

客户端                                服务器
  |                                     |
  |====== TCP 连接到 21 端口 ==========>|  建立控制连接
  |<----- 220 Welcome to FTP Server ----|
  |------ USER wiznet ----------------->|
  |<----- 331 Password required --------|
  |------ PASS wiznet ----------------->|
  |<----- 230 User logged in -----------|
  |------ PORT 192,168,1,5,20,100 ----->|  主动模式, 端口 = 20*256+100 = 5260
  |<----- 200 PORT command successful --|
  |------ LIST ------------------------>|  获取当前目录文件信息
  |<----- 150 Opening data connection --|
  |<====== 目录数据 (数据连接传输) ====|
  |------ QUIT ------------------------>|  退出 FTP 会话
  |<----- 221 Goodbye ------------------|

硬件连接与拓扑

请注意:

PC 和 W55MH32 需处于同一网段。PC 端需先启动 FileZilla Server 并配置好用户账户和共享文件夹,W55MH32 作为 FTP 客户端连接服务器进行文件操作。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口
┌──────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬───────────────────┬──────────┘
│ │
┌────┴──────┐ ┌────┴──────────────┐
│ W55MH32 │ │ PC │
│(FTP Cli) │ │ (FileZilla Srv) │
│192.168.1.30│ │ TCP :21 │
└───────────┘ └───────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:PC 运行 FileZilla Server 作为 FTP 服务器(TCP :21),W55MH32 作为 FTP 客户端连接

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\FTP_Client\,核心文件如下:

FTP_Client/
├── Project/
│   └── Project.uvprojx            ← Keil 工程文件
└── USER/
    ├── main.c                     ← 硬件初始化 + 网络配置 + 主循环
    ├── wiz_interface/
    │   ├── wiz_interface.c        ← PHY 检测 / 网络信息打印 / DHCP
    │   └── wiz_interface.h
    ├── ftpc/
    │   ├── ftpc.c                 ← FTP 客户端实现(初始化/运行/响应处理)
    │   ├── ftpc.h
    │   └── ftpc_util.c/h          ← FTP 辅助函数
    ├── ioLibrary/
    │   └── Internet/FTPClient/    ← FTP 客户端协议库
    ├── tim/
    │   └── bsp_tim.c/h            ← TIM3 定时器
    └── uart/
        └── bsp_uart.c/h           ← 调试串口 + printf 重定向

本例程的核心:通过 ftpc_init 初始化客户端配置,ftpc_run 双状态机驱动控制与数据两个 Socket,proc_ftpc 解析服务器响应码执行对应操作。

步骤1:FTP Client 初始化

文件:ftpc.c — ftpc_init() 函数的主要作用是初始化 FTP 客户端的配置和状态,包括设置传输模式、本地 IP 地址和端口、Socket、工作目录,并创建用于命令传输的控制 Socket。

void ftpc_init(uint8_t *src_ip, uint8_t sn_ctrl, uint8_t sn_data)
{
    ftpc.dsock_mode = ACTIVE_MODE;
    local_ip.cVal[0] = src_ip[0];
    local_ip.cVal[1] = src_ip[1];
    local_ip.cVal[2] = src_ip[2];
    local_ip.cVal[3] = src_ip[3];
    local_port       = 35000;
    socket_ctrl      = sn_ctrl;
    socket_data      = sn_data;
    strcpy(ftpc.workingdir, "/");
    socket(socket_ctrl, Sn_MR_TCP, FTP_destport, 0x0);
}

步骤2:在主循环中运行 ftpc_run

文件:main.c — 在主循环中不断调用 ftpc_run(),驱动控制连接和数据连接两个 TCP 状态机。

while (1)
{
    ftpc_run(ethernet_buf);
}

在 ftpc_run() 函数中,会分别执行两个 TCP 状态机:一个用于 FTP 控制连接,一个用于 FTP 数据传输。FTP 数据传输的状态机仅当需要进行数据传输时(上传或下载文件)才会开启。

步骤3:FTP 控制连接状态机

文件:ftpc.c — 当用户在选项菜单中选择不同选项时,系统会根据所选选项发送相应的操作指令。这些操作指令被发送至服务器后,服务器返回响应内容。在 proc_ftpc() 函数中,程序根据服务器返回的响应码判断操作是否成功,并依据响应码执行下一步操作。

uint8_t ftpc_run(uint8_t *dbuf)
{
    uint16_t size = 0;
    long     ret  = 0;
#if defined(F_FILESYSTEM)
    uint32_t send_byte;
#endif
    uint32_t recv_byte;
    uint32_t blocklen;
    uint32_t remain_filesize;
    uint32_t remain_datasize;
    uint8_t  dat[50] = {0,};

    switch (getSn_SR(socket_ctrl))
    {
    case SOCK_ESTABLISHED:
        if (!connect_state_control_ftpc)
        {
            printf("%d:FTP Connected\r\n", socket_ctrl);
            strcpy(ftpc.workingdir, "/");
            connect_state_control_ftpc = 1;
        }
        if (gMenuStart)
        {
            gMenuStart = 0;
            printf("\r\n----------------------------------------\r\n");
            printf("Press menu key\r\n");
            printf("----------------------------------------\r\n");
            printf("1> View FTP Server Directory\r\n");
            printf("2> View My Directory\r\n");
            printf("3> Sets the type of file to be transferred. Current state : %s\r\n", (ftpc.type == ASCII_TYPE) ? "Ascii" : "Binary");
            printf("4> Sets Data Connection. Current state : %s\r\n", (ftpc.dsock_mode == ACTIVE_MODE) ? "Active" : "Passive");
            printf("5> Put File to Server\r\n");
            printf("6> Get File from Server\r\n");
#if defined(F_FILESYSTEM)
            printf("7> Delete My File\r\n");
#endif
            printf("----------------------------------------\r\n");
            while (1)
            {
                memset(gMsgBuf, 0, sizeof(gMsgBuf));
                scanf("%s", gMsgBuf);
                if (gMsgBuf[0] == '1')
                {
                    if (ftpc.dsock_mode == PASSIVE_MODE)
                    {
                        sprintf((char *)dat, "PASV\r\n");
                        send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                        Command.First = f_dir;
                        break;
                    }
                    else
                    {
                        wiz_NetInfo gWIZNETINFO;
                        ctlnetwork(CN_GET_NETINFO, (void *)&gWIZNETINFO);
                        sprintf((char *)dat, "PORT %d,%d,%d,%d,%d,%d\r\n",
                                gWIZNETINFO.ip[0], gWIZNETINFO.ip[1],
                                gWIZNETINFO.ip[2], gWIZNETINFO.ip[3],
                                (uint8_t)(local_port >> 8),
                                (uint8_t)(local_port & 0x00ff));
                        send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                        Command.First = f_dir;
                        gModeActivePassiveflag = 1;
                        break;
                    }
                }
                else if (gMsgBuf[0] == '5')
                {
                    if (ftpc.dsock_mode == PASSIVE_MODE)
                    {
                        sprintf((char *)dat, "PASV\r\n");
                        send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                        Command.First = f_put;
                        break;
                    }
                    else
                    {
                        wiz_NetInfo gWIZNETINFO;
                        ctlnetwork(CN_GET_NETINFO, (void *)&gWIZNETINFO);
                        sprintf((char *)dat, "PORT %d,%d,%d,%d,%d,%d\r\n",
                                gWIZNETINFO.ip[0], gWIZNETINFO.ip[1],
                                gWIZNETINFO.ip[2], gWIZNETINFO.ip[3],
                                (uint8_t)(local_port >> 8),
                                (uint8_t)(local_port & 0x00ff));
                        send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                        Command.First = f_put;
                        gModeActivePassiveflag = 1;
                        break;
                    }
                }
                else if (gMsgBuf[0] == '6')
                {
                    if (ftpc.dsock_mode == PASSIVE_MODE)
                    {
                        sprintf((char *)dat, "PASV\r\n");
                        send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                        Command.First = f_get;
                        break;
                    }
                    else
                    {
                        wiz_NetInfo gWIZNETINFO;
                        ctlnetwork(CN_GET_NETINFO, (void *)&gWIZNETINFO);
                        sprintf((char *)dat, "PORT %d,%d,%d,%d,%d,%d\r\n",
                                gWIZNETINFO.ip[0], gWIZNETINFO.ip[1],
                                gWIZNETINFO.ip[2], gWIZNETINFO.ip[3],
                                (uint8_t)(local_port >> 8),
                                (uint8_t)(local_port & 0x00ff));
                        send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                        Command.First = f_get;
                        gModeActivePassiveflag = 1;
                        break;
                    }
                }
                else if (gMsgBuf[0] == '2')
                {
#if defined(F_FILESYSTEM)
                    scan_files(ftpc.workingdir, dbuf, (int *)&size);
                    printf("\r\n%s\r\n", dbuf);
#else
                    if (strncmp(ftpc.workingdir, "/$Recycle.Bin", sizeof("/$Recycle.Bin")) != 0)
                        size = sprintf((char *)dbuf,
                            "drwxr-xr-x 1 ftp ftp 0 Dec 31 2014 $Recycle.Bin\r\n"
                            "-rwxr-xr-x 1 ftp ftp 512 Dec 31 2014 test.txt\r\n");
                    printf("\r\n%s\r\n", dbuf);
#endif
                    gMenuStart = 1;
                    break;
                }
                else if (gMsgBuf[0] == '3')
                {
                    printf("1> ASCII\r\n");
                    printf("2> BINARY\r\n");
                    while (1)
                    {
                        memset(gMsgBuf, 0, sizeof(gMsgBuf));
                        scanf("%s", gMsgBuf);
                        if (gMsgBuf[0] == '1')
                        {
                            sprintf((char *)dat, "TYPE %c\r\n", TransferAscii);
                            ftpc.type = ASCII_TYPE;
                            send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                            break;
                        }
                        else if (gMsgBuf[0] == '2')
                        {
                            sprintf((char *)dat, "TYPE %c\r\n", TransferBinary);
                            ftpc.type = IMAGE_TYPE;
                            send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                            break;
                        }
                        else if (gMsgBuf[0] != 0x00)
                        {
                            printf("\r\nRetry...\r\n");
                        }
                    }
                    break;
                }
                else if (gMsgBuf[0] == '4')
                {
                    printf("1> ACTIVE\r\n");
                    printf("2> PASSIVE\r\n");
                    while (1)
                    {
                        memset(gMsgBuf, 0, sizeof(gMsgBuf));
                        scanf("%s", gMsgBuf);
                        if (gMsgBuf[0] == '1')
                        {
                            ftpc.dsock_mode = ACTIVE_MODE;
                            break;
                        }
                        else if (gMsgBuf[0] == '2')
                        {
                            ftpc.dsock_mode = PASSIVE_MODE;
                            break;
                        }
                        else if (gMsgBuf[0] != 0x00)
                        {
                            printf("\r\nRetry...\r\n");
                        }
                    }
                    gMenuStart = 1;
                    break;
                }
#if defined(F_FILESYSTEM)
                else if (msg_c == '7')
                {
                    printf(">del filename?");
                    memset(gMsgBuf, 0, sizeof(gMsgBuf));
                    scanf("%s", gMsgBuf);
                    sprintf((char *)dat, "STOR %s\r\n", gMsgBuf);
                    if (f_unlink((const char *)ftpc.filename) != 0)
                    {
                        printf("\r\nCould not delete.\r\n");
                    }
                    else
                    {
                        printf("\r\nDeleted.\r\n");
                    }
                    gMenuStart = 1;
                    break;
                }
#endif
                else if (gMsgBuf[0] != 0x00)
                {
                    printf("\r\nRetry...\r\n");
                }
            }
        }
        if (gDataSockReady)
        {
            gDataSockReady = 0;
            switch (Command.First)
            {
            case f_dir:
                sprintf((char *)dat, "LIST\r\n");
                send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                break;
            case f_put:
                printf(">put file name?");
                memset(gMsgBuf, 0, sizeof(gMsgBuf));
                scanf("%s", gMsgBuf);
                sprintf((char *)dat, "STOR %s\r\n", gMsgBuf);
                send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                break;
            case f_get:
                printf(">get file name?");
                memset(gMsgBuf, 0, sizeof(gMsgBuf));
                scanf("%s", gMsgBuf);
                sprintf((char *)dat, "RETR %s\r\n", gMsgBuf);
                send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
                break;
            default:
                printf("Command.First = default\r\n");
                break;
            }
        }
        if ((size = getSn_RX_RSR(socket_ctrl)) > 0)
        {
            memset(dbuf, 0, _MAX_SS);
            if (size > _MAX_SS)
                size = _MAX_SS - 1;
            ret       = recv(socket_ctrl, dbuf, size);
            dbuf[ret] = '\0';
            if (ret != size)
            {
                if (ret == SOCK_BUSY)
                    return 0;
                if (ret < 0)
                {
                    printf("%d:recv() error:%ld\r\n", socket_ctrl, ret);
                    close(socket_ctrl);
                    return ret;
                }
            }
            printf("Rcvd Command: %s\r\n", dbuf);
            proc_ftpc((char *)dbuf, size);
        }
        break;
    case SOCK_CLOSE_WAIT:
        printf("%d:CloseWait\r\n", socket_ctrl);
        if ((ret = disconnect(socket_ctrl)) != SOCK_OK)
            return ret;
        printf("%d:Closed\r\n", socket_ctrl);
        break;
    case SOCK_CLOSED:
        printf("%d:FTPStart\r\n", socket_ctrl);
        if ((ret = socket(socket_ctrl, Sn_MR_TCP, FTP_destport, 0x0)) != socket_ctrl)
        {
            printf("%d:socket() error:%ld\r\n", socket_ctrl, ret);
            close(socket_ctrl);
            return ret;
        }
        break;
    case SOCK_INIT:
        printf("%d:Opened\r\n", socket_ctrl);
        if ((ret = connect(socket_ctrl, local_ip.cVal, FTP_destport)) != SOCK_OK)
        {
            printf("%d:Connect error\r\n", socket_ctrl);
            return ret;
        }
        connect_state_control_ftpc = 0;
        printf("%d:Connectting...\r\n", socket_ctrl);
        break;
    default:
        break;
    }

步骤4:FTP 数据连接状态机

文件:ftpc.c — 数据连接状态机负责处理文件上传(s_put)、下载(s_get)和目录列表获取(s_dir)。在主动模式下,数据 Socket 通过 listen() 等待服务器连接;在被动模式下,数据 Socket 主动 connect() 到服务器指定的端口。

    switch (getSn_SR(socket_data))
    {
    case SOCK_ESTABLISHED:
        if (!connect_state_data_ftpc)
        {
            printf("%d:FTP Data socket Connected\r\n", socket_data);
            connect_state_data_ftpc = 1;
        }
        if (gDataPutGetStart)
        {
            switch (Command.Second)
            {
            case s_dir:
                printf("dir waiting...\r\n");
                if ((size = getSn_RX_RSR(socket_data)) > 0)
                {
                    printf("ok\r\n");
                    memset(dbuf, 0, _MAX_SS);
                    if (size > _MAX_SS)
                        size = _MAX_SS - 1;
                    ret       = recv(socket_data, dbuf, size);
                    dbuf[ret] = '\0';
                    if (ret != size)
                    {
                        if (ret == SOCK_BUSY)
                            return 0;
                        if (ret < 0)
                        {
                            printf("%d:recv() error:%ld\r\n", socket_ctrl, ret);
                            close(socket_data);
                            return ret;
                        }
                    }
                    printf("Rcvd Data:\n\r%s\n\r", dbuf);
                    gDataPutGetStart = 0;
                    Command.Second   = s_nocmd;
                }
                break;
            case s_put:
                printf("put waiting...\r\n");
                if (strlen(ftpc.workingdir) == 1)
                    sprintf(ftpc.filename, "/%s", (uint8_t *)gMsgBuf);
                else
                    sprintf(ftpc.filename, "%s/%s", ftpc.workingdir, (uint8_t *)gMsgBuf);
#if defined(F_FILESYSTEM)
                ftpc.fr = f_open(&(ftpc.fil), (const char *)ftpc.filename, FA_READ);
                if (ftpc.fr == FR_OK)
                {
                    remain_filesize = ftpc.fil.fsize;
                    printf("f_open return FR_OK\r\n");
                    do
                    {
                        memset(dbuf, 0, _MAX_SS);
                        if (remain_filesize > _MAX_SS)
                            send_byte = _MAX_SS;
                        else
                            send_byte = remain_filesize;
                        ftpc.fr = f_read(&(ftpc.fil), (void *)dbuf, send_byte, (UINT *)&blocklen);
                        if (ftpc.fr != FR_OK)
                        {
                            break;
                        }
                        printf("#");
                        send(socket_data, dbuf, blocklen);
                        remain_filesize -= blocklen;
                    } while (remain_filesize != 0);
                    printf("\r\nFile read finished\r\n");
                    ftpc.fr = f_close(&(ftpc.fil));
                }
                else
                {
                    printf("File Open Error: %d\r\n", ftpc.fr);
                    ftpc.fr = f_close(&(ftpc.fil));
                }
#else
                remain_filesize = strlen(ftpc.filename);
                do
                {
                    memset(dbuf, 0, _MAX_SS);
                    blocklen = sprintf((char *)dbuf, "%s", ftpc.filename);
                    printf("########## dbuf:%s\r\n", dbuf);
                    send(socket_data, dbuf, blocklen);
                    remain_filesize -= blocklen;
                } while (remain_filesize != 0);
#endif
                gDataPutGetStart = 0;
                Command.Second   = s_nocmd;
                disconnect(socket_data);
                break;
            case s_get:
                printf("get waiting...\r\n");
                if (strlen(ftpc.workingdir) == 1)
                    sprintf(ftpc.filename, "/%s", (uint8_t *)gMsgBuf);
                else
                    sprintf(ftpc.filename, "%s/%s", ftpc.workingdir, (uint8_t *)gMsgBuf);
#if defined(F_FILESYSTEM)
                ftpc.fr = f_open(&(ftpc.fil), (const char *)ftpc.filename,
                                 FA_CREATE_ALWAYS | FA_WRITE);
                if (ftpc.fr == FR_OK)
                {
                    printf("f_open return FR_OK\r\n");
                    while (1)
                    {
                        if ((remain_datasize = getSn_RX_RSR(socket_data)) > 0)
                        {
                            while (1)
                            {
                                memset(dbuf, 0, _MAX_SS);
                                if (remain_datasize > _MAX_SS)
                                    recv_byte = _MAX_SS;
                                else
                                    recv_byte = remain_datasize;
                                ret     = recv(socket_data, dbuf, recv_byte);
                                ftpc.fr = f_write(&(ftpc.fil), (const void *)dbuf,
                                                  (UINT)ret, (UINT *)&blocklen);
                                remain_datasize -= blocklen;
                                if (ftpc.fr != FR_OK)
                                {
                                    printf("f_write failed\r\n");
                                    break;
                                }
                                if (remain_datasize <= 0)
                                    break;
                            }
                            if (ftpc.fr != FR_OK)
                            {
                                printf("f_write failed\r\n");
                                break;
                            }
                            printf("#");
                        }
                        else
                        {
                            if (getSn_SR(socket_data) != SOCK_ESTABLISHED)
                                break;
                        }
                    }
                    printf("\r\nFile write finished\r\n");
                    ftpc.fr          = f_close(&(ftpc.fil));
                    gDataPutGetStart = 0;
                }
                else
                {
                    printf("File Open Error: %d\r\n", ftpc.fr);
                }
#else
                while (1)
                {
                    if ((remain_datasize = getSn_RX_RSR(socket_data)) > 0)
                    {
                        while (1)
                        {
                            memset(dbuf, 0, _MAX_SS);
                            if (remain_datasize > _MAX_SS)
                                recv_byte = _MAX_SS;
                            else
                                recv_byte = remain_datasize;
                            ret = recv(socket_data, dbuf, recv_byte);
                            printf("########## dbuf:%s\r\n", dbuf);
                            remain_datasize -= ret;
                            if (remain_datasize <= 0)
                                break;
                        }
                    }
                    else
                    {
                        if (getSn_SR(socket_data) != SOCK_ESTABLISHED)
                            break;
                    }
                }
                gDataPutGetStart = 0;
                Command.Second   = s_nocmd;
#endif
                break;
            default:
                printf("Command.Second = default\r\n");
                break;
            }
        }
        break;
    case SOCK_CLOSE_WAIT:
        printf("%d:CloseWait\r\n", socket_data);
        if ((size = getSn_RX_RSR(socket_data)) > 0)
        {
            ret       = recv(socket_data, dbuf, size);
            dbuf[ret] = '\0';
            if (ret != size)
            {
                if (ret == SOCK_BUSY)
                    return 0;
                if (ret < 0)
                {
                    printf("%d:recv() error:%ld\r\n", socket_ctrl, ret);
                    close(socket_data);
                    return ret;
                }
            }
            printf("Rcvd Data:\n\r%s\n\r", dbuf);
        }
        if ((ret = disconnect(socket_data)) != SOCK_OK)
            return ret;
        printf("%d:Closed\r\n", socket_data);
        break;
    case SOCK_CLOSED:
        if (ftpc.dsock_state == DATASOCK_READY)
        {
            if (ftpc.dsock_mode == PASSIVE_MODE)
            {
                printf("%d:FTPDataStart, port : %d\r\n", socket_data, local_port);
                if ((ret = socket(socket_data, Sn_MR_TCP, local_port, 0x0)) != socket_data)
                {
                    printf("%d:socket() error:%ld\r\n", socket_data, ret);
                    close(socket_data);
                    return ret;
                }
                local_port++;
                if (local_port > 50000)
                    local_port = 35000;
            }
            else
            {
                printf("%d:FTPDataStart, port : %d\r\n", socket_data, local_port);
                if ((ret = socket(socket_data, Sn_MR_TCP, local_port, 0x0)) != socket_data)
                {
                    printf("%d:socket() error:%ld\r\n", socket_data, ret);
                    close(socket_data);
                    return ret;
                }
                local_port++;
                if (local_port > 50000)
                    local_port = 35000;
            }
            ftpc.dsock_state = DATASOCK_START;
        }
        break;
    case SOCK_INIT:
        printf("%d:Opened\r\n", socket_data);
        if (ftpc.dsock_mode == ACTIVE_MODE)
        {
            if ((ret = listen(socket_data)) != SOCK_OK)
            {
                printf("%d:Listen error\r\n", socket_data);
                return ret;
            }
            gDataSockReady = 1;
            printf("%d:Listen ok\r\n", socket_data);
        }
        else
        {
            if ((ret = connect(socket_data, remote_ip.cVal, remote_port)) != SOCK_OK)
            {
                printf("%d:Connect error\r\n", socket_data);
                return ret;
            }
            gDataSockReady = 1;
        }
        connect_state_data_ftpc = 0;
        break;
    default:
        break;
    }
    return 0;
}

步骤5:proc_ftpc 响应码处理

文件:ftpc.c — 当 FTP 控制的 Socket 收到数据时,会调用 proc_ftpc() 函数进行处理。该函数从服务器响应中解析三位数字状态码,根据不同的响应码执行相应的操作:220 提示输入用户名、331 提示输入密码、230 登录成功后设置传输类型、227 解析被动模式端口、425 主动模式失败切换被动模式等。

char proc_ftpc(char *buf, uint16_t len)
{
    uint16_t Responses[3] = {0};
    uint16_t i            = 0;
    uint8_t  j            = 0;
    uint8_t  k;
    uint8_t  dat[30] = {0,};
    uint8_t buff[30];
    while (i < len)
    {
        if ((buf[i] >= '0') & (buf[i] <= '9'))
        {
            if (((buf[i] >= '0') & (buf[i] <= '9'))
              & ((buf[i + 1] >= '0') & (buf[i + 1] <= '9'))
              & ((buf[i + 2] >= '0') & (buf[i + 2] <= '9'))
              & (buf[i + 3] == ' '))
            {
                Responses[j] = (buf[i] - '0') * 100
                             + (buf[i + 1] - '0') * 10
                             + (buf[i + 2] - '0');
                j++;
            }
            i += 3;
        }
        else
        {
            i++;
        }
    }
    for (k = 0; k < j; k++)
    {
        switch (Responses[k])
        {
        case R_530:
            printf("\r\nLogin failed, please check your ID and Password.\r\n");
        case R_220: /* Service ready for new user. */
            printf("\r\nInput your User ID > ");
            scanf("%s", buff);
            sprintf((char *)dat, "USER %s\r\n", buff);
            send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
            break;
        case R_331: /* User name okay, need password. */
            printf("\r\nInput your Password > ");
            scanf("%s", buff);
            sprintf((char *)dat, "PASS %s\r\n", buff);
            send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
            break;
        case R_230: /* User logged in, proceed */
            printf("\r\nUser logged in, proceed\r\n");
            sprintf((char *)dat, "TYPE %c\r\n", TransferAscii);
            ftpc.type = ASCII_TYPE;
            send(socket_ctrl, (uint8_t *)dat, strlen((char *)dat));
            break;
        case R_200:
            if ((ftpc.dsock_mode == ACTIVE_MODE) && gModeActivePassiveflag)
            {
                ftpc.dsock_state       = DATASOCK_READY;
                gModeActivePassiveflag = 0;
            }
            else
            {
                gMenuStart = 1;
            }
            break;
        case R_125:
        case R_150:
            switch (Command.First)
            {
            case f_dir:
                Command.First    = f_nocmd;
                Command.Second   = s_dir;
                gDataPutGetStart = 1;
                break;
            case f_get:
                Command.First    = f_nocmd;
                Command.Second   = s_get;
                gDataPutGetStart = 1;
                break;
            case f_put:
                Command.First    = f_nocmd;
                Command.Second   = s_put;
                gDataPutGetStart = 1;
                break;
            default:
                printf("Command.First = default\r\n");
                break;
            }
            break;
        case R_226:
            gMenuStart = 1;
            break;
        case R_227:
            if (pportc(buf) == -1)
            {
                printf("Bad port syntax\r\n");
            }
            else
            {
                printf("Go Open Data Sock...\r\n ");
                ftpc.dsock_mode  = PASSIVE_MODE;
                ftpc.dsock_state = DATASOCK_READY;
            }
            break;
        case R_425:
            printf("Active mode request failed, switching to passive mode\r\n");
            ftpc.dsock_mode = PASSIVE_MODE;
            gDataSockReady  = 0;
            close(socket_data);
            gMenuStart = 1;
            break;
        default:
            printf("\r\nDefault Status = %d\r\n", (uint16_t)Responses[k]);
            gDataSockReady = 1;
            break;
        }
    }
    return 1;
}

proc_ftpc() 函数遍历接收缓冲区,查找符合三位数字加空格格式的响应码(如 "230 "),将其转换为数值存入 Responses 数组。然后通过 switch 语句根据不同的响应码执行相应操作:R_220 提示输入用户名、R_331 提示输入密码、R_230 登录成功后设置传输类型、R_200 在主动模式下准备数据 Socket、R_150/R_125 启动数据传输、R_227 解析被动端口、R_425 切换到被动模式。

运行验证

烧录例程运行后,首先进行 PHY 链路检测,然后通过 DHCP 获取网络地址并打印网络地址信息,打印出 FTP 连接信息并提示输入进行连接。

W55MH32 FTP Client example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running
DHCP success!
====================================================================================================
 W55MH32 network configuration : DHCP

 MAC         : 00:08:DC:00:0E:9A
 IP          : 192.168.1.6
 Subnet Mask : 255.255.255.0
 Gateway     : 192.168.1.1
 DNS         : 192.168.1.1
====================================================================================================

0:Opened
0:Connectting...
0:FTP Connected
Rcvd Command: 220-FileZilla Server ????????? 0.9.60 beta
220-written by Tim Kosse ([email protected])
220 Please visit https://filezilla-project.org/

Input your User ID > Rcvd Command: 331 Password required for wiznet

Input your Password > Rcvd Command: 230 Logged on

User logged in, proceed
Rcvd Command: 200 Type set to A

----------------------------------------
Press menu key
----------------------------------------
1> View FTP Server Directory
2> View My Directory
3> Sets the type of file to be transferred. Current state : Ascii
4> Sets Data Connection. Current state : Active
5> Put File to Server
6> Get File from Server
----------------------------------------
Rcvd Command: 200 Port command successful

1:FTPDataStart, port : 35000
1:Opened
1:Listen ok
>get file name?Rcvd Command: 150 Opening data channel for file download from server of "/tftp_test_file.txt"

1:CloseWait
Rcvd Data:

HelloWIZNET

1:Closed
Rcvd Command: 226 Successfully transferred "/tftp_test_file.txt"

----------------------------------------
Press menu key
----------------------------------------
1> View FTP Server Directory
2> View My Directory
3> Sets the type of file to be transferred. Current state : Ascii
4> Sets Data Connection. Current state : Active
5> Put File to Server
6> Get File from Server
----------------------------------------

测试前提

测试实例需要 PC 端和 W55MH32 处于同一网段。使用 FileZilla Server 作为 FTP 服务器端,操作步骤如下:第一步建立服务器并设置账户(用户名:wiznet,密码:123456);第二步添加共享文件夹;第三步在串口助手根据提示输入用户名和密码;第四步选择 6 从服务器获取文件;第五步输入文件名成功获取文件内容。

FileZilla Server Users 设置 — 创建用户 wiznet(第一步)
FileZilla Server Shared Folders 设置 — 添加共享目录及权限(第二步)
FileZilla Server 日志(wiznet 登录成功)+ WIZ UartTool 串口输出(FTP 菜单 + 认证 230 Logged on)— 第三步
rtTool — 数据连接建立FileZilla Server 日志(PORT 命令)+ WIZ UartTool 串口输出(200 Port command successful)— 第四步
FileZilla Server 日志(RETR /tftp_test_file.txt)+ WIZ UartTool 串口输出(226 Successfully transferred)— 第五步

注意事项

发送的内容的结尾需要带上回车换行符(\r\n)。其他操作类似,这里不再一一讲解。

常见问题

Q: 主动模式和被动模式如何选择

主动模式下服务器主动连接客户端的数据端口,适合服务器端网络无防火墙限制的环境。被动模式下客户端主动连接服务器指定的端口,更适合客户端在 NAT 或防火墙后的情况。W55MH32 例程中可通过菜单选项 4 切换 ACTIVE_MODE 和 PASSIVE_MODE。当主动模式请求失败(收到 425 响应码)时,程序会自动切换到被动模式重试。

Q: FTP 传输为何使用两个 TCP 连接

FTP 协议将控制连接(端口 21)和数据连接(端口 20 或 PASV 动态端口)分离。控制连接在整个会话期间保持,用于传输命令和响应;数据连接仅在进行文件传输或目录列表操作时建立,传输完成后即关闭。这种分离设计使得控制命令和数据传输互不干扰,提高了协议的灵活性和可靠性。W55MH32 例程中使用两个独立的 Socket(socket_ctrl 和 socket_data)分别处理这两个连接。

Q: W55MH32 实现 FTP 客户端需要哪些 Socket 资源

需要两个 Socket:一个用于控制连接(socket_ctrl),一个用于数据连接(socket_data)。在 ftpc_init() 中通过 sn_ctrl 和 sn_data 参数指定 Socket 编号。控制连接 Socket 连接到服务器的 21 端口并保持整个会话;数据连接 Socket 在需要传输数据时动态创建,主动模式下监听等待服务器连接,被动模式下主动连接服务器指定端口。

Q: proc_ftpc 函数如何解析服务器响应

proc_ftpc() 函数遍历接收缓冲区,查找符合三位数字加空格格式的响应码(如 "230 "),将其转换为数值存入 Responses 数组。然后通过 switch 语句根据不同的响应码执行相应操作:R_220 提示输入用户名、R_331 提示输入密码、R_230 登录成功后设置传输类型、R_200 在主动模式下准备数据 Socket、R_150/R_125 启动数据传输、R_227 解析被动端口、R_425 切换到被动模式。

总结

  • 协议架构:FTP 采用客户端-服务器架构,使用控制连接(端口 21)和数据连接双 TCP 连接,支持主动和被动两种传输模式
  • 核心函数:ftpc_init() 初始化客户端配置,ftpc_run() 通过双状态机驱动控制与数据两个 Socket,proc_ftpc() 解析响应码执行对应操作
  • 文件操作:支持目录查看(LIST)、文件上传(STOR)、文件下载(RETR)等 FTP 标准命令
  • 模式切换:主动模式失败时自动切换被动模式,适应不同网络环境(NAT/防火墙)
  • 应用价值:适用于固件升级、数据采集上传、远程配置与日志管理等嵌入式场景

下一篇文章将聚焦 WOL(Wake-on-LAN)网络唤醒功能,解析其核心原理及在网络设备管理中的应用,敬请期待!

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家