FTP Client
本篇文章,我们将详细介绍如何在W55MH32芯片上面实现FTP协议的客户端模式。并通过实战例程,为大家讲解如何在W55MH32上使用FTP协议的客户端模式来访问FTP服务器并下载文件。
该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关W55MH32的初始化过程,请参考Network Install章节,这里将不再赘述。
FTP协议简介
FTP(File Transfer Protocol,文件传输协议)是遵循文件传输协议(FTP),在网络中提供文件传输服务的重要组件。它采用客户端 - 服务器架构,工作模式分为主动和被动。主动模式下,服务器主动发起数据连接;被动模式中,由客户端发起数据连接。用户访问时,需进行身份验证,常见方式是输入用户名和密码,部分服务器也支持匿名登录。
FTP协议特点
- 基于 TCP 传输:
- 分离控制与数据:
- 控制连接用于发送命令和接收响应。
- 数据连接用于文件内容或目录信息的传输。
- 支持多种传输模式:
- 主动模式(Active Mode):服务器主动连接客户端的数据端口。
- 被动模式(Passive Mode):客户端主动连接服务器提供的数据端口,解决 NAT 防火墙限制。
- 支持多种操作方式:
- 文件上传(STOR)、下载(RETR)、删除(DELE)。
- 目录操作(MKD、RMD、CWD、PWD)。
- 获取文件列表(LIST、NLST)。
- 明文传输(传统 FTP):
- 灵活的用户认证机制:
- 支持匿名登录。
- 支持认证用户名和密码。
FTP 使用两个 TCP 连接:控制连接(端口 21)和数据连接(端口 20 或 PASV 模式动态分配的端口),确保可靠的数据传输。
用户名、密码及数据以明文形式传输,不安全。安全改进:FTPS(FTP Secure,基于 SSL/TLS)和 SFTP(Secure File Transfer Protocol,基于 SSH)。
FTP协议应用场景
接下来,我们了解下在W55MH32上,可以使用FTP协议完成哪些操作及应用呢?
- 固件升级:嵌入式设备通过 FTP 下载新固件或软件更新包进行系统升级。适用于需要定期更新功能的设备,如路由器、工业控制设备等。
- 数据采集与传输:嵌入式设备(如传感器节点或数据记录器)将采集的数据上传至远程服务器进行存储和分析。例如:环境监测设备将温湿度数据上传到服务器。
- 远程配置与日志管理:设备通过 FTP 下载配置文件或上传日志信息供管理员分析和排错。适用于工业自动化设备和嵌入式监控系统。
- 嵌入式 Web 服务器的文件管理:许多嵌入式设备内置简易 Web 服务器,用于文件存储或内容分发,通过 FTP 管理这些文件资源。
FTP协议基本工作流程
- 建立控制连接
- 客户端初始化:客户端启动 FTP 客户端程序,指定要连接的 FTP 服务器的地址和端口号(端口号为 21)。
- TCP 连接建立:客户端通过 TCP 协议向服务器的 21 端口发起连接请求。服务器监听该端口,接收到请求后,与客户端建立起一条 TCP 连接,这个连接被称为控制连接,主要用于传输 FTP 命令和服务器的响应信息。
- 身份验证:连接建立后,服务器会提示客户端输入用户名和密码进行身份验证。客户端发送相应的用户名和密码信息到服务器,服务器验证通过后,才允许客户端进行后续操作。也有一些匿名 FTP 服务器,允许用户以 “anonymous” 作为用户名,以电子邮件地址作为密码进行登录,提供公开的文件访问服务 。
- 传输模式选择
- 主动模式(PORT 模式):客户端通过控制连接告诉服务器自己的数据端口(客户端随机开放的一个端口),服务器使用 20 端口主动连接客户端的数据端口来传输数据。
- 被动模式(PASV 模式):客户端发送 PASV 命令给服务器,服务器在控制连接上告知客户端自己开放的一个临时数据端口(通常是 1024 以上的端口),然后客户端使用自己的一个随机端口连接服务器的这个临时数据端口来传输数据。
- 数据传输
- 上传文件:客户端向服务器发送 STOR(存储)命令,然后通过数据连接将本地文件数据发送到服务器。服务器接收到数据后,将其存储在指定的目录下。
- 下载文件:客户端向服务器发送 RETR(检索)命令,请求下载服务器上的文件。服务器通过数据连接将文件数据发送给客户端,客户端接收数据并将其保存到本地指定位置。
- 目录操作:客户端还可以发送诸如LIST(列出目录内容)、CWD(更改工作目录)、MKD(创建目录)、RMD(删除目录)等命令,服务器执行相应操作,并通过控制连接返回操作结果。执行这些命令时,若需要传输目录列表等数据,也会通过数据连接进行传输。
- 关闭连接
- 数据连接关闭:在完成文件传输或其他操作后,数据连接会被关闭。如果还有其他操作需要进行,客户端和服务器可以根据需要重新建立数据连接。
- 控制连接关闭:当客户端完成所有操作后,会向服务器发送 QUIT 命令,服务器接收到该命令后,会关闭控制连接。至此,客户端与服务器之间的 FTP 会话结束。
客户端和服务器在控制连接上协商数据传输模式,主要有两种模式:
根据用户的操作需求,通过数据连接进行文件或目录相关操作:
主动模式与被动模式详解
主动模式(Active Mode):
- 客户端打开一个端口并监听。
- 客户端通过控制连接告诉服务器自己的 IP 和端口。
- 服务器主动连接到客户端指定的端口传输数据。
被动模式(Passive Mode):
- 客户端通过控制连接请求被动模式。
- 服务器打开一个随机端口并通过控制连接告知客户端。
- 客户端主动连接到服务器指定的端口传输数据。
优缺点对比:
- 客户端通过控制连接请求被动模式。
- 主动模式更适合在服务器端网络无防火墙限制的环境。
- 被动模式更适合客户端在 NAT 或防火墙后的情况。
FTP报文解析
FTP 报文分为命令和响应报文,命令报文用于发送操作请求,响应报文用于返回结果。
命令报文格式为“<命令><参数>\r\n”,字段解释如下:
- <命令>:FTP命令(如 USER、PASS)。
- <参数>:命令的附加信息(如用户名、文件名)。
例如“USER username\r\n”。常见的命令包括登录 (USER, PASS)、文件操作 (RETR, STOR)、目录操作 (LIST, CWD) 等。每个 FTP 报文由命令或响应代码、状态码及附加数据组成,状态码用于指示操作结果。
以下是 FTP 常见命令:
- USER:提供用户名进行身份验证。
- PASS:提供密码进行身份验证。
- CWD:更改当前工作目录。
- PWD:显示当前工作目录。
- LIST:列出目录下的文件和子目录。
- RETR: 从服务器下载文件。
- STOR:上传文件到服务器。
- DELE:删除指定文件。
- MKD:创建新目录。
- RMD:删除目录。
- QUIT:终止会话并退出。
- TYPE:设置文件传输类型(ASCII 或 Binary)。
- PORT: 指定数据连接的端口。
- PASV: 启用被动模式,服务器指定端口供客户端连接。
响应报文格式为“<状态码><说明文字>\r\n”,字段解释如下:
- <状态码>:三位数字表示状态。
- <说明文字>:状态的文字描述。
例如“230 User logged in, proceed.\r\n”。以下是FTP常见的响应码:
- 1xx(信息性响应): 主要是提供一些初步的信息,通常表示服务器正在处理请求,还没有完成操作。
- 2xx(成功响应): 表示命令成功执行。这是客户端最希望看到的响应类型之一,说明请求的操作(如登录、文件传输等)顺利完成。
- 3xx(补充信息响应): 表示服务器需要一些额外的信息才能完成操作。通常是在身份验证或者文件定位等过程中出现。
- 4xx(暂时错误响应): 表示客户端的请求有问题,但错误是暂时的,可能通过一些调整(如重新发送请求等)可以解决。
- 5xx(永久性错误响应): 表示客户端的请求存在错误,并且这个错误是比较严重的,很难通过简单的调整来纠正。
接着我们来看看FTP获取目录的报文示例:
- 客户端建立TCP连接到服务器的21端口
- 服务器返回:220 Welcome to FTP Server\r\n
- 客户端发送:USER wiznet\r\n
- 服务器返回:331 User wiznet OK.Password required\r\n
- 客户端发送:PASS wiznet\r\n
- 服务器返回:230 User logged in\r\n
- 客户端发送PORT 192,168,1,5,20,100\r\n(主动模式,192,168,1,5是客户端的地址,20,100是客户端期望的端口号20*256+100=5260)
- 服务器返回:200 PORT command successful\r\n
- 客户端发送:LIST\r\n(DIR命令,获取当前目录的文件信息)。
- 服务器回复:150 Opening ASCII mode data connection for file list\r\n
- 服务器向客户端期望的端口号发起TCP连接,并传输目录信息,传输完成后关闭TCP连接。
- 客户端发送:QUIT\r\n(退出FTP会话)
- 服务器回复:221 Goodbye\r\n
实现过程
接下来,我们看看如何在W55MH32上实现FTP协议Client模式。
步骤1:FTP Client模式初始化
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);
}
ftpc_init()函数的主要作用是初始化FTP客户端的配置和状态,包括设置传输模式、本地IP地址和端口、socket、工作目录,并创建用于命令传输的控制socket。
步骤2:在主循环中运行ftpc_run()函数
while (1)
{
ftpc_run(ethernet_buf);
}
ftpc_run()函数如下所示:
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)
{ // Don't need to check SOCKERR_BUSY because it doesn't not occur.
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;
}
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)
{ // Don't need to check SOCKERR_BUSY because it doesn't not occur.
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); // Upload file content
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)
{ // Don't need to check SOCKERR_BUSY because it doesn't not occur.
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;
}
在这个函数中,会分别执行两个TCP状态机,一个用于FTP控制,一个用于FTP数据传输,FTP数据传输的状态机,仅当要进行数据传输时(上传或下载文件)才会开启。
首先看到FTP控制的状态机,大致流程如下图所示:

当用户在选项菜单中选择不同的选项时,系统会根据所选选项发送相应的操作指令。这些操作指令将被发送至服务器,随后服务器会返回相应的响应内容。在proc_ftpc()函数中,程序会根据服务器返回的响应码来判断操作是否成功,并依据响应码的具体情况执行下一步操作。一旦相应操作完成,程序将自动返回选项菜单,以便用户继续进行后续操作。
当FTP控制的socket收到数据时,会调用proc_ftpc()函数进行处理,proc_ftpc()函数如下:
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;
}
运行结果
请注意:
测试实例需要PC端和W55MH32处于同一网段
烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息,打印出FTP连接信息,并提示输入信息进行连接,如下图所示:

FileZilla Server Interface 是 FileZilla Server 的图形用户界面(GUI),用于管理和配置 FileZilla Server。FileZilla Server 是一款开源的、跨平台的 FTP 和 FTP over TLS (FTPS) 服务器,它常用于文件共享和数据传输。下载链接:服务端 - FileZilla中文网。下载、安装完成后打开。
第一步:建立服务器,设置一个账户(用户名:wiznet 密码:123456)


第二步:添加一个共享文件夹,用于客户端和服务器的文件操作使用

第三步:在串口助手界面根据串口消息提示先输入用户名,再输入密码,然后就会进入选项菜单
请注意:
发送的内容的结尾需要带上回车换行符

第四步:我们选择6,从服务器获取文件

第五步:然后输入需要获取文件的名字,如图所示,成功获取文件内容

其他操作类似,这里就不在一一进行讲解。
总结
本文讲解了如何在 W55MH32 芯片上实现 FTP 协议的客户端模式,通过实战例程展示了使用该客户端模式访问 FTP 服务器并下载文件的过程,涵盖 FTP 客户端模式初始化、在主循环中运行相关函数实现与服务器交互等关键步骤。文章详细介绍了 FTP 协议的概念、特点、应用场景、工作流程、主动与被动模式、客户端功能、报文解析,帮助读者理解其在文件传输中的实际应用价值。
下一篇文章将聚焦 WOL(Wake-on-LAN)网络唤醒功能,解析其核心原理及在网络设备管理中的应用,同时讲解如何在W55MH32上实现 WOL 功能,敬请期待!