W55MH32作为FTP客户端
更新于 2026年9月16日
本篇总结
W55MH32 实现 FTP 客户端功能,可访问 FTP 服务器进行文件上传、下载和目录管理。
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 上实现 FTP(文件传输协议)的客户端模式,通过实战例程讲解 FTP 文件上传、下载和目录管理操作。该例程用到的其他网络协议,例如 DHCP,请参考相关章节。
前置工具准备
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 客户端连接服务器进行文件操作。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬───────────────────┬──────────┘
│ │
┌────┴──────┐ ┌────┴──────────────┐
│ W55MH32 │ │ PC │
│(FTP Cli) │ │ (FileZilla Srv) │
│192.168.1.30│ │ TCP :21 │
└───────────┘ └───────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
工程结构
工程位于 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 从服务器获取文件;第五步输入文件名成功获取文件内容。






注意事项
发送的内容的结尾需要带上回车换行符(\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)网络唤醒功能,解析其核心原理及在网络设备管理中的应用,敬请期待!