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


常见问题
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 客户端功能,敬请期待!