W6300作为FTP服务器
更新于 2026年10月12日
本篇总结
在 W6300 上实现 FTP 服务器,支持主动模式与被动模式,可同时服务两个客户端。
硬件平台:本例程基于 W6300-EVB-M4 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W6300-EVB-M4 开发板
本示例演示如何在 W6300 上实现 FTP(文件传输协议)。并通过实战例程,为大家讲解使用 W6300 作为 FTP 服务器、PC 端作为 FTP 客户端进行文件传输、目录操作等多种功能。该例程用到的其他网络协议(例如 DHCP),请参考相关章节。
前置工具准备
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 一致。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口
- 确保 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 │
└───────────┘
工程结构
工程核心文件如下:
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。


打开 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 命令,处理认证、文件操作、目录操作及数据传输模式切换