uboot移植LWIP实现FTP下载文件

admin2024-09-05  7

第1部分 调试环境说明

1.1 硬件环境

(1)嵌入式开发板使用米尔科技的MYD-YA157C,其核心芯片为STM32MP157,芯片为双核Cortex-A7+单核Cortex-M4。

(2)路由器选择TP-LINK,网关IP为192.168.10.1。

(3)PC端选用惠普光影精灵6。

1.2 软件环境

(1)uboot源码使用米尔科技官方提供的MYiR-STM32-u-boot源码包,版本为2020.01。

(2)lwip源码使用lwip官方提供的lwip1.4.1版本源码包。

(3)FTP服务器使用PC端虚拟机Ubuntu18.04系统,版本为vsFTPd3.0.3。

(4)交叉编译环境选择GNU的arm-buildroot-linux-gnueabihf交叉编译工具链,gcc版本为8.4.0。

(5)程序开发环境使用虚拟机Ubuntu18.04系统,在VS Code下进行开发与编译。

(6)程序烧写工具选用ST官方STM32Cube Programmer2.6.0进行开发板uboot程序烧录。

(7)调试工具选用Wireshark进行网络数据抓包,选用MobaXterm进行开发板控制。

第2部分 前期知识储备

2.1 lwip框架解读

1.概述

        lwip是瑞典计算机科学院的Adam Dunkels开发的一个小型开源TCP/IP协议栈,其实现重点是在保持TCP协议主要功能的基础上减少对RAM的占用,因此适用于嵌入式开发板这类存储资源较小的设备。

        lwip提供了三种API:

(1)RAW API。它将协议栈与应用程序放在一个进程里,该接口基于函数回调技术,适合在无操作系统环境,对比于其他两种API,可提高应用程序的效率、节省内存开销。但代码可读性差,容易出现丢包现象。

(2)NETCONN API。它基于操作系统的 IPC 机制(即信号量和邮箱机制) 实现, 它的设计将 LwIP 内核代码和网络应用程序分离成了独立的线程。它相较于RAW API,简化了编程工作,使用户用户可以像操作文件一样操作网络连接(打开/关闭、读/写数据),避免了内核程序与应用程序之间的数据拷贝,提高了数据递交的效率。但它需要耗费更多的时间和内存,且需要用户对lwip使用的数据结构有所了解。

(3)SOCKET API。即套接字编程,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接。lwip中的SOCKET API采用BSD SOCKET。相较于 NETCONN API, SOCKET API 具有更好的易用性。使用 Socket API 编写的程序可读性好,便于维护,也便于移植到其它的系统中。 SOCKET API在内核程序和应用程序之间存在数据的拷贝,这会降低数据递交的效率。另外,SOCKET API 是基于NETCONN API 实现的,所以效率要比NETCONN API更低。

2.lwip1.4.1文件夹文件组织

        lwip1.4.1版本是最广为应用的版本,其文件夹文件组织如图1所示。

uboot移植LWIP实现FTP下载文件,第1张

图1 lwip1.4.1文件夹文件组织

(1)doc:该文件夹是供给用户使用的移植说明文档。

(2)src:该文件夹包含了LwIP的源码。

(3)test:该文件夹是官方提供的测试程序。

(4)CHANGELOG:该文档记录了版本的新特性和改动。

(5)COPYING:该文档是作者的版权说明文档。

(6)FILES:该文档讲述了doc、src文件夹的用途。

(7)UPGRADING:该文档记录了版本间的改动,为旧版本的升级提供说明。

3.doc目录下的文件

        doc目录下的文件结构如图2所示,每个文件的作用在FILES文件中已给出具体说明。

uboot移植LWIP实现FTP下载文件,第2张

图2 doc目录下的文件结构

       文件FILES内容如图3所示。

uboot移植LWIP实现FTP下载文件,第3张

图3 doc目录下文件FILES内容

       (1)savannah.txt,说明如何获得当前lwip开发版本的源码。

        (2)contrib.txt,说明如何作为开发者为lwip贡献。

        (3)rawapi.txt,说明lwip RAW/Callback API编程中的核心API,同时也提供其他API编程方式和多线程的概述。

        (4)snmp_agent.txt,说明lwip SNMP代理方法。

        (5)sys_arch.txt,说明lwip的系统抽象层接口。

4.src目录下的文件

src目录是整个lwip的核心部分,其目录结构如图4所示。

uboot移植LWIP实现FTP下载文件,第4张

图4 src文件夹目录结构

       通过查看FILES文档,可知各文件夹包含内容如图5所示。

uboot移植LWIP实现FTP下载文件,第5张

图5 src目录下文件FILES内容

(1)api,包含高级API源码,即NETCONN API和SOCKET API,若用户使用RAW/Callback API编程,则无需该文件夹。

(2)core,包含TCP/IP协议栈的核心源码,协议应用、内存管理以及RAW/Callback API编程。

(3)include,包含lwip头文件。

(4)netif,包含通用网络接口文件以及ARP相关文件。

2.2 FTP协议工作原理

1.FTP概述

        FTP工作于OSI七层模型中的第七层,即应用层,它使用的传输层协议是TCP,因此需要经历连接时三次握手和关闭时四次握手的过程。

        FTP协议需要两个端口。

(1)端口号21作为控制连接端口,用于发送指令给服务器以及等待服务器相应。

(2)端口号20作为数据传输端口(仅PORT模式),用于建立数据传输通道,主要有三个作用:

  1. 从客户端向服务端发送文件
  2. 从服务端向客户端发送文件
  3. 从服务端向客户端发送文件或目录列表
2.FTP工作模式

        FTP的工作模式有两种,PORT模式为主动模式,PASV模式为被动模式,这里是相对于服务器而言的。

1)PORT模式

当FTP以PORT模式连接服务器时,会动态的选择一个端口号连接服务器的21端口,注意这个端口号一定是1024以上。发送用户名和密码,经过TCP三次握手后,连接(控制信道)被建立。现在用户要列出服务器上的目录结构,那么首先要建立一个数据通道,因为只有数据通道才能传输目录和文件列表。此时用户会发出PORT指令告诉服务器连接自己的什么端口来建立一条数据通道(这个指令通过控制信道发送给服务器),当服务器接收到指令时,服务器会使用20端口连接用户在PORT指令中指定的端口号,用以发送目录列表。当完成这些操作后,FTP客户端也许要下载一个文件,那么就会发送get指令,这时客户端会再次发送PORT指令,告诉服务器连接它的哪个“新”端口,当这个新的数传输通道建立后,就开始了文件传输工作。

2)PASV模式

        当FTP以PASV模式连接服务器时,会动态的选择一个端口号连接服务器的21端口,注意这个端口号一定是1024以上。发送用户名和密码,经过TCP三次握手后,连接(控制信道)被建立。不同的是,FTP客户端发送lsdirget等这些要求数据返回的命令时,会向服务端发送PASV指令,在这个指令中,用户告诉服务器自己要连接的某一个端口,如果这个服务器上的这个端口是空闲可用的,那服务器会返回ACK的确认信息,之后数据传输通道被建立,并返回用户所要的信息;如果这个服务器上的这个端口被占用,服务器会返回UNACK的信息,那这时,FTP客户端会再次发送PASV指令,这就是所谓的连接建议的协商过程。

        特别注意:在FTP客户端连接服务端过程中,控制信道是一直保持连接的,而数据传输通道是临时建立的。

3.FTP PASV模式具体通信过程

        本文选用FTP的PASV模式进行数据通信,在这种工作模式下,对客户端通过FTP远程登录至服务端和客户端通过FTP远程下载服务端上的文件两个过程进行详细分析。过程中的具体值均为一次通信测试中通过wireshark进行捕获的值。

1)客户端通过FTP远程登录至服务端

        首先通过ARP协议查找服务端IP地址对应的MAC地址。

(1)若客户端在本地ARP缓存表中找不到服务端IP地址对应的MAC地址时,发送ARP请求至服务端。

(2)服务端收到ARP请求后,将自身的MAC地址00:0c:29:02:b2:4a回发至客户端。

(3)客户端接收到MAC地址后将其写入本地ARP缓存表。

(4) 客户端发送TCP连接请求,本地端口号为49153,服务端端口号为21。将带SYN的请求帧发送至服务端,Seq=0、Win=5840、Len=0、MSS=1460。

(5)服务端接收到TCP连接请求后,回发带SYN和ACK的收到帧,Seq=0、Ack=1、Win=64240、Len=0、MSS=1460。

(6)客户端收到服务端的收到帧后,回发确认帧,Seq=1、Ack=1、Win=5840、Len=0。

至此TCP三次握手完成,控制通道建立。

(7)服务端回发响应帧,数据为220 (vsFTPd 3.0.3),其中括号内为FTP服务器版本号。

(8)客户端发送用户名,数据为USER cansun。

(9)服务端确认用户名,回发响应帧,数据为331 Please specify the password。

(10)客户端发送密码,数据为PASS 123456。

(11)服务端确认密码,回发响应帧,数据为230 Login successful.。

至此客户端成功登录至FTP服务器。

2)客户端通过FTP远程下载服务端上的文件

        在已登录的前提下,从FTP服务器下载/home/cansun/test/test2.txt文件,存储至本地地址0xc2000000处。

(1)客户端通过已建立的控制信道向服务端发送命令,数据为TYPE I,表示文件为二进制类型。

(2)服务端回发响应帧,数据为200 Switching to Binary mode.。

(3)客户端发送控制命令,设置FTP数据传输为PASV(被动)模式,数据为PASV。

(4)服务端回发响应帧,数据为227 Entering Passive mode (192,168,10,130,21,132).,其中括号内表明了服务端IP地址,以及商议后使用的数据通道端口号5508。

(5)客户端发送TCP连接请求,本地端口号为49154,服务端端口号为5508,Seq=0、Win=5840、Len=0、MSS=1460。

(6)服务端接收到TCP连接请求后,回发带SYN和ACK的收到帧,Seq=0、Ack=1、Win=64240、Len=0、MSS=1460。

(7)客户端收到服务端的收到帧后,回发确认帧,Seq=1、Ack=1、Win=5840、Len=0。

至此TCP三次握手完成,数据通道建立。

(8)客户端通过控制通道发送命令,数据为RETR /home/cansun/test/test2.txt。

(9)服务端回发响应帧,数据为150 Opening BINARY mode data connection for /home/cansun/test/test2.txt (9 bytes).。

(10)服务端通过数据通道发送/home/cansun/test/test2.txt文件数据至本地客户端。

(11)服务端发送FIN和ACK请求断开本次数据连接。

(12)客户端发送ACK答应服务器的请求。

(13)服务端回发响应帧,数据为226 Transfer complete.。

(14)客户端向服务端发送含有ACK的数据段来确认收到信息。

(15)客户端向服务端发送含有FIN和ACK的数据段请求断开数据连接。

(16)服务端向客户端发送含有ACK的数据段确认断开连接。

        至此一次下载文件过程完成。

第3部分 uboot下lwip移植适配过程

3.1 文件拷贝

(1)对于lwip中的头文件:将lwip1.4.1源码中的src/include文件夹下的ipv4、lwip、netif、posix文件夹拷贝到uboot源码目录的include文件夹下。

(2)对于lwip中的源文件:在uboot源码目录的lib文件夹下新建lwip文件夹,将lwip1.4.1源码中src文件夹下的core和netif拷贝至uboot源码目录lib/lwip下。

(3)由于本文使用ipv4协议,不使用点对点通信,因此删除uboot源码目录下的lib/lwip/core/ipv6文件夹与lib/lwip/netif/ppp文件夹。

3.2 文件新增

(1)将lwip源码加入编译环境。在lib/lwip,lib/lwip/core,lib/lwip/core/ipv4,lib/lwip/core/snmp,lib/lwip/netif这几个文件夹下新建Makefile,加入uboot的编译架构下。

(2)在uboot源码目录的include文件夹下新建文件夹arch,在里面新增cc.h、perf.h、lwipopts.h、sys_arch.h文件。各文件功能说明如下表

文件名

作用

cc.h

平台相关。类型定义,大小端设置,内存对齐等

lwipopts.h

lwip配置文件,非常重要

perf.h

平台相关的性能测量实现,留空

sys_arch.h

当使用lwip的带操作系统的API时,存放头文件,由于uboot为裸机程序,无操作系统,因此该文件留空

其中cc.h文件内容如下:

#ifndef __ARCH_CC_H__

#define __ARCH_CC_H__

// Includes definition of macro to do printf

extern int printf(const char *fmt, ...);

#define BYTE_ORDER LITTLE_ENDIAN

#define LWIP_PLATFORM_BYTESWAP 0

typedef unsigned char u8_t;

typedef char s8_t;

typedef unsigned short u16_t;

typedef short s16_t;

typedef unsigned int u32_t;

typedef int s32_t;

typedef u32_t mem_ptr_t;

#define LWIP_ERR_T int

/* Define (sn)printf formatters for these lwIP types */

#define U16_F "u"

#define S16_F "d"

#define X16_F "x"

#define U32_F "u"

#define S32_F "d"

#define X32_F "x"

/* Compiler hints for packing structures */

#define PACK_STRUCT_FIELD(x) x

#define PACK_STRUCT_STRUCT __attribute__((packed))

#define PACK_STRUCT_BEGIN

#define PACK_STRUCT_END

/* Plaform specific diagnostic output */

#define LWIP_PLATFORM_DIAG(x, ...) do { \

printf(x, ##__VA_ARGS__); \

} while (0)

#define LWIP_PLATFORM_ASSERT(x) do { \

printf("Assert \"%s\" failed at line %d in %s\n", \

x, __LINE__, __FILE__); \

} while (0)

#endif /* __ARCH_CC_H__ */

lwipopts.h文件内容如下:

#ifndef __LWIPOPTS_H__

#define __LWIPOPTS_H__

#define NO_SYS 1

#define MEM_LIBC_MALLOC 1

#define MEMP_MEM_MALLOC 0

#define MEMP_SEPARATE_POOLS 0

#define MEM_ALIGNMENT 4

#define MEM_SIZE (4 * 1024 * 1024)

#define MEMP_NUM_PBUF 1024

#define MEMP_NUM_UDP_PCB 20

#define MEMP_NUM_TCP_PCB 20

#define MEMP_NUM_TCP_PCB_LISTEN 16

#define MEMP_NUM_TCP_SEG 128

#define MEMP_NUM_REASSDATA 32

#define MEMP_NUM_ARP_QUEUE 10

#define PBUF_POOL_SIZE 512

#define LWIP_ARP 1

#define LWIP_TCP 1

#define LWIP_UDP 1

#define LWIP_DHCP 1

#define IP_REASS_MAX_PBUFS 64

#define IP_FRAG_USES_STATIC_BUF 0

#define IP_DEFAULT_TTL 255

#define IP_SOF_BROADCAST 1

#define IP_SOF_BROADCAST_RECV 1

#define LWIP_ICMP 1

#define LWIP_BROADCAST_PING 1

#define LWIP_MULTICAST_PING 1

#define LWIP_RAW 1

#define TCP_WND (4 * TCP_MSS)

#define TCP_MSS 1460

#define TCP_SND_BUF (8 * TCP_MSS)

#define TCP_LISTEN_BACKLOG 1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!