第十八章 W55MH32 FTP_Server示例

目录

1 FTP协议简介

2 FTP协议特点

3 FTP协议应用场景

4 FTP协议基本工作流程

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

6 FTP报文解析

7 实现过程

8 运行结果

9 总结


本篇文章,我们将详细介绍如何在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协议特点

  1. 基于 TCP 传输:FTP 使用两个 TCP 连接:控制连接(端口 21)和数据连接(端口 20 或 PASV 模式动态分配的端口),确保可靠的数据传输。
  2. 分离控制与数据:
  1. 控制连接用于发送命令和接收响应。
  2. 数据连接用于文件内容或目录信息的传输。
  1. 支持多种传输模式:
  1. 主动模式(Active Mode):服务器主动连接客户端的数据端口。
  2. 被动模式(Passive Mode):客户端主动连接服务器提供的数据端口,解决 NAT 防火墙限制。
  1. 支持多种文件操作:
  1. 文件上传(STOR)、下载(RETR)、删除(DELE)。
  2. 目录操作(MKD、RMD、CWD、PWD)。
  3. 获取文件列表(LIST、NLST)。
  1. 明文传输(传统 FTP):用户名、密码及数据以明文形式传输,不安全。安全改进:FTPS(FTP Secure,基于 SSL/TLS)和 SFTP(Secure File Transfer Protocol,基于 SSH)。
  2. 灵活的用户认证机制:
  1. 支持匿名登录(匿名用户可通过 email 作为密码)。
  2. 支持认证用户名和密码。

3 FTP协议应用场景

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

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

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

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

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

4 FTP协议基本工作流程

1. 建立控制连接

  1. 客户端初始化:客户端启动 FTP 客户端程序,指定要连接的 FTP 服务器的地址和端口号(端口号为 21)。
  2. TCP 连接建立:客户端通过 TCP 协议向服务器的 21 端口发起连接请求。服务器监听该端口,接收到请求后,与客户端建立起一条 TCP 连接,这个连接被称为控制连接,主要用于传输 FTP 命令和服务器的响应信息。
  3. 身份验证:连接建立后,服务器会提示客户端输入用户名和密码进行身份验证。客户端发送相应的用户名和密码信息到服务器,服务器验证通过后,才允许客户端进行后续操作。也有一些匿名 FTP 服务器,允许用户以 “anonymous” 作为用户名,以电子邮件地址作为密码进行登录,提供公开的文件访问服务 。

2. 传输模式选择

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

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

3. 数据传输

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

  1. 上传文件:客户端向服务器发送 STOR(存储)命令,然后通过数据连接将本地文件数据发送到服务器。服务器接收到数据后,将其存储在指定的目录下。
  2. 下载文件:客户端向服务器发送 RETR(检索)命令,请求下载服务器上的文件。服务器通过数据连接将文件数据发送给客户端,客户端接收数据并将其保存到本地指定位置。
  3. 目录操作:客户端还可以发送诸如 LIST(列出目录内容)、CWD(更改工作目录)、MKD(创建目录)、RMD(删除目录)等命令,服务器执行相应操作,并通过控制连接返回操作结果。执行这些命令时,若需要传输目录列表等数据,也会通过数据连接进行传输。

4. 关闭连接

  1. 数据连接关闭:在完成文件传输或其他操作后,数据连接会被关闭。如果还有其他操作需要进行,客户端和服务器可以根据需要重新建立数据连接。
  2. 控制连接关闭:当客户端完成所有操作后,会向服务器发送 QUIT 命令,服务器接收到该命令后,会关闭控制连接。至此,客户端与服务器之间的 FTP 会话结束。

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

主动模式(Active Mode):

  1. 客户端打开一个端口并监听。
  2. 客户端通过控制连接告诉服务器自己的 IP 和端口。
  3. 服务器主动连接到客户端指定的端口传输数据。

被动模式(Passive Mode):

  1. 客户端通过控制连接请求被动模式。
  2. 服务器打开一个随机端口并通过控制连接告知客户端。
  3. 客户端主动连接到服务器指定的端口传输数据。

优缺点对比:

  1. 主动模式更适合在服务器端网络无防火墙限制的环境。
  2. 被动模式更适合客户端在 NAT 或防火墙后的情况。

6 FTP报文解析

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

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

  1. <命令>:FTP命令(如 USER、PASS)。
  2. <参数>:命令的附加信息(如用户名、文件名)。

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

以下是 FTP 常见命令:

  1. USER: 提供用户名进行身份验证。
  2. PASS: 提供密码进行身份验证。
  3. CWD: 更改当前工作目录。
  4. PWD: 显示当前工作目录。
  5. LIST: 列出目录下的文件和子目录。
  6. RETR: 从服务器下载文件。
  7. STOR: 上传文件到服务器。
  8. DELE: 删除指定文件。
  9. MKD: 创建新目录。
  10. RMD: 删除目录。
  11. QUIT: 终止会话并退出。
  12. TYPE: 设置文件传输类型(ASCII 或 Binary)。
  13. PORT: 指定数据连接的端口。
  14. PASV: 启用被动模式,服务器指定端口供客户端连接。

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

  1. <状态码>:三位数字表示状态。
  2. <说明文字>:状态的文字描述。

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

  1. 1xx(信息性响应): 主要是提供一些初步的信息,通常表示服务器正在处理请求,还没有完成操作。
  2. 2xx(成功响应): 表示命令成功执行。这是客户端最希望看到的响应类型之一,说明请求的操作(如登录、文件传输等)顺利完成。
  3. 3xx(补充信息响应): 表示服务器需要一些额外的信息才能完成操作。通常是在身份验证或者文件定位等过程中出现。
  4. 4xx(暂时错误响应): 表示客户端的请求有问题,但错误是暂时的,可能通过一些调整(如重新发送请求等)可以解决。
  5. 5xx(永久性错误响应): 表示客户端的请求存在错误,并且这个错误是比较严重的,很难通过简单的调整来纠正。

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

  1. 客户端建立TCP连接到服务器的21端口
  2. 服务器返回:220 Welcome to FTP Server\r\n
  3. 客户端发送:USER wiznet\r\n
  4. 服务器返回:331 User wiznet OK.Password required\r\n
  5. 客户端发送:PASS wiznet\r\n
  6. 服务器返回:230 User logged in\r\n
  7. 客户端发送PORT 192,168,1,5,20,100\r\n(主动模式,192,168,1,5是客户端的地址,20,100是客户端期望的端口号20*256+100=5260)
  8. 服务器返回:200 PORT command successful\r\n
  9. 客户端发送:LIST\r\n(DIR命令,获取当前目录的文件信息)
  10. 服务器回复:150 Opening ASCII mode data connection for file list\r\n
  11. 服务器像客户端期望的端口号发起TCP连接,并传输目录信息,传输完成后关闭TCP连接。
  12. 客户端发送:QUIT\r\n(退出FTP会话)
  13. 服务器回复:221 Goodbye\r\n

7 实现过程

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

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

步骤一:获取网络配置信息和FTP初始化

    wizchip_getnetinfo(&net_info);
    ftpd_init(net_info.ip);

ftpd_init()函数内容如下:

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 服务运行做好准备。

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

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

  while (1)
    {
        ftpd_run(ethernet_buf);
    }

ftpd_run()函数内容如下:

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
            // fsprintf(CTRL_SOCK, banner, HOSTNAME, VERSION);
            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 defined(_FTP_DEBUG_)
        // printf("ftp socket %d\r\n", 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: %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;
    }

进入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()函数如下:

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)
    {
        // fsprintf(CTRL_SOCK, badcmd, buf);
        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:
            // fsprintf(CTRL_SOCK, notlog);
            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);
        }
        // fsprintf(CTRL_SOCK, givepass);
        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': // Ascii
            ftp.type = ASCII_TYPE;
            // fsprintf(CTRL_SOCK, typeok, arg);
            slen = sprintf(sendbuf, "200 Type set to %s\r\n", 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 %s\r\n", arg);
            send(sn, (uint8_t *)sendbuf, slen);
            break;

        default: /* Invalid */
            // fsprintf(CTRL_SOCK, badtype, arg);
            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
        // fsprintf(CTRL_SOCK, bye);
        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)
        {
            // fsprintf(CTRL_SOCK, badport);
            slen = sprintf(sendbuf, "501 Bad port syntax\r\n");
            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.\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);
            // strcpy(ftp.workingdir, 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:
        // fsprintf(CTRL_SOCK, unimp);
        slen = sprintf(sendbuf, "502 Command does not implemented yet.\r\n");
        send(sn, (uint8_t *)sendbuf, slen);
        break;

    default: // Invalid
        // fsprintf(CTRL_SOCK, badcmd, arg);
        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 处理传输类型设置,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 客户端功能,敬请期待!

### STM32W55BCGU6低功耗特性与配置 #### 一、STM32W55BCGU6概述 STM32系列微控制器以其高性能和丰富的外设而闻名,其中STM32W55BCGU6型号继承了该家族的优势并特别注重低功耗应用的设计。此款MCU不仅具备强大的处理能力,而且通过多种机制实现了高效的能量管理,在休眠模式下仍能维持必要的外围设备运作。 #### 二、低功耗特性 为了满足不同应用场景的需求,STM32W55BCGU6提供了多样化的省电选项: - **待机模式(Standby Mode)**:当系统处于长时间无活动状态时可启用这种最深程度的节能方式;此时除了RTC之外几乎所有的电路都会被关闭以减少静态电流消耗。 - **停止模式(STOP Mode)**:允许CPU暂停工作但保留SRAM中的数据以及一些基本定时器的功能,以便快速唤醒恢复执行任务。在此期间可以利用外部中断或事件来触发重新激活过程[^4]。 - **睡眠模式(Sleep Mode)**:这是相对较浅的一种节省电量的方法,核心频率降低至最低限度但仍保持足够的响应速度用于即时处理突发情况下的请求。 #### 三、具体配置方法 针对上述提到的各种低功率运行状况,可以通过调整软件设置来进行优化控制: 1. 对于进入STOP模式的情况,开发者可以根据实际需求选择合适的入口函数(`PWR_STOPEntry_WFI` 或 `PWR_STOPEntry_WFE`) 来决定等待条件是特定类型的中断还是任意发生的事件。 ```c if(PWR_STOPEntry == PWR_STOPEntry_WFI) { /* Request Wait For Interrupt */ __WFI(); } else { /* Request Wait For Event */ __WFE(); } ``` 2. 当涉及到更深层次如Standby模式,则需调用相应API接口完成电源管理和实时时钟(RTC)初始化等工作,并确保在此之前保存好所有重要变量的状态信息以防丢失。 3. 此外还有其他参数可供调节比如电压规模(VOS),主PLL分频系数等都对最终达到的效果有着直接影响,因此建议参照官方文档详细了解各项指令的具体含义及其作用范围后再做设定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值