(1)嵌入式开发板使用米尔科技的MYD-YA157C,其核心芯片为STM32MP157,芯片为双核Cortex-A7+单核Cortex-M4。
(2)路由器选择TP-LINK,网关IP为192.168.10.1。
(3)PC端选用惠普光影精灵6。
(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进行开发板控制。
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更低。
lwip1.4.1版本是最广为应用的版本,其文件夹文件组织如图1所示。
图1 lwip1.4.1文件夹文件组织 |
(1)doc:该文件夹是供给用户使用的移植说明文档。
(2)src:该文件夹包含了LwIP的源码。
(3)test:该文件夹是官方提供的测试程序。
(4)CHANGELOG:该文档记录了版本的新特性和改动。
(5)COPYING:该文档是作者的版权说明文档。
(6)FILES:该文档讲述了doc、src文件夹的用途。
(7)UPGRADING:该文档记录了版本间的改动,为旧版本的升级提供说明。
doc目录下的文件结构如图2所示,每个文件的作用在FILES文件中已给出具体说明。
图2 doc目录下的文件结构 |
文件FILES内容如图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的系统抽象层接口。
src目录是整个lwip的核心部分,其目录结构如图4所示。
图4 src文件夹目录结构 |
通过查看FILES文档,可知各文件夹包含内容如图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相关文件。
FTP工作于OSI七层模型中的第七层,即应用层,它使用的传输层协议是TCP,因此需要经历连接时三次握手和关闭时四次握手的过程。
FTP协议需要两个端口。
(1)端口号21作为控制连接端口,用于发送指令给服务器以及等待服务器相应。
(2)端口号20作为数据传输端口(仅PORT模式),用于建立数据传输通道,主要有三个作用:
FTP的工作模式有两种,PORT模式为主动模式,PASV模式为被动模式,这里是相对于服务器而言的。
当FTP以PORT模式连接服务器时,会动态的选择一个端口号连接服务器的21端口,注意这个端口号一定是1024以上。发送用户名和密码,经过TCP三次握手后,连接(控制信道)被建立。现在用户要列出服务器上的目录结构,那么首先要建立一个数据通道,因为只有数据通道才能传输目录和文件列表。此时用户会发出PORT指令告诉服务器连接自己的什么端口来建立一条数据通道(这个指令通过控制信道发送给服务器),当服务器接收到指令时,服务器会使用20端口连接用户在PORT指令中指定的端口号,用以发送目录列表。当完成这些操作后,FTP客户端也许要下载一个文件,那么就会发送get指令,这时客户端会再次发送PORT指令,告诉服务器连接它的哪个“新”端口,当这个新的数传输通道建立后,就开始了文件传输工作。
当FTP以PASV模式连接服务器时,会动态的选择一个端口号连接服务器的21端口,注意这个端口号一定是1024以上。发送用户名和密码,经过TCP三次握手后,连接(控制信道)被建立。不同的是,当FTP客户端发送ls,dir,get等这些要求数据返回的命令时,会向服务端发送PASV指令,在这个指令中,用户告诉服务器自己要连接的某一个端口,如果这个服务器上的这个端口是空闲可用的,那服务器会返回ACK的确认信息,之后数据传输通道被建立,并返回用户所要的信息;如果这个服务器上的这个端口被占用,服务器会返回UNACK的信息,那这时,FTP客户端会再次发送PASV指令,这就是所谓的连接建议的协商过程。
特别注意:在FTP客户端连接服务端过程中,控制信道是一直保持连接的,而数据传输通道是临时建立的。
本文选用FTP的PASV模式进行数据通信,在这种工作模式下,对客户端通过FTP远程登录至服务端和客户端通过FTP远程下载服务端上的文件两个过程进行详细分析。过程中的具体值均为一次通信测试中通过wireshark进行捕获的值。
首先通过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服务器。
在已登录的前提下,从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的数据段确认断开连接。
至此一次下载文件过程完成。
(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文件夹。
(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 |