全部例程

W55MH32作为FTP服务器

W55MH32

更新于 2026年9月15日

本篇总结

在 W55MH32 上实现 FTP 服务器,支持主动模式与被动模式,可同时服务两个客户端。

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

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

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

本示例演示如何在 W55MH32 上实现 FTP(文件传输协议)。并通过实战例程,为大家讲解使用 W55MH32 作为 FTP 服务器、PC 端作为 FTP 客户端进行文件传输、目录操作等多种功能。该例程用到的其他网络协议(例如 DHCP),请参考相关章节。

前置工具准备

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

  • FileZilla:FTP 客户端软件,用于连接 FTP 服务器进行文件传输:点击下载
  • WIZ UartTool V1.0:串口调试工具:点击下载

FTP 协议简介

FTP(File Transfer Protocol,文件传输协议)是应用层的文件传输协议,采用客户端-服务器架构,基于 TCP 双连接工作:控制连接(端口 21)用于传输命令和响应,数据连接(端口 20 或 PASV 模式动态端口)用于传输文件数据。FTP 支持主动模式(PORT)和被动模式(PASV),提供用户认证、文件上传/下载、目录操作等功能。

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

W55MH32 的优势

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

硬件连接与拓扑

请注意:

PC 和 W55MH32 需处于同一网段。FileZilla 中填写的主机 IP 必须与开发板获取的 IP 地址一致,用户名和密码需与程序中 ftp_ID 和 ftp_PW 一致。

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

工程结构

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

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

本例程的核心:通过 ftpd_init 初始化服务器参数,ftpd_run 基于 TCP Server 状态机管理控制连接与数据连接,proc_ftpd 解析并执行 FTP 命令。

步骤1:获取网络配置和 FTP 初始化

文件:main.c — 首先获取网络配置信息,然后调用 ftpd_init() 初始化 FTP 服务器。

wizchip_getnetinfo(&net_info);

ftpd_init(net_info.ip);

ftpd_init() 函数内容如下:

/**
 * @brief Initialize the FTP server
 *
 * Initialize the FTP server, set the status, commands, modes and other parameters
 * of the FTP server, and initialize the FTP login username and password.
 *
 * @param src_ip: Source IP address
 */
void ftpd_init(uint8_t *src_ip)
{
    ftp.state       = FTPS_NOT_LOGIN;
    ftp.current_cmd = NO_CMD;
    ftp.dsock_mode  = ACTIVE_MODE;

    ftp.ID_Enable = STATUS_USED;
    ftp.PW_Enable = STATUS_USED;

    if (ftp.ID_Enable == STATUS_USED)
    {
        strcpy(ftp.username, ftp_ID);
        printf(" FTP ID[%d]:%s \r\n", strlen(ftp.username), ftp.username);
    }

    if (ftp.PW_Enable == STATUS_USED)
    {
        strcpy(ftp.userpassword, ftp_PW);
        printf(" FTP PW[%d]:%s \r\n", strlen(ftp.userpassword), ftp.userpassword);
    }

    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;

    strcpy(ftp.workingdir, "/");

    socket(CTRL_SOCK, Sn_MR_TCP, IPPORT_FTP, 0x0);
    socket(CTRL_SOCK1, Sn_MR_TCP, IPPORT_FTP, 0x0);
}

ftpd_init() 的主要作用是对 FTP 服务器的各种参数进行初始化设置,包括服务器状态、用户认证信息、网络地址和端口,以及创建 TCP socket,为后续的 FTP 服务运行做好准备。

步骤2:实现服务器和客户端之间的持续交互

ftpd_run() 函数在主循环中不断被调用,作用是让 FTP 服务器持续运行,不断处理客户端的各种请求,实现服务器与客户端之间的持续交互。

while (1)
{
    ftpd_run(ethernet_buf);
}

ftpd_run() 函数内容如下:

/**
 * Runs the FTP daemon, processing incoming data.
 *
 * @param dbuf: Pointer to data buffer for FTP processing.
 *
 * @return      Status/error code (implementation-specific).
 *
 * Note: Main entry point for FTP daemon.
 */
uint8_t ftpd_run(uint8_t *dbuf)
{
    uint16_t size = 0;
    long     ret  = 0;
    uint32_t blocklen, recv_byte;
    uint32_t remain_filesize;
    int32_t  remain_datasize;
#if defined(F_FILESYSTEM)
    FILINFO fno;
#endif

// FTP Control 1
#if 1
    switch (getSn_SR(CTRL_SOCK))
    {
    case SOCK_ESTABLISHED:
        if (!connect_state_control)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:FTP Connected\r\n", CTRL_SOCK);
#endif
            strcpy(ftp.workingdir, "/");
            sprintf((char *)dbuf, "220 %s FTP version %s ready.\r\n", HOSTNAME, VERSION);
            ret = send(CTRL_SOCK, (uint8_t *)dbuf, strlen((const char *)dbuf));
#if defined(_FTP_DEBUG_)
            printf("%d:send() [%s]\r\n", CTRL_SOCK, dbuf);
#endif
            if (ret < 0)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:send() error:%ld\r\n", CTRL_SOCK, ret);
#endif
                close(CTRL_SOCK);
                return ret;
            }
            connect_state_control = 1;
        }
#if connect_timeout_en
        else
        {
            if (con_remain_cnt1 > remain_time)
            {
                if ((ret = disconnect(CTRL_SOCK)) != SOCK_OK)
                    return ret;
#if defined(_FTP_DEBUG_)
                printf("%d:Timeout Closed\r\n", CTRL_SOCK);
#endif
            }
#if defined(_FTP_DEBUG_)
            else if (((con_remain_cnt1 % 10000) == 0) && (con_remain_cnt1 != 0))
            {
                // printf("%d:Timeout Count:%ld\r\n", CTRL_SOCK, con_remain_cnt1);
            }
#endif
            con_remain_cnt1++;
        }
#endif
        if ((size = getSn_RX_RSR(CTRL_SOCK)) > 0)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:size: %d\r\n", CTRL_SOCK, size);
#endif
            memset(dbuf, 0, _MAX_SS);
            if (size > _MAX_SS)
                size = _MAX_SS - 1;
            ret       = recv(CTRL_SOCK, dbuf, size);
            dbuf[ret] = '\0';
            if (ret != size)
            {
                if (ret == SOCK_BUSY)
                    return 0;
                if (ret < 0)
                {
#if defined(_FTP_DEBUG_)
                    printf("%d:recv() error:%ld\r\n", CTRL_SOCK, ret);
#endif
                    close(CTRL_SOCK);
                    return ret;
                }
            }
#if defined(_FTP_DEBUG_)
            printf("%d:Rcvd Command: %s", CTRL_SOCK, dbuf);
#endif
            proc_ftpd(CTRL_SOCK, (char *)dbuf);
            con_remain_cnt1 = 0;
        }
        break;

    case SOCK_CLOSE_WAIT:
#if defined(_FTP_DEBUG_)
        printf("%d:CloseWait\r\n", CTRL_SOCK);
#endif
        if ((ret = disconnect(CTRL_SOCK)) != SOCK_OK)
            return ret;
#if defined(_FTP_DEBUG_)
        printf("%d:Closed\r\n", CTRL_SOCK);
#endif
        break;

    case SOCK_CLOSED:
#if defined(_FTP_DEBUG_)
        printf("%d:FTPStart\r\n", CTRL_SOCK);
#endif
        if ((ret = socket(CTRL_SOCK, Sn_MR_TCP, IPPORT_FTP, 0x0)) != CTRL_SOCK)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:socket() error:%ld\r\n", CTRL_SOCK, ret);
#endif
            close(CTRL_SOCK);
            return ret;
        }
        break;

    case SOCK_INIT:
#if defined(_FTP_DEBUG_)
        printf("%d:Opened\r\n", CTRL_SOCK);
#endif
        strcpy(ftp.workingdir, "/");
        if ((ret = listen(CTRL_SOCK)) != SOCK_OK)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:Listen error\r\n", CTRL_SOCK);
#endif
            return ret;
        }
        connect_state_control = 0;
        con_remain_cnt1       = 0;
#if defined(_FTP_DEBUG_)
        printf("%d:Listen ok\r\n", CTRL_SOCK);
#endif
        break;

    default:
        break;
    }

    // FTP Control 2
    switch (getSn_SR(CTRL_SOCK1))
    {
    case SOCK_ESTABLISHED:
        if (!connect_state_control1)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:FTP Connected\r\n", CTRL_SOCK1);
#endif
            strcpy(ftp.workingdir, "/");
            sprintf((char *)dbuf, "220 %s FTP version %s ready.\r\n", HOSTNAME, VERSION);
            ret = send(CTRL_SOCK1, (uint8_t *)dbuf, strlen((const char *)dbuf));
#if defined(_FTP_DEBUG_)
            printf("%d:send() [%s]\r\n", CTRL_SOCK1, dbuf);
#endif
            if (ret < 0)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:send() error:%ld\r\n", CTRL_SOCK1, ret);
#endif
                close(CTRL_SOCK1);
                return ret;
            }
            connect_state_control1 = 1;
        }
#if connect_timeout_en
        else
        {
            if (con_remain_cnt2 > remain_time)
            {
                if ((ret = disconnect(CTRL_SOCK1)) != SOCK_OK)
                    return ret;
#if defined(_FTP_DEBUG_)
                printf("%d:Timeout Closed\r\n", CTRL_SOCK1);
#endif
            }
#if defined(_FTP_DEBUG_)
            else if (((con_remain_cnt2 % 10000) == 0) && (con_remain_cnt2 != 0))
            {
                printf("%d:Timeout Count:%ld\r\n", CTRL_SOCK1, con_remain_cnt2);
            }
#endif
            con_remain_cnt2++;
        }
#endif
        if ((size = getSn_RX_RSR(CTRL_SOCK1)) > 0)
        {
#if defined(_FTP_DEBUG_)
            printf("%d: size: %d\r\n", CTRL_SOCK1, size);
#endif
            memset(dbuf, 0, _MAX_SS);
            if (size > _MAX_SS)
                size = _MAX_SS - 1;
            ret       = recv(CTRL_SOCK1, dbuf, size);
            dbuf[ret] = '\0';
            if (ret != size)
            {
                if (ret == SOCK_BUSY)
                    return 0;
                if (ret < 0)
                {
#if defined(_FTP_DEBUG_)
                    printf("%d:recv() error:%ld\r\n", CTRL_SOCK1, ret);
#endif
                    close(CTRL_SOCK1);
                    return ret;
                }
            }
#if defined(_FTP_DEBUG_)
            printf("%d: Rcvd Command: %s", CTRL_SOCK1, dbuf);
#endif
            proc_ftpd(CTRL_SOCK1, (char *)dbuf);
            con_remain_cnt2 = 0;
        }
        break;

    case SOCK_CLOSE_WAIT:
#if defined(_FTP_DEBUG_)
        printf("%d:CloseWait\r\n", CTRL_SOCK1);
#endif
        if ((ret = disconnect(CTRL_SOCK1)) != SOCK_OK)
            return ret;
        connect_count--;
#if defined(_FTP_DEBUG_)
        printf("%d:Closed\r\n", CTRL_SOCK1);
#endif
        break;

    case SOCK_CLOSED:
#if defined(_FTP_DEBUG_)
        printf("%d:FTPStart\r\n", CTRL_SOCK1);
#endif
        if ((ret = socket(CTRL_SOCK1, Sn_MR_TCP, IPPORT_FTP, 0x0)) != CTRL_SOCK1)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:socket() error:%ld\r\n", CTRL_SOCK1, ret);
#endif
            close(CTRL_SOCK1);
            return ret;
        }
        break;

    case SOCK_INIT:
#if defined(_FTP_DEBUG_)
        printf("%d:Opened\r\n", CTRL_SOCK1);
#endif
        strcpy(ftp.workingdir, "/");
        if ((ret = listen(CTRL_SOCK1)) != SOCK_OK)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:Listen error\r\n", CTRL_SOCK1);
#endif
            return ret;
        }
        connect_state_control1 = 0;
        con_remain_cnt2        = 0;
#if defined(_FTP_DEBUG_)
        printf("%d:Listen ok\r\n", CTRL_SOCK1);
#endif
        break;

    default:
        break;
    }
#endif

    ///////////////////////////////// ftp data part
#if 1
    switch (getSn_SR(DATA_SOCK))
    {
    case SOCK_ESTABLISHED:
        if (!connect_state_data)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:FTP Data socket Connected\r\n", DATA_SOCK);
#endif
            connect_state_data = 1;
        }

        switch (ftp.current_cmd)
        {
        case LIST_CMD:
        case MLSD_CMD:
#if defined(_FTP_DEBUG_)
            printf("previous size: %d\r\n", size);
#endif
#if defined(F_FILESYSTEM)
            scan_files(ftp.workingdir, dbuf, (int *)&size);
#endif
#if defined(_FTP_DEBUG_)
            printf("returned size: %d\r\n", size);
            printf("%s\r\n", dbuf);
#endif
#if !defined(F_FILESYSTEM)
            if (strncmp(ftp.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");
#endif
            size = strlen((char *)dbuf);
            send(DATA_SOCK, dbuf, size);
            ftp.current_cmd = NO_CMD;
            disconnect(DATA_SOCK);
            size = sprintf((char *)dbuf, "226 Successfully transferred \"%s\"\r\n", ftp.workingdir);
            send(cur_sn, dbuf, size);
            break;

        case RETR_CMD:
#if defined(_FTP_DEBUG_)
            printf("filename to retrieve : %s %d\r\n", ftp.filename, strlen(ftp.filename));
#endif
#if defined(F_FILESYSTEM)
            ftp.fr = f_open(&(ftp.fil), (const char *)ftp.filename, FA_READ);
            if (ftp.fr == FR_OK)
            {
                remain_filesize = ftp.fil.fsize;
#if defined(_FTP_DEBUG_)
                printf("f_open return FR_OK\r\n");
#endif
                do
                {
#if defined(_FTP_DEBUG_)
                    printf("remained file size: %d\r\n", ftp.fil.fsize);
#endif
                    memset(dbuf, 0, _MAX_SS);
                    if (remain_filesize > _MAX_SS)
                        send_byte = _MAX_SS;
                    else
                        send_byte = remain_filesize;
                    ftp.fr = f_read(&(ftp.fil), dbuf, send_byte, &blocklen);
                    if (ftp.fr != FR_OK)
                        break;
#if defined(_FTP_DEBUG_)
                    printf("#");
                    printf("----->fsize:%d recv:%d len:%d \r\n", remain_filesize, send_byte, blocklen);
                    printf("----->fn:%s data:%s \r\n", ftp.filename, dbuf);
#endif
                    send(DATA_SOCK, dbuf, blocklen);
                    remain_filesize -= blocklen;
                } while (remain_filesize != 0);
#if defined(_FTP_DEBUG_)
                printf("\r\nFile read finished\r\n");
#endif
                ftp.fr = f_close(&(ftp.fil));
            }
            else
            {
#if defined(_FTP_DEBUG_)
                printf("File Open Error: %d\r\n", ftp.fr);
#endif
            }
#else
            remain_filesize = strlen(ftp.filename);
#if defined(_FTP_DEBUG_)
            printf("<<<<<1recv data[%d]\r\n", remain_datasize);
#endif
            do
            {
                memset(dbuf, 0, _MAX_SS);
                blocklen = sprintf((char *)dbuf, "%s", ftp.filename);
                printf("####1[%d] dbuf:%s\r\n", remain_filesize, dbuf);
                send(DATA_SOCK, dbuf, blocklen);
                remain_filesize -= blocklen;
            } while (remain_filesize != 0);
#endif
            ftp.current_cmd = NO_CMD;
            disconnect(DATA_SOCK);
            size = sprintf((char *)dbuf, "226 Successfully transferred \"%s\"\r\n", ftp.filename);
            send(cur_sn, dbuf, size);
            break;

        case STOR_CMD:
#if defined(_FTP_DEBUG_)
            printf("filename to store : %s %d\r\n", ftp.filename, strlen(ftp.filename));
#endif
#if defined(F_FILESYSTEM)
            ftp.fr = f_open(&(ftp.fil), (const char *)ftp.filename, FA_CREATE_ALWAYS | FA_WRITE);
            if (ftp.fr == FR_OK)
            {
#if defined(_FTP_DEBUG_)
                printf("f_open return FR_OK\r\n");
#endif
                while (1)
                {
                    if ((remain_datasize = getSn_RX_RSR(DATA_SOCK)) > 0)
                    {
                        while (1)
                        {
                            memset(dbuf, 0, _MAX_SS);
                            if (remain_datasize > _MAX_SS)
                                recv_byte = _MAX_SS;
                            else
                                recv_byte = remain_datasize;
                            ret = recv(DATA_SOCK, dbuf, recv_byte);
#if defined(_FTP_DEBUG_)
                            printf("----->fn:%s data:%s \r\n", ftp.filename, dbuf);
#endif
                            ftp.fr = f_write(&(ftp.fil), dbuf, (UINT)ret, &blocklen);
#if defined(_FTP_DEBUG_)
                            printf("----->dsize:%d recv:%d len:%d \r\n", remain_datasize, ret, blocklen);
#endif
                            remain_datasize -= blocklen;
                            if (ftp.fr != FR_OK)
                            {
#if defined(_FTP_DEBUG_)
                                printf("f_write failed\r\n");
#endif
                                break;
                            }
                            if (remain_datasize <= 0)
                                break;
                        }
                        if (ftp.fr != FR_OK)
                        {
#if defined(_FTP_DEBUG_)
                            printf("f_write failed\r\n");
#endif
                            break;
                        }
#if defined(_FTP_DEBUG_)
                        printf("#");
#endif
                    }
                    else
                    {
                        if (getSn_SR(DATA_SOCK) != SOCK_ESTABLISHED)
                            break;
                    }
                }
#if defined(_FTP_DEBUG_)
                printf("\r\nFile write finished\r\n");
#endif
                ftp.fr = f_close(&(ftp.fil));
            }
            else
            {
#if defined(_FTP_DEBUG_)
                printf("File Open Error: %d\r\n", ftp.fr);
#endif
            }
#else
            while (1)
            {
                if ((remain_datasize = getSn_RX_RSR(DATA_SOCK)) > 0)
                {
#if defined(_FTP_DEBUG_)
                    printf("<<<<<2recv data[%ld]\r\n", remain_datasize);
#endif
                    while (1)
                    {
                        memset(dbuf, 0, _MAX_SS);
                        if (remain_datasize > _MAX_SS)
                            recv_byte = _MAX_SS;
                        else
                            recv_byte = remain_datasize;
                        ret = recv(DATA_SOCK, dbuf, recv_byte);
                        if (ret < 0)
                        {
#if defined(_FTP_DEBUG_)
                            printf("finish ret[%d\r\n", ret);
#endif
                            break;
                        }
#if defined(_FTP_DEBUG_)
                        printf("#####2[%ld] dbuf:%s\r\n", remain_datasize, dbuf);
#endif
                        remain_datasize -= ret;
#if defined(_FTP_DEBUG_)
                        printf("###ret:%ld,remain:%d\r\n", ret, remain_datasize);
#endif
                        if (remain_datasize <= 0)
                            break;
                    }
                }
                else
                {
                    if (getSn_SR(DATA_SOCK) != SOCK_ESTABLISHED)
                        break;
                }
            }
#endif
            ftp.current_cmd = NO_CMD;
            disconnect(DATA_SOCK);
            size = sprintf((char *)dbuf, "226 Successfully transferred \"%s\"\r\n", ftp.filename);
            send(cur_sn, dbuf, size);
            break;

        case NO_CMD:
        default:
            break;
        }
        break;

    case SOCK_CLOSE_WAIT:
#if defined(_FTP_DEBUG_)
        printf("%d:CloseWait\r\n", DATA_SOCK);
#endif
        if ((ret = disconnect(DATA_SOCK)) != SOCK_OK)
            return ret;
#if defined(_FTP_DEBUG_)
        printf("%d:Closed\r\n", DATA_SOCK);
#endif
        break;

    case SOCK_CLOSED:
        if (ftp.dsock_state == DATASOCK_READY)
        {
            if (ftp.dsock_mode == PASSIVE_MODE)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:FTPDataStart, port : %d\r\n", DATA_SOCK, local_port);
#endif
                if ((ret = socket(DATA_SOCK, Sn_MR_TCP, local_port, 0x0)) != DATA_SOCK)
                {
#if defined(_FTP_DEBUG_)
                    printf("%d:socket() error:%ld\r\n", DATA_SOCK, ret);
#endif
                    close(DATA_SOCK);
                    return ret;
                }
                local_port++;
                if (local_port > 50000)
                    local_port = 35000;
            }
            else
            {
#if defined(_FTP_DEBUG_)
                printf("%d:FTPDataStart, port : %d\r\n", DATA_SOCK, IPPORT_FTPD);
#endif
                if ((ret = socket(DATA_SOCK, Sn_MR_TCP, IPPORT_FTPD, 0x0)) != DATA_SOCK)
                {
#if defined(_FTP_DEBUG_)
                    printf("%d:socket() error:%ld\r\n", DATA_SOCK, ret);
#endif
                    close(DATA_SOCK);
                    return ret;
                }
            }
            ftp.dsock_state = DATASOCK_START;
        }
        break;

    case SOCK_INIT:
#if defined(_FTP_DEBUG_)
        printf("%d:Opened\r\n", DATA_SOCK);
#endif
        if (ftp.dsock_mode == PASSIVE_MODE)
        {
            if ((ret = listen(DATA_SOCK)) != SOCK_OK)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:Listen error\r\n", DATA_SOCK);
#endif
                return ret;
            }
#if defined(_FTP_DEBUG_)
            printf("%d:Listen ok\r\n", DATA_SOCK);
#endif
        }
        else
        {
            if ((ret = connect(DATA_SOCK, remote_ip.cVal, remote_port)) != SOCK_OK)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:Connect error\r\n", DATA_SOCK);
#endif
                return ret;
            }
        }
        connect_state_data = 0;
        break;

    default:
        break;
    }
#endif

    return 0;
}

进入 ftpd_run() 函数后,程序会执行一个 TCP Server 模式的状态机。当 socket 处于 SOCK_ESTABLISHED 状态时,首次进入会向客户端发送欢迎消息;后续则监听客户端指令,收到指令后进入 proc_ftpd() 进行处理。

连接超时功能

当宏定义 connect_timeout_en 的值设置为 1 时,会启用连接超时功能。如果在超过宏定义 remain_time 所设定的时长后仍然没有进行任何操作,系统将自动断开连接。

proc_ftpd() 函数如下:

/**
 * Processes FTP-related data or request.
 *
 * @param sn   Identifier for distinguishing requests/sessions.
 * @param buf  Buffer to store result or status information.
 *
 * @return Character indicating operation status ('S' for success, 'E' for error, etc.).
 *
 * Note: Refer to documentation for exact behavior and meaning of return value.
 */
char proc_ftpd(uint8_t sn, char *buf)
{
    char **cmdp, *cp, *arg, *tmpstr;
    char   sendbuf[200];
    int    slen;
    long   ret;

    // Translate first word to lower case
    for (cp = buf; *cp != ' ' && *cp != '\0'; cp++)
        *cp = tolower(*cp);

    // Find command in table; if not present, return syntax error
    for (cmdp = commands; *cmdp != NULL; cmdp++)
        if (strncmp(*cmdp, buf, strlen(*cmdp)) == 0)
            break;

    if (*cmdp == NULL)
    {
        slen = sprintf(sendbuf, "500 Unknown command '%s'\r\n", buf);
        send(sn, (uint8_t *)sendbuf, slen);
        return 0;
    }

    // Allow only USER, PASS and QUIT before logging in
    if (ftp.state == FTPS_NOT_LOGIN)
    {
        switch (cmdp - commands)
        {
        case USER_CMD:
        case PASS_CMD:
        case QUIT_CMD:
            break;
        default:
            slen = sprintf(sendbuf, "530 Please log in with USER and PASS\r\n");
            send(sn, (uint8_t *)sendbuf, slen);
            return 0;
        }
    }

    arg = &buf[strlen(*cmdp)];
    while (*arg == ' ')
        arg++;

    /* Execute specific command */
    switch (cmdp - commands)
    {
    case USER_CMD:
#if defined(_FTP_DEBUG_)
        printf("USER_CMD : %s", arg);
#endif
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
        if (ftp.ID_Enable == STATUS_USED)
        {
            if (strcmp(ftp.username, arg) != 0)
            {
                slen = sprintf(sendbuf, "430 Invalid username\r\n");
                ret  = send(sn, (uint8_t *)sendbuf, slen);
                if (ret < 0)
                {
#if defined(_FTP_DEBUG_)
                    printf("%d:send() error:%ld\r\n", sn, ret);
#endif
                    close(sn);
                    return ret;
                }
                break;
            }
        }
        else
        {
            strcpy(ftp.username, arg);
        }
        slen = sprintf(sendbuf, "331 Enter PASS command\r\n");
        ret  = send(sn, (uint8_t *)sendbuf, slen);
        if (ret < 0)
        {
#if defined(_FTP_DEBUG_)
            printf("%d:send() error:%ld\r\n", sn, ret);
#endif
            close(sn);
            return ret;
        }
        break;

    case PASS_CMD:
#if defined(_FTP_DEBUG_)
        printf("PASS_CMD : %s", arg);
#endif
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
        if (ftp.PW_Enable == STATUS_USED)
        {
            if (strcmp(ftp.userpassword, arg) != 0)
            {
                slen = sprintf(sendbuf, "430 Invalid password\r\n");
                ret  = send(sn, (uint8_t *)sendbuf, slen);
                if (ret < 0)
                {
#if defined(_FTP_DEBUG_)
                    printf("%d:send() error:%ld\r\n", sn, ret);
#endif
                    close(sn);
                    return ret;
                }
                break;
            }
        }
        ftplogin(sn, arg);
        break;

    case TYPE_CMD:
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
        switch (arg[0])
        {
        case 'A':
        case 'a':
            ftp.type = ASCII_TYPE;
            slen = sprintf(sendbuf, "200 Type set to %s\r\n", arg);
            send(sn, (uint8_t *)sendbuf, slen);
            break;
        case 'B':
        case 'b':
        case 'I':
        case 'i':
            ftp.type = IMAGE_TYPE;
            slen = sprintf(sendbuf, "200 Type set to %s\r\n", arg);
            send(sn, (uint8_t *)sendbuf, slen);
            break;
        default:
            slen = sprintf(sendbuf, "501 Unknown type \"%s\"\r\n", arg);
            send(sn, (uint8_t *)sendbuf, slen);
            break;
        }
        break;

    case FEAT_CMD:
        slen = sprintf(sendbuf, "211-Features:\r\n MDTM\r\n REST STREAM\r\n SIZE\r\n MLST size*;type*;create*;modify*;\r\n MLSD\r\n UTF8\r\n CLNT\r\n MFMT\r\n211 END\r\n");
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    case QUIT_CMD:
#if defined(_FTP_DEBUG_)
        printf("QUIT_CMD\r\n");
#endif
        slen = sprintf(sendbuf, "221 Goodbye!\r\n");
        send(sn, (uint8_t *)sendbuf, slen);
        disconnect(sn);
        break;

    case RETR_CMD:
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
#if defined(_FTP_DEBUG_)
        printf("RETR_CMD\r\n");
#endif
        if (strlen(ftp.workingdir) == 1)
            sprintf(ftp.filename, "/%s", arg);
        else
            sprintf(ftp.filename, "%s/%s", ftp.workingdir, arg);
        slen = sprintf(sendbuf, "150 Opening data channel for file downloand from server of \"%s\"\r\n", ftp.filename);
        send(sn, (uint8_t *)sendbuf, slen);
        ftp.current_cmd = RETR_CMD;
        break;

    case APPE_CMD:
    case STOR_CMD:
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
#if defined(_FTP_DEBUG_)
        printf("STOR_CMD\r\n");
#endif
        if (strlen(ftp.workingdir) == 1)
            sprintf(ftp.filename, "/%s", arg);
        else
            sprintf(ftp.filename, "%s/%s", ftp.workingdir, arg);
        slen = sprintf(sendbuf, "150 Opening data channel for file upload to server of \"%s\"\r\n", ftp.filename);
        send(sn, (uint8_t *)sendbuf, slen);
        ftp.current_cmd = STOR_CMD;
        if (ftp.dsock_mode == ACTIVE_MODE)
        {
            if ((ret = connect(DATA_SOCK, remote_ip.cVal, remote_port)) != SOCK_OK)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:Connect error\r\n", DATA_SOCK);
#endif
                return ret;
            }
        }
        connect_state_data = 0;
        break;

    case PORT_CMD:
#if defined(_FTP_DEBUG_)
        printf("PORT_CMD\r\n");
#endif
        if (pport(arg) == -1)
        {
            slen = sprintf(sendbuf, "501 Bad port syntax\r\n");
            send(sn, (uint8_t *)sendbuf, slen);
        }
        else
        {
            ftp.dsock_mode  = ACTIVE_MODE;
            ftp.dsock_state = DATASOCK_READY;
            slen            = sprintf(sendbuf, "200 PORT command successful.\r\n");
            send(sn, (uint8_t *)sendbuf, slen);
        }
        break;

    case MLSD_CMD:
#if defined(_FTP_DEBUG_)
        printf("MLSD_CMD\r\n");
#endif
        slen = sprintf(sendbuf, "150 Opening data channel for directory listing of \"%s\"\r\n", ftp.workingdir);
        send(sn, (uint8_t *)sendbuf, slen);
        ftp.current_cmd = MLSD_CMD;
        break;

    case LIST_CMD:
#if defined(_FTP_DEBUG_)
        printf("LIST_CMD\r\n");
#endif
        slen = sprintf(sendbuf, "150 Opening data channel for directory listing of \"%s\"\r\n", ftp.workingdir);
        send(sn, (uint8_t *)sendbuf, slen);
        ftp.current_cmd = LIST_CMD;
        break;

    case NLST_CMD:
#if defined(_FTP_DEBUG_)
        printf("NLST_CMD\r\n");
#endif
        break;

    case SYST_CMD:
        slen = sprintf(sendbuf, "215 UNIX emulated by WIZnet\r\n");
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    case PWD_CMD:
    case XPWD_CMD:
        slen = sprintf(sendbuf, "257 \"%s\" is current directory.\r\n", ftp.workingdir);
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    case PASV_CMD:
        slen = sprintf(sendbuf, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n", local_ip.cVal[0], local_ip.cVal[1], local_ip.cVal[2], local_ip.cVal[3], local_port >> 8, local_port & 0x00ff);
        send(sn, (uint8_t *)sendbuf, slen);
        if (getSn_SR(DATA_SOCK) == SOCK_ESTABLISHED)
        {
#if defined(_FTP_DEBUG_)
            printf("data disconnect: %d\r\n", DATA_SOCK);
#endif
            disconnect(DATA_SOCK);
        }
        ftp.dsock_mode  = PASSIVE_MODE;
        ftp.dsock_state = DATASOCK_READY;
        cur_sn          = sn;
#if defined(_FTP_DEBUG_)
        printf("PASV port: %d\r\n", local_port);
#endif
        break;

    case SIZE_CMD:
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
        if (slen > 3)
        {
            tmpstr  = strrchr(arg, '/');
            *tmpstr = 0;
#if defined(F_FILESYSTEM)
            slen = get_filesize(arg, tmpstr + 1);
#else
            slen = _MAX_SS;
#endif
            if (slen > 0)
                slen = sprintf(sendbuf, "213 %d\r\n", slen);
            else
                slen = sprintf(sendbuf, "550 File not Found\r\n");
        }
        else
        {
            slen = sprintf(sendbuf, "550 File not Found\r\n");
        }
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    case CWD_CMD:
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
        if (slen > 3)
        {
            arg[slen - 3] = 0x00;
            tmpstr        = strrchr(arg, '/');
            *tmpstr       = 0;
#if defined(F_FILESYSTEM)
            slen = get_filesize(arg, tmpstr + 1);
#else
            slen = 0;
#endif
            *tmpstr = '/';
            if (slen == 0)
            {
                slen = sprintf(sendbuf, "213 %d\r\n", slen);
                strcpy(ftp.workingdir, arg);
                slen = sprintf(sendbuf, "250 CWD successful. \"%s\" is current directory.\r\n", ftp.workingdir);
            }
            else
            {
                slen = sprintf(sendbuf, "550 CWD failed. \"%s\"\r\n", arg);
            }
        }
        else
        {
            strcpy(ftp.workingdir, arg);
            slen = sprintf(sendbuf, "250 CWD successful. \"%s\" is current directory.\r\n", ftp.workingdir);
        }
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    case MKD_CMD:
    case XMKD_CMD:
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
#if defined(F_FILESYSTEM)
        if (f_mkdir(arg) != 0)
        {
            slen = sprintf(sendbuf, "550 Can't create directory. \"%s\"\r\n", arg);
        }
        else
        {
            slen = sprintf(sendbuf, "257 MKD command successful. \"%s\"\r\n", arg);
        }
#else
        slen = sprintf(sendbuf, "550 Can't create directory. Permission denied\r\n");
#endif
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    case DELE_CMD:
        slen          = strlen(arg);
        arg[slen - 1] = 0x00;
        arg[slen - 2] = 0x00;
#if defined(F_FILESYSTEM)
        if (f_unlink(arg) != 0)
        {
            slen = sprintf(sendbuf, "550 Could not delete. \"%s\"\r\n", arg);
        }
        else
        {
            slen = sprintf(sendbuf, "250 Deleted. \"%s\"\r\n", arg);
        }
#else
        slen = sprintf(sendbuf, "550 Could not delete. Permission denied\r\n");
#endif
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    case XCWD_CMD:
    case ACCT_CMD:
    case XRMD_CMD:
    case RMD_CMD:
    case STRU_CMD:
    case MODE_CMD:
    case XMD5_CMD:
        slen = sprintf(sendbuf, "502 Command does not implemented yet.\r\n");
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    default:
        slen = sprintf(sendbuf, "500 Unknown command '%s'\r\n", arg);
        send(sn, (uint8_t *)sendbuf, slen);
        break;
    }

    return 1;
}

进入 proc_ftpd() 函数后,程序首先将接收的命令转换为小写并在命令表中查找,未找到时发送错误信息。登录前仅允许 USER、PASS、QUIT 命令,其余报错。对于不同命令执行相应操作:USER_CMD 处理用户名验证,PASS_CMD 进行密码验证和登录,TYPE_CMD 处理传输类型设置,QUIT_CMD 断开连接,RETR_CMD/STOR_CMD 等文件操作命令,以及 PORT_CMD/PASV_CMD 等数据连接模式相关命令。

运行验证

烧录例程运行后,首先进行了 PHY 链路检测,然后打印设置网络信息。FTP 服务器初始化后,在 FileZilla 中连接即可进行文件传输操作。

W55MH32 FTP Server 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
====================================================================================================

 FTP ID[6]:wiznet
 FTP PW[6]:wiznet
1:Opened
1:Listen ok
3:Opened
3:Listen ok

测试前提

测试实例需要 PC 端和 W55MH32 处于同一网段。打开 FileZilla 软件,填写主机 IP(192.168.1.6)、用户名(wiznet)、密码(wiznet)、端口号(21),连接 FTP 服务器。连接成功后,可将本地文件拖动到远程站点进行上传。

WIZ UartTool 串口输出(FTP 服务器初始化 + wiznet 登录)+ FileZilla 客户端(连接 192.168.1.6,目录列表)— FTP 服务器连接测试
WIZ UartTool 串口输出(test_file.txt)+ FileZilla 客户端(文件上传成功)— FTP 文件上传测试

常见问题

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

主动模式下服务器主动连接客户端端口,适合服务器端网络无防火墙限制的环境。被动模式下客户端主动连接服务器端口,适合客户端在 NAT 或防火墙后的情况。由于大多数客户端都在 NAT 后,实际应用中被动模式(PASV)更为常用。W55MH32 的 FTP 服务器同时支持两种模式,可根据客户端网络环境灵活选择。

Q: W55MH32 FTP 服务器支持哪些文件操作

支持文件上传(STOR)、下载(RETR)、删除(DELE),以及目录操作包括创建目录(MKD)、删除目录(RMD)、更改工作目录(CWD)、显示当前目录(PWD)和列出目录内容(LIST/MLSD)。启用 F_FILESYSTEM 宏定义后,这些操作会通过 FatFs 文件系统持久化存储;未启用时仅支持模拟文件操作。

Q: 如何修改 FTP 服务器的用户名和密码

在 ftpd_init() 函数中,ftp.ID_Enable 和 ftp.PW_Enable 设为 STATUS_USED 时,服务器使用预设的 ftp_ID 和 ftp_PW 进行认证。修改这两个宏定义的值即可更改用户名和密码。若将 ID_Enable 或 PW_Enable 设为 STATUS_UNUSED,则可禁用对应的认证项(如允许匿名登录)。

Q: FTP 连接超时功能如何配置

通过宏定义 connect_timeout_en 控制是否启用连接超时功能(1 为开启,0 为关闭),宏定义 remain_time 设定最大允许无操作时长。启用后,若客户端在 remain_time 时间内没有任何操作,服务器将自动断开该连接,防止空闲连接占用 socket 资源。W55MH32 的 FTP 服务器支持两个控制连接 socket(CTRL_SOCK 和 CTRL_SOCK1),可同时服务两个客户端。

总结

  • 协议基础:FTP 采用客户端-服务器架构,基于 TCP 双连接(控制连接 21 端口 + 数据连接),支持主动/被动两种传输模式
  • 应用场景:固件升级、数据采集与传输、远程配置与日志管理、嵌入式 Web 服务器文件管理
  • 初始化:ftpd_init() 设置服务器状态、用户认证、网络地址端口并创建 TCP socket
  • 持续交互:ftpd_run() 基于 TCP Server 状态机管理控制连接与数据连接,支持超时自动断开
  • 命令处理:proc_ftpd() 解析 FTP 命令,处理认证、文件操作、目录操作及数据传输模式切换

下一篇文章将聚焦 FTP 协议客户端模式,解析其核心原理及在文件传输中的应用,同时讲解如何在 W55MH32 上实现 FTP 客户端功能,敬请期待!

下载本章例程

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

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

Andy

W5500 技术支持专家