第十八章 W55MH32 FTP_Server示例

描述

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机

W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。

在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。

W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入http://www.w5500.com/网站或者私信获取。

此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。

为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。

若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页:http://www.w5500.com/,我们期待与您共同探索W55MH32的无限可能。

数据传输

第十八章 W55MH32 FTP_Server示例

本篇文章,我们将详细介绍如何在W55MH32芯片上实现FTP协议。并通过实战例程,为大家讲解使用W55MH32作为FTP服务器、PC端作为FTP客户端进行文件传输、目录操作等多种功能。

该例程用到的其他网络协议,例如DHCP请参考相关章节。有关W55MH32的初始化过程,请参考Network Install 章节,这里将不再赘述。

1 FTP协议简介

FTP(File Transfer Protocol,文件传输协议)是一种标准的网络协议,用于在客户端和服务器之间传输文件。FTP 客户端协议是基于 FTP 协议实现的,用来指导客户端如何与 FTP 服务器通信,实现文件的上传、下载、目录操作等功能。由于 FTP 最初是以明文传输设计的,不够安全,在 FTP 上加入 SSL/TLS 加密层,可提供加密的控制连接和数据连接。以下是 FTP 客户端协议的主要内容和工作机制的介绍。

2 FTP协议特点

基于 TCP 传输:FTP 使用两个 TCP 连接:控制连接(端口 21)和数据连接(端口 20 或 PASV 模式动态分配的端口),确保可靠的数据传输。

分离控制与数据:

控制连接用于发送命令和接收响应。

数据连接用于文件内容或目录信息的传输。

支持多种传输模式:

主动模式(Active Mode):服务器主动连接客户端的数据端口。

被动模式(Passive Mode):客户端主动连接服务器提供的数据端口,解决 NAT 防火墙限制。

支持多种文件操作:

文件上传(STOR)、下载(RETR)、删除(DELE)。

目录操作(MKD、RMD、CWD、PWD)。

获取文件列表(LIST、NLST)。

明文传输(传统 FTP):用户名、密码及数据以明文形式传输,不安全。安全改进:FTPS(FTP Secure,基于 SSL/TLS)和 SFTP(Secure File Transfer Protocol,基于 SSH)。

灵活的用户认证机制:

支持匿名登录(匿名用户可通过 email 作为密码)。

支持认证用户名和密码。

3 FTP协议应用场景

接下来,我们了解下在W55MH32上,可以使用FTP协议完成哪些操作及应用呢?

固件升级:嵌入式设备通过 FTP 下载新固件或软件更新包进行系统升级。适用于需要定期更新功能的设备,如路由器、工业控制设备等。

数据采集与传输:嵌入式设备(如传感器节点或数据记录器)将采集的数据上传至远程服务器进行存储和分析。例如:环境监测设备将温湿度数据上传到服务器。

远程配置与日志管理:设备通过 FTP 下载配置文件或上传日志信息供管理员分析和排错。适用于工业自动化设备和嵌入式监控系统。

嵌入式 Web 服务器的文件管理:许多嵌入式设备内置简易 Web 服务器,用于文件存储或内容分发,通过 FTP 管理这些文件资源。

4 FTP协议基本工作流程

1. 建立控制连接

客户端初始化:客户端启动 FTP 客户端程序,指定要连接的 FTP 服务器的地址和端口号(端口号为 21)。

TCP 连接建立:客户端通过 TCP 协议向服务器的 21 端口发起连接请求。服务器监听该端口,接收到请求后,与客户端建立起一条 TCP 连接,这个连接被称为控制连接,主要用于传输 FTP 命令和服务器的响应信息。

身份验证:连接建立后,服务器会提示客户端输入用户名和密码进行身份验证。客户端发送相应的用户名和密码信息到服务器,服务器验证通过后,才允许客户端进行后续操作。也有一些匿名 FTP 服务器,允许用户以 “anonymous” 作为用户名,以电子邮件地址作为密码进行登录,提供公开的文件访问服务。

2. 传输模式选择

客户端和服务器在控制连接上协商数据传输模式,主要有两种模式:

主动模式(PORT 模式):客户端通过控制连接告诉服务器自己的数据端口(客户端随机开放的一个端口),服务器使用 20 端口主动连接客户端的数据端口来传输数据。

被动模式(PASV 模式):客户端发送 PASV 命令给服务器,服务器在控制连接上告知客户端自己开放的一个临时数据端口(通常是 1024 以上的端口),然后客户端使用自己的一个随机端口连接服务器的这个临时数据端口来传输数据。

3. 数据传输

根据用户的操作需求,通过数据连接进行文件或目录相关操作:

上传文件:客户端向服务器发送 STOR(存储)命令,然后通过数据连接将本地文件数据发送到服务器。服务器接收到数据后,将其存储在指定的目录下。

下载文件:客户端向服务器发送 RETR(检索)命令,请求下载服务器上的文件。服务器通过数据连接将文件数据发送给客户端,客户端接收数据并将其保存到本地指定位置。

目录操作:客户端还可以发送诸如 LIST(列出目录内容)、CWD(更改工作目录)、MKD(创建目录)、RMD(删除目录)等命令,服务器执行相应操作,并通过控制连接返回操作结果。执行这些命令时,若需要传输目录列表等数据,也会通过数据连接进行传输。

4. 关闭连接

数据连接关闭:在完成文件传输或其他操作后,数据连接会被关闭。如果还有其他操作需要进行,客户端和服务器可以根据需要重新建立数据连接。

控制连接关闭:当客户端完成所有操作后,会向服务器发送 QUIT 命令,服务器接收到该命令后,会关闭控制连接。至此,客户端与服务器之间的 FTP 会话结束。

5 主动模式与被动模式详解

主动模式(Active Mode):

客户端打开一个端口并监听。

客户端通过控制连接告诉服务器自己的 IP 和端口。

服务器主动连接到客户端指定的端口传输数据。

被动模式(Passive Mode):

客户端通过控制连接请求被动模式。

服务器打开一个随机端口并通过控制连接告知客户端。

客户端主动连接到服务器指定的端口传输数据。

优缺点对比:

主动模式更适合在服务器端网络无防火墙限制的环境。

被动模式更适合客户端在 NAT 或防火墙后的情况。

6 FTP报文解析

FTP 报文分为命令和响应报文,命令报文用于发送操作请求,响应报文用于返回结果。

命令报文格式为“<命令> <参数>rn”,字段解释如下:

<命令>:FTP命令(如 USER、PASS)。

<参数>:命令的附加信息(如用户名、文件名)。

例如“USER usernamern”。常见的命令包括登录 (USER, PASS)、文件操作 (RETR, STOR)、目录操作 (LIST, CWD) 等。每个 FTP 报文由命令或响应代码、状态码及附加数据组成,状态码用于指示操作结果。

以下是 FTP 常见命令:

USER: 提供用户名进行身份验证。

PASS: 提供密码进行身份验证。

CWD: 更改当前工作目录。

PWD: 显示当前工作目录。

LIST: 列出目录下的文件和子目录。

RETR: 从服务器下载文件。

STOR: 上传文件到服务器。

DELE: 删除指定文件。

MKD: 创建新目录。

RMD: 删除目录。

QUIT: 终止会话并退出。

TYPE: 设置文件传输类型(ASCII 或 Binary)。

PORT: 指定数据连接的端口。

PASV: 启用被动模式,服务器指定端口供客户端连接。

响应报文格式为“<状态码> <说明文字>rn”,字段解释如下:

<状态码>:三位数字表示状态。

<说明文字>:状态的文字描述。

例如“230 User logged in, proceed.rn”。以下是FTP常见的响应码:

1xx(信息性响应): 主要是提供一些初步的信息,通常表示服务器正在处理请求,还没有完成操作。

2xx(成功响应): 表示命令成功执行。这是客户端最希望看到的响应类型之一,说明请求的操作(如登录、文件传输等)顺利完成。

3xx(补充信息响应): 表示服务器需要一些额外的信息才能完成操作。通常是在身份验证或者文件定位等过程中出现。

4xx(暂时错误响应): 表示客户端的请求有问题,但错误是暂时的,可能通过一些调整(如重新发送请求等)可以解决。

5xx(永久性错误响应): 表示客户端的请求存在错误,并且这个错误是比较严重的,很难通过简单的调整来纠正。

接着我们来看看FTP获取目录的报文示例:

客户端建立TCP连接到服务器的21端口

服务器返回:220 Welcome to FTP Serverrn

客户端发送:USER wiznetrn

服务器返回:331 User wiznet OK.Password requiredrn

客户端发送:PASS wiznetrn

服务器返回:230 User logged inrn

客户端发送PORT 192,168,1,5,20,100rn(主动模式,192,168,1,5是客户端的地址,20,100是客户端期望的端口号20*256+100=5260)

服务器返回:200 PORT command successfulrn

客户端发送:LISTrn(DIR命令,获取当前目录的文件信息)

服务器回复:150 Opening ASCII mode data connection for file listrn

服务器像客户端期望的端口号发起TCP连接,并传输目录信息,传输完成后关闭TCP连接。

客户端发送:QUITrn(退出FTP会话)

服务器回复:221 Goodbyern

7 实现过程

接下来,我们看看如何在W55MH32上实现FTP协议的Server模式。

注意:测试实例需要PC端和W55MH32处于同一网段。

步骤一:获取网络配置信息和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 rn", strlen(ftp.username), ftp.username);
   }
   if (ftp.PW_Enable == STATUS_USED)
   {
       strcpy(ftp.userpassword, ftp_PW);
       printf(" FTP PW[%d]:%s rn", 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 服务运行做好准备。

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

ftpd_run()函数在主循环中不断被调用,作用是让 FTP 服务器持续运行,不断处理客户端的各种请求,实现服务器与客户端之间的持续交互,以提供稳定的 FTP 服务 。

 

while (1)
{
 ftpd_run(ethernet_buf);
}

 

ftpd_run()函数内容如下:

c

运行

 

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 Connectedrn", CTRL_SOCK);
#endif
                // fsprintf(CTRL_SOCK, banner, HOSTNAME, VERSION);
                strcpy(ftp.workingdir, "/");
                sprintf((char *)dbuf, "220 %s FTP version %s ready.rn", HOSTNAME, VERSION);
                ret = send(CTRL_SOCK, (uint8_t *)dbuf, strlen((const char *)dbuf));

#if defined(_FTP_DEBUG_)
                printf("%d:send() [%s]rn", CTRL_SOCK, dbuf);
#endif
                if (ret < 0)
                {
#if defined(_FTP_DEBUG_)
                    printf("%d:send() error:%ldrn", 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 Closedrn", CTRL_SOCK);
#endif
                }
#if defined(_FTP_DEBUG_)
                else if (((con_remain_cnt1 % 10000) == 0) && (con_remain_cnt1 != 0))
                {
                    // printf("%d:Timeout Count:%ldrn", CTRL_SOCK, con_remain_cnt1);
                }
#endif
                con_remain_cnt1++;
            }
#endif

#if defined(_FTP_DEBUG_)
            // printf("ftp socket %drn", CTRL_SOCK);
#endif

            if ((size = getSn_RX_RSR(CTRL_SOCK)) > 0)  // Don't need to check SOCKERR_BUSY because it doesn't not occur.
            {
#if defined(_FTP_DEBUG_)
                printf("%d:size: %drn", 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] = '';
                if (ret != size)
                {
                    if (ret == SOCK_BUSY)
                        return 0;
                    if (ret < 0)
                    {
#if defined(_FTP_DEBUG_)
                        printf("%d:recv() error:%ldrn", 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:CloseWaitrn", CTRL_SOCK);
#endif
            if ((ret = disconnect(CTRL_SOCK)) != SOCK_OK)
                return ret;
#if defined(_FTP_DEBUG_)
            printf("%d:Closedrn", CTRL_SOCK);
#endif
            break;

        case SOCK_CLOSED:
#if defined(_FTP_DEBUG_)
            printf("%d:FTPStartrn", CTRL_SOCK);
#endif
            if ((ret = socket(CTRL_SOCK, Sn_MR_TCP, IPPORT_FTP, 0x0)) != CTRL_SOCK)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:socket() error:%ldrn", CTRL_SOCK, ret);
#endif
                close(CTRL_SOCK);
                return ret;
            }
            break;

        case SOCK_INIT:
#if defined(_FTP_DEBUG_)
            printf("%d:Openedrn", CTRL_SOCK);
#endif
            strcpy(ftp.workingdir, "/");
            if ((ret = listen(CTRL_SOCK)) != SOCK_OK)
            {
#if defined(_FTP_DEBUG_)
                printf("%d:Listen errorrn", CTRL_SOCK);
#endif
                return ret;
            }
            connect_state_control = 0;
            con_remain_cnt1       = 0;

#if defined(_FTP_DEBUG_)
            printf("%d:Listen okrn", CTRL_SOCK);
#endif
            break;

        default:
            break;
    }
#endif

 

进入ftpd_run()函数后,程序会执行一个TCP Server模式的状态机(具体可参考TCP Server章节),当socket处于SOCK_ESTABLISHED 状态时,如果是首次进入SOCK_ESTABLISHED 状态,则会向客户端发送欢迎消息。后续则是监听客户端指令,当收到客户端指令后,会进入proc_ftpd()进行处理。

proc_ftpd()函数处理 FTP 服务的命令,根据不同命令及参数进行相应操作,包括用户认证、文件操作、数据传输、状态处理及错误响应。

注意:当宏定义connect_timeout_en的值设置为 1 时,会启用连接超时功能。如果在超过宏定义remain_time所设定的时长后,仍然没有进行任何操作,系统将自动断开连接。其中,connect_timeout_en是一个控制连接超时功能开启或关闭的宏,其值为 1 表示开启该功能,为 0 表示关闭;而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 != ''; 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)
   {
       // fsprintf(CTRL_SOCK, badcmd, buf);
       slen = sprintf(sendbuf, "500 Unknown command '%s'rn", 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:
           // fsprintf(CTRL_SOCK, notlog);
           slen = sprintf(sendbuf, "530 Please log in with USER and PASSrn");
           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 usernamern");
               ret  = send(sn, (uint8_t *)sendbuf, slen);
               if (ret < 0)
               {
#if defined(_FTP_DEBUG_)
                   printf("%d:send() error:%ldrn", sn, ret);
#endif
                   close(sn);
                   return ret;
               }
               break;
           }
       }
       else
       {
           strcpy(ftp.username, arg);
       }
       // fsprintf(CTRL_SOCK, givepass);
       slen = sprintf(sendbuf, "331 Enter PASS commandrn");
       ret  = send(sn, (uint8_t *)sendbuf, slen);
       if (ret < 0)
       {
#if defined(_FTP_DEBUG_)
           printf("%d:send() error:%ldrn", 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 passwordrn");
               ret  = send(sn, (uint8_t *)sendbuf, slen);
               if (ret < 0)
               {
#if defined(_FTP_DEBUG_)
                   printf("%d:send() error:%ldrn", 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': // Ascii
           ftp.type = ASCII_TYPE;
           // fsprintf(CTRL_SOCK, typeok, arg);
           slen = sprintf(sendbuf, "200 Type set to %srn", arg);
           send(sn, (uint8_t *)sendbuf, slen);
           break;
       case 'B':
       case 'b': // Binary
       case 'I':
       case 'i': // Image
           ftp.type = IMAGE_TYPE;
           // fsprintf(CTRL_SOCK, typeok, arg);
           slen = sprintf(sendbuf, "200 Type set to %srn", arg);
           send(sn, (uint8_t *)sendbuf, slen);
           break;
       default: /* Invalid */
           // fsprintf(CTRL_SOCK, badtype, arg);
           slen = sprintf(sendbuf, "501 Unknown type "%s"rn", arg);
           send(sn, (uint8_t *)sendbuf, slen);
           break;
       }
       break;
   case FEAT_CMD:
       slen = sprintf(sendbuf, "211-Features:rn MDTMrn REST STREAMrn SIZErn MLST size*;type*;create*;modify*;rn MLSDrn UTF8rn CLNTrn MFMTrn211 ENDrn");
       send(sn, (uint8_t *)sendbuf, slen);
       break;
   case QUIT_CMD:
#if defined(_FTP_DEBUG_)
       printf("QUIT_CMDrn");
#endif
       // fsprintf(CTRL_SOCK, bye);
       slen = sprintf(sendbuf, "221 Goodbye!rn");
       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_CMDrn");
#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"rn", 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_CMDrn");
#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"rn", 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 errorrn", DATA_SOCK);
#endif
               return ret;
           }
       }
       connect_state_data = 0;
       break;
   case PORT_CMD:
#if defined(_FTP_DEBUG_)
       printf("PORT_CMDrn");
#endif
       if (pport(arg) == -1)
       {
           // fsprintf(CTRL_SOCK, badport);
           slen = sprintf(sendbuf, "501 Bad port syntaxrn");
           send(sn, (uint8_t *)sendbuf, slen);
       }
       else
       {
           // fsprintf(CTRL_SOCK, portok);
           ftp.dsock_mode  = ACTIVE_MODE;
           ftp.dsock_state = DATASOCK_READY;
           slen            = sprintf(sendbuf, "200 PORT command successful.rn");
           send(sn, (uint8_t *)sendbuf, slen);
       }
       break;
   case MLSD_CMD:
#if defined(_FTP_DEBUG_)
       printf("MLSD_CMDrn");
#endif
       slen = sprintf(sendbuf, "150 Opening data channel for directory listing of "%s"rn", ftp.workingdir);
       send(sn, (uint8_t *)sendbuf, slen);
       ftp.current_cmd = MLSD_CMD;
       break;
   case LIST_CMD:
#if defined(_FTP_DEBUG_)
       printf("LIST_CMDrn");
#endif
       slen = sprintf(sendbuf, "150 Opening data channel for directory listing of "%s"rn", ftp.workingdir);
       send(sn, (uint8_t *)sendbuf, slen);
       ftp.current_cmd = LIST_CMD;
       break;
   case NLST_CMD:
#if defined(_FTP_DEBUG_)
       printf("NLST_CMDrn");
#endif
       break;
   case SYST_CMD:
       slen = sprintf(sendbuf, "215 UNIX emulated by WIZnetrn");
       send(sn, (uint8_t *)sendbuf, slen);
       break;
   case PWD_CMD:
   case XPWD_CMD:
       slen = sprintf(sendbuf, "257 "%s" is current directory.rn", 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)rn", 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: %drn", 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: %drn", 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 %drn", slen);
           else
               slen = sprintf(sendbuf, "550 File not Foundrn");
       }
       else
       {
           slen = sprintf(sendbuf, "550 File not Foundrn");
       }
       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 %drn", slen);
               strcpy(ftp.workingdir, arg);
               slen = sprintf(sendbuf, "250 CWD successful. "%s" is current directory.rn", ftp.workingdir);
           }
           else
           {
               slen = sprintf(sendbuf, "550 CWD failed. "%s"rn", arg);
           }
       }
       else
       {
           strcpy(ftp.workingdir, arg);
           slen = sprintf(sendbuf, "250 CWD successful. "%s" is current directory.rn", 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"rn", arg);
       }
       else
       {
           slen = sprintf(sendbuf, "257 MKD command successful. "%s"rn", arg);
           // strcpy(ftp.workingdir, arg);
       }
#else
       slen = sprintf(sendbuf, "550 Can't create directory. Permission deniedrn");
#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"rn", arg);
       }
       else
       {
           slen = sprintf(sendbuf, "250 Deleted. "%s"rn", arg);
       }
#else
       slen = sprintf(sendbuf, "550 Could not delete. Permission deniedrn");
#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:
       // fsprintf(CTRL_SOCK, unimp);
       slen = sprintf(sendbuf, "502 Command does not implemented yet.rn");
       send(sn, (uint8_t *)sendbuf, slen);
       break;
   default: // Invalid
       // fsprintf(CTRL_SOCK, badcmd, arg);
       slen = sprintf(sendbuf, "500 Unknown command '%s'rn", arg);
       send(sn, (uint8_t *)sendbuf, slen);
       break;
   }
   return 1;
}

 

进入 proc_ftpd()函数后,程序会执行一个状态机,首先将接收的命令转换为小写并在命令表中查找,未找到时发送错误信息。登录前仅允许 USER、PASS、QUIT 命令,其余报错。对于不同命令,如 USER_CMD 处理用户名验证和后续操作,PASS_CMD 进行密码验证和登录,TYPE_CMD 处理传输类型设置,FEAT_CMD 发送特性信息,QUIT_CMD 断开连接,还有 RETR_CMD 等文件操作命令,以及 PORT_CMD、PASV_CMD 等数据连接模式相关命令,根据不同情况执行相应的操作和错误处理,同时发送相应的状态信息。

8 运行结果

烧录例程运行后,首先进行了PHY链路检测,然后打印设置网络信息。打开filezilla软件(下载链接:客户端 - FileZilla中文网),在filezilla软件上填写主机ID,用户名,密码,端口号(通常是21)连接FTP服务器。连接成功显示如下界面:

数据传输

然后拉住本地站点文件向远程站点(服务器)内拖动,成功向服务器传输文件。

数据传输

9 总结

本文讲解了如何在 W55MH32 芯片上实现 FTP 协议的服务器模式,通过实战例程展示了使用 W55MH32 作为 FTP 服务器与 PC 端进行文件传输、目录操作等功能的过程,涵盖获取网络配置信息和 FTP 初始化、实现服务器和客户端之间的持续交互等关键步骤。文章详细介绍了 FTP 协议的概念、特点、应用场景、基本工作流程、主动与被动模式、报文解析,帮助读者理解其在文件传输中的实际应用价值。

下一篇文章将聚焦 FTP 协议客户端模式,解析其核心原理及在文件传输中的应用,同时讲解如何在W55MH32上实现 FTP 客户端功能,敬请期待!

WIZnet 是一家无晶圆厂半导体公司,成立于 1998 年。产品包括互联网处理器 iMCU™,它采用 TOE(TCP/IP 卸载引擎)技术,基于独特的专利全硬连线 TCP/IP。iMCU™ 面向各种应用中的嵌入式互联网设备。

WIZnet 在全球拥有 70 多家分销商,在香港、韩国、美国设有办事处,提供技术支持和产品营销。

香港办事处管理的区域包括:澳大利亚、印度、土耳其、亚洲(韩国和日本除外)。

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分