全部例程

W6300作为FTP服务器

W6300

更新于 2026年10月12日

本篇总结

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

🔑 MH2457、QSPI、UART调试、硬件TOE引擎、FTP、DHCP

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

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W6300-EVB-M4 开发板

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

前置工具准备

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

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

FTP 协议简介

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

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

W6300 的优势

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

硬件连接与拓扑

请注意:

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

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口
  3. 确保 W6300 与 MH2457 之间的 QSPI 连线正确(CSn、SCK、DATA0~DATA3)
┌──────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬───────────────────┬──────────┘
│ │
┌────┴──────┐ ┌────┴──────────────┐
│ W6300 │ │ PC │
│(FTP Srv) │ │ (FileZilla Cli) │
│ TCP :21 │ │ 192.168.1.20 │
└────┬──────┘ └───────────────────┘
│ QSPI ↑
┌────┴──────┐ DEBUG USB
│ MH2457 │──────→ WIZ UartTool
│ MCU │
└───────────┘
网络拓扑:W6300 作为 FTP 服务器(TCP :21),PC 运行 FileZilla 作为客户端连接,MH2457 通过 QSPI 驱动 W6300

工程结构

工程核心文件如下:

Project/
├── main.c                    ← 系统入口:串口、GPIO、QSPI 初始化 + user_run()
└── USER/
    ├── user_main.c           ← 网络地址定义 + user_run() 主流程
    ├── ftpd.c/h              ← FTP 服务器配置封装(ftpd_init / ftpd_run)
    ├── ftp_server.c/h        ← FTP 服务器协议实现(状态机 / 命令解析 / 数据传输)
    ├── wiz_platform.c/h      ← 平台适配:debug_usart_init、wizchip_gpio_init、hspi_init
    └── wiz_interface.c/h     ← W6300 驱动接口:wizchip_initialize、network_init、PHY 检测、软件定时器

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

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

wizchip_getnetinfo(&net_info);
ftpd_init(net_info.ip);

ftpd_init() 是 W6300 平台的封装层,作用是把 ftpd.h 中的宏定义组装成 ftp_server_config_t,然后调用 ftp_server_init() 完成服务器初始化。

static const ftp_server_config_t s_ftpd_config = {
    .control_socket = APP_FTP_CONTROL_SOCKET,
    .data_socket = APP_FTP_DATA_SOCKET,
    .control_socket_secondary = APP_FTP_CONTROL_SOCKET_SECONDARY,
    .control_port = APP_FTP_CONTROL_PORT,
    .active_port = APP_FTP_ACTIVE_PORT,
    .passive_port = APP_FTP_PASSIVE_PORT,
    .user = APP_FTP_USER,
    .password = APP_FTP_PASSWORD,
    .file_name = APP_FTP_FILE_NAME
};

void ftpd_init(uint8_t *src_ip)
{
    (void)src_ip;
    s_ftpd_ready = (ftp_server_init(&s_ftpd_config) == 0) ? 1U : 0U;
}

真正做初始化的是 ftp_server_init():保存配置指针、预生成 LIST / NLST 命令的应答内容(只包含一个固定文件),然后循环为两个控制 Socket 分别调用 ftp_server_open_control() 打开并监听 21 端口。

int ftp_server_init(const ftp_server_config_t *config)
{
    int list_length;
    int name_length;
    uint8_t session;

    if ((config == NULL) || (config->user == NULL) ||
        (config->password == NULL) || (config->file_name == NULL))
    {
        return -1;
    }

    s_ftp_server_config = config;
    name_length = snprintf((char *)s_ftp_server_name_data,
                           sizeof(s_ftp_server_name_data),
                           "%s\r\n",
                           s_ftp_server_config->file_name);
    ...
    list_length = snprintf((char *)s_ftp_server_list_data,
                           sizeof(s_ftp_server_list_data),
                           "-r--r--r-- 1 wiznet wiznet %u Jan 01 00:00 %s\r\n",
                           (unsigned int)(sizeof(s_ftp_server_file_data) - 1U),
                           s_ftp_server_config->file_name);
    ...

    for (session = 0U; session < FTP_SERVER_CONTROL_SESSION_COUNT; ++session)
    {
        s_ftp_server_reopen_tick[session] = 0U;
        s_ftp_server_reopen_error[session] = 0U;

        if (ftp_server_open_control(session) < 0)
        {
            return -1;
        }
    }

    printf("FTP control port: %u\r\n", (unsigned int)s_ftp_server_config->control_port);
    printf("FTP passive port: %u\r\n", (unsigned int)s_ftp_server_config->passive_port);
    printf("FTP server listening\r\n");
    return 0;
}

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

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

while (1)
{
    ftpd_run(ethernet_buf);
}

ftpd_run() 内部调用 ftp_server_run() 驱动整个 FTP 服务器状态机:

void ftp_server_run(void)
{
    uint8_t session;

    for (session = 0U; session < FTP_SERVER_CONTROL_SESSION_COUNT; ++session)
    {
        ftp_server_run_control(session);
    }

    ftp_server_run_transfer();
}

ftp_server_run_control() 对每个控制 Socket 执行一个 TCP Server 状态机,支持 Socket 异常重开、监听、接收命令、断开复位四种处理:

static void ftp_server_run_control(uint8_t session)
{
    uint8_t control_socket;
    uint8_t status;

    if (session >= FTP_SERVER_CONTROL_SESSION_COUNT)
    {
        return;
    }

    s_ftp_server_active_session = session;
    control_socket = ftp_server_control_socket(session);
    status = getSn_SR(control_socket);

    if (status == SOCK_CLOSED)
    {
        uint32_t now = wiz_platform_get_tick();

        if ((uint32_t)(now - s_ftp_server_reopen_tick[session]) < 1000U)
        {
            return;
        }

        s_ftp_server_reopen_tick[session] = now;
        if (ftp_server_open_control(session) == 0)
        {
            s_ftp_server_reopen_error[session] = 0U;
        }
        else if (s_ftp_server_reopen_error[session] == 0U)
        {
            s_ftp_server_reopen_error[session] = 1U;
            printf("FTP server reopen: fail\r\n");
        }

        return;
    }

    if (status == SOCK_INIT)
    {
        if (listen(control_socket) != SOCK_OK)
        {
            (void)close(control_socket);
        }
        return;
    }

    if (status == SOCK_LISTEN)
    {
        return;
    }

    if (status == SOCK_ESTABLISHED)
    {
        if (s_ftp_server_connected[session] == 0U)
        {
            s_ftp_server_connected[session] = 1U;
            s_ftp_server_user_ok[session] = 0U;
            s_ftp_server_logged_in[session] = 0U;
            s_ftp_server_command_length[session] = 0U;
            s_ftp_server_command_overflow[session] = 0U;
            printf("FTP client connected\r\n");

            if (ftp_server_send_reply("220 MH2457 W6300 FTP server ready.\r\n") < 0)
            {
                if (s_ftp_server_data_owner == session)
                {
                    ftp_server_close_data();
                }

                (void)close(control_socket);
                return;
            }
        }

        ftp_server_receive_commands(session);
        return;
    }

    if (status == SOCK_CLOSE_WAIT)
    {
        if (s_ftp_server_data_owner == session)
        {
            ftp_server_close_data();
        }

        (void)disconnect(control_socket);
        (void)close(control_socket);

        if (s_ftp_server_connected[session] != 0U)
        {
            printf("FTP client disconnected\r\n");
        }

        ftp_server_reset_session(session);
        return;
    }

    if ((status != SOCK_LISTEN) && (status != SOCK_ESTABLISHED) &&
        (s_ftp_server_data_owner == session))
    {
        ftp_server_close_data();
    }
}

ftp_server_receive_commands() 负责按行切分命令:每收到一个 \n 就把缓冲区中的数据交给 ftp_server_handle_command() 解析,同时处理命令过长(overflow)和非法字符(0x00)两种异常。命令解析函数负责按命令字分派:

static int ftp_server_handle_command(char *line)
{
    char *argument;
    char *space;
    ...
    ftp_server_upper_command(line);

    if (strcmp(line, "USER") == 0) { ... }
    if (strcmp(line, "PASS") == 0) { ... }
    if (strcmp(line, "QUIT") == 0) { ... }
    if (strcmp(line, "SYST") == 0) { ... }
    if (strcmp(line, "FEAT") == 0) { ... }
    if (strcmp(line, "OPTS") == 0) { ... }
    if (strcmp(line, "NOOP") == 0) { ... }

    if (s_ftp_server_logged_in[session] == 0U)
    {
        return ftp_server_send_reply("530 Please login with USER and PASS.\r\n");
    }

    if ((strcmp(line, "PWD") == 0) || (strcmp(line, "XPWD") == 0)) { ... }
    if (strcmp(line, "CWD") == 0) { ... }
    if (strcmp(line, "CDUP") == 0) { ... }
    if (strcmp(line, "TYPE") == 0) { ... }
    if (strcmp(line, "PASV") == 0) { ... }
    if (strcmp(line, "EPSV") == 0) { ... }
    if ((strcmp(line, "LIST") == 0) || (strcmp(line, "MLSD") == 0)) { ... }
    if (strcmp(line, "NLST") == 0) { ... }
    if (strcmp(line, "SIZE") == 0) { ... }
    if (strcmp(line, "MDTM") == 0) { ... }
    if (strcmp(line, "RETR") == 0) { ... }
    if ((strcmp(line, "STOR") == 0) || (strcmp(line, "APPE") == 0)) { ... }
    if (strcmp(line, "REST") == 0) { ... }
    if (strcmp(line, "PORT") == 0) { ... }
    if ((strcmp(line, "MKD") == 0) || (strcmp(line, "XMKD") == 0)) { ... }
    if (strcmp(line, "DELE") == 0) { ... }
    ...
    return ftp_server_send_reply("502 Command not implemented.\r\n");
}

登录前只允许 USER / PASS / QUIT / SYST / FEAT / OPTS / NOOP,其余命令返回 530 Please login;登录后处理 PWD、CWD、TYPE、PASV / EPSV、LIST / MLSD / NLST、SIZE / MDTM、RETR、STOR / APPE、REST、PORT、MKD / XMKD、DELE 等命令。文件上传(STOR)在本例程中接收后丢弃(没有挂载文件系统),文件下载(RETR)返回 MCU 内存中的固定文件。

ftp_server_run_transfer() 是数据连接状态机,使用一个 ftp_data_phase_t 状态变量在 WAIT_ESTABLISHED → WAIT_SEND_OK / WAIT_RECEIVE → WAIT_CLOSE 之间切换,根据 s_ftp_server_transfer 的值分别处理 LIST / NLST / RETR / STOR 四类传输,并在 15 秒无交互时主动关闭数据连接。

连接超时功能

数据连接的等待时间由 FTP_SERVER_DATA_TIMEOUT_MS 宏定义控制(默认 15000ms)。如果在超时时长内没有任何数据交互,服务器会主动关闭数据连接,避免占用 Socket 资源。

运行验证

烧录例程运行后,首先进行 PHY 链路检测,然后打印设置网络信息,FTP 服务器初始化后打印监听端口并开始监听 21 端口。打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1)。

MH2457 W6300 QSPI example start
W6300 FTP Server example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running

DHCP success!
====================================================================================================
 W6300 network configuration : DHCP

 MAC         : 00:08:DC:12:34:56
 IP          : 192.168.1.8
 Subnet Mask : 255.255.255.0
 Gateway     : 192.168.1.1
 DNS         : 192.168.1.1
====================================================================================================

FTP control port: 21
FTP passive port: 50001
FTP server listening

测试前提

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

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

打开 FileZilla 软件,在 FileZilla 软件上填写主机 ID、用户名、密码、端口号(通常是 21)连接 FTP 服务器。连接成功后,拉住本地站点文件向远程站点(服务器)内拖动,即可成功向服务器传输文件。

常见问题

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

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

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

本例程实现的支持文件上传(STOR / APPE,接收后丢弃,因为没有挂载文件系统)、下载(RETR,返回 MCU 内存中的固定文件),以及目录相关命令包括 PWD / XPWD、CWD、CDUP、LIST / MLSD / NLST、SIZE / MDTM。MKD / DELE 等命令返回 550 Permission denied。REST 只支持 REST 0。

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

修改 ftpd.h 中的 APP_FTP_USER 和 APP_FTP_PASSWORD 宏定义即可。这两个宏定义会被组装到 ftp_server_config_t 中,客户端连接时使用相同的用户名和密码即可通过认证。

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

数据连接的等待时间由 FTP_SERVER_DATA_TIMEOUT_MS 宏定义控制(默认 15000ms)。超时后服务器会主动关闭数据连接并发送 425 响应,防止空闲连接占用 socket 资源。W6300 的 FTP 服务器支持两个控制连接 socket(SOCKET 1 和 SOCKET 3),可同时服务两个客户端。

Q: 为什么上传的文件在服务器上看不到

本例程没有挂载文件系统(如 FatFs),文件上传(STOR / APPE)时数据会被接收后丢弃,只在串口打印接收到的字节数。如果需要在服务器上持久化存储上传的文件,需要挂载文件系统并在 FTP_DATA_PHASE_WAIT_RECEIVE 分支中将接收到的数据写入文件。

总结

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

下载本章例程

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

开发环境: Keil MDK5

Andy

W5500 技术支持专家