2023_open_source_contest_preliminary_1st_issue3

This commit is contained in:
JasenChao 2023-09-01 16:03:08 +08:00
parent a1df906d16
commit 62d51144e8
19 changed files with 823 additions and 3 deletions

View File

@ -134,7 +134,7 @@ ifeq ($(CONFIG_ADD_XIZI_FEATURES),y)
endif
ifeq ($(CONFIG_USER_TEST_FTPCLIENT),y)
SRC_FILES +=
SRC_FILES += test_ftpclient/test_ftpclient.c test_ftpclient/ftp_client/ftp_client.c
endif
ifeq ($(CONFIG_USER_TEST_LORA_P2P),y)

View File

@ -0,0 +1,80 @@
# # 基于k210-emulator实现基数树并测试验证
## 1. 简介
基于矽璓已实现的Lwip在ARM上实现FTP协议的Client功能并编写测试程序在shell终端打印结果。
## 2. 数据结构设计说明
FTP client设计为
```c
typedef struct ftp_client_struct
{
char username[64];
char password[64];
char server_ip[64];
char data_port[6];
char control_port[6];
int control_socket;
int data_socket;
}ftp_client;
```
一共实现了 8 个函数,其中 4 个为接口函数,分别为:
- `ftp_cmd`:向 FTP server 发送指令
- `ftp_downloaddata`:从 FTP server 接收数据
- `ftp_uploaddata`:向 FTP server 上传数据
- `ftp_login`:登陆 FTP server
- `ftp_downloadfile`:从 FTP server 下载文件
- `ftp_uploadfile`:向 FTP server 上传文件
- `ftp_changedir`:切换目录
- `ftp_quit`:关闭 FTP client
## 3. 测试程序说明
测试程序 `TestFtpClient` 已经注册为 shell 命令,可以调用执行。编译时需要打开 lwip。
1. 首先确保个人电脑已安装 FTP server通过 `bash` 程序在 server 上的 `test` 目录下生成了 10 个 4KB 大小的测试文件,其中前 3 个文件由随机生成的 ASCII 乱码填充后7个文件由数字填充。
![fig0](fig0.png)
2. 将开发板与个人电脑通过以太网连接,设置个人电脑的以太网网卡 IP 为 `192.168.130.78`,子网掩码为 `255.255.254.0`,网关为 `192.168.130.1`。测试程序开始时会将开发板网卡 IP 初始化为 `192.168.130.77`,开发板和个人电脑处于同一网段下,可以相互通信。
3. 登陆到 FTP server如果 server 匿名访问受限,则此时需要将测试程序中 `ftp_login("192.168.130.78", "anonymous", "anonymous")` 的用户名和密码替换为对应的内容。
4. 切换目录到 `test`,需要根据实际情况修改。
5. 根据 10 个文件的文件名进行文件下载,文件内容通过终端打印输出。
6. 关闭 FTP client。
## 4. 运行结果(需结合运行测试截图按步骤说明)
首先确保个人电脑已安装 FTP server并已经和开发板正确配置和连接。
1. 在工作区终端中进入 `menuconfig` 配置页面,打开 `Using LwIP by ethernet device` 以及 `test app` 中的 `Config test ftp client`
2. 执行编译命令:`make BOARD=edu-arm32`,正常情况下应当编译无误,将编译好的 bin 文件烧录进开发板。
3. 启动开发板,通过串口终端观察输出,执行 `TestFtpClient` 命令。
4. 输出显示网卡初始化并连接到 server 成功,用户名密码校验通过,并切换至目标目录。
![fig1](fig1.png)
5. 开始下载文件,观察输出的内容,通过比对,与 server 上的文件内容一致,接收到的数据长度也和 server 返回的数据长度相等。
![fig2](fig2.png)
![fig3](fig3.png)
![fig4](fig4.png)
![fig5](fig5.png)
![fig6](fig6.png)
![fig7](fig7.png)
![fig8](fig8.png)
![fig9](fig9.png)
![fig10](fig10.png)
![fig11](fig11.png)
6. 关闭 FTP client测试结束。

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -0,0 +1,673 @@
/**
* @file: ftp_client.c
* @brief: Implement FTP client
* @version: 1.0
* @date: 2023/8/30
*/
#include <transform.h>
#include "ftp_client.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
/**
* @description: Send a cmd to FTP server
* @param socket - Control socket
* @param wait_time - Select timeout period
* @param cmd - Cmd code
* @param param - Store input parameter if needed
* @param other - Store output parameter if needed
* @return 0: success; others: failure
*/
static int ftp_cmd(int socket, int wait_time, int cmd, char *param, int *other)
{
fd_set readset;
struct timeval timeout;
char buff[128] = {0};
int len = 0;
int code= 0;
char cmdbuf[128] = {0};
int a,b,c,d,e,f = 0;
timeout.tv_sec = wait_time/1000;
timeout.tv_usec = 0;
FD_ZERO(&readset);
FD_SET(socket, &readset);
/* Determine if a command needs to be sent and what to send based on cmd */
switch (cmd)
{
case CWD:
sprintf(cmdbuf, "CWD %s\r\n", param);
len = send(socket, cmdbuf, strlen(cmdbuf), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case PASV:
sprintf(cmdbuf, "PASV\r\n");
len = send(socket, cmdbuf, strlen(cmdbuf), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case SIZE:
sprintf(cmdbuf, "SIZE %s\r\n", param);
len = send(socket, cmdbuf, strlen(cmdbuf), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case RETR:
sprintf(cmdbuf, "RETR %s\r\n", param);
len = send(socket, cmdbuf, strlen(cmdbuf), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case USER:
sprintf(cmdbuf, "USER %s\r\n", param);
len = send(socket, cmdbuf, strlen(cmdbuf), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case PASS:
sprintf(cmdbuf, "PASS %s\r\n", param);
len = send(socket, cmdbuf, strlen(cmdbuf), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case TYPE:
len = send(socket, "TYPE I\r\n", strlen("TYPE I\r\n"), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case QUIT:
len = send(socket, "QUIT\r\n", strlen("QUIT\r\n"), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
case STOR:
sprintf(cmdbuf, "STOR %s\r\n", param);
len = send(socket, cmdbuf, strlen(cmdbuf), 0);
if (len < 0) {
LOG_I("Send cmd failed.\n");
return -1;
}
break;
default:
break;
}
/* Waiting for server response */
if (select(socket + 1, &readset, NULL, NULL, &timeout) <= 0) {
LOG_I("select the socket timeout!\n");
return -1;
}
/* Wait and receive the packet back from the server. */
int i = 0;
len = 0;
while (1) {
len = recv(socket, (char*)(buff + i), 1, 0);
if (len < 0) {
LOG_I("reading from socket error!\n");
break;
}
if (*(buff + i) == '\n') break; // Stop receiving when a line break is encountered
++i;
}
buff[i + 1] = '\0';
LOG_I("Recv:%s\n", buff);
/* Identify if the status code is correct according to cmd */
switch (cmd)
{
case NOCMD:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 220) {
LOG_I("Server no ack.\n");
return -1;
}
LOG_I("Server ack.\n");
return 0;
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case FINISH:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 226) {
LOG_I("Server no ack.\n");
return -1;
}
LOG_I("Server ack.\n");
return 0;
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case CWD:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 250) {
LOG_I("Switch dir error.\n");
return -1;
}
LOG_I("Switch dir ok.\n");
return 0;
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case PASV:
if (7 == sscanf(buff, "%d%*[^(](%d,%d,%d,%d,%d,%d", &code,&a,&b,&c,&d,&e,&f)) {
if (code != 227) {
LOG_I("Enter pasv mode error.\n");
return -1;
}
*other = e * 256 + f;
LOG_I("Enter pasv mode ok.\n");
return 0;
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case SIZE:
if (2 == sscanf(buff, "%d %d", &code, other)) {
if (code != 213) {
LOG_I("Get size error.\n");
return -1;
}
LOG_I("Get size ok.\n");
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case RETR:
if (2 == sscanf(buff, "%d%*[^(](%d", &code, other)) {
if (code != 150) {
LOG_I("Download file error.\n");
return -1;
}
LOG_I("Download file ok.\n");
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case USER:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 331) {
LOG_I("Username error.\n");
return -1;
}
LOG_I("Username ok.\n");
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case PASS:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 230) {
LOG_I("Password error.\n");
return -1;
}
LOG_I("Password ok.\n");
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case TYPE:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 200) {
LOG_I("Bin mode error.\n");
return -1;
}
LOG_I("Bin mode ok.\n");
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case QUIT:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 221) {
LOG_I("Quit error.\n");
return -1;
}
LOG_I("Quit ok.\n");
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
case STOR:
if (1 == sscanf(buff, "%d", &code)) {
if (code != 150) {
LOG_I("Upload error.\n");
return -1;
}
LOG_I("Upload ok.\n");
} else {
LOG_I("Server no ack.\n");
return -1;
}
break;
default:
break;
}
}
/**
* @description: Recv data from FTP server
* @param socket - Data socket
* @param wait_time - Select timeout period
* @param buf - Buffer for storing data
* @param bufsize - Buffer size
* @param getlen - Actual length of data received
* @return 0: success; others: failure
*/
static int ftp_downloaddata(int socket, int wait_time, char *buf, int bufsize, int *getlen)
{
fd_set readset;
struct timeval timeout;
int len = 0;
timeout.tv_sec = wait_time/1000;
timeout.tv_usec = 0;
FD_ZERO(&readset);
FD_SET(socket, &readset);
/* Waiting for server response */
if (select(socket + 1, &readset, NULL, NULL, &timeout) <= 0) {
LOG_I("select the socket timeout!");
return -1;
}
/* Wait and receive the packet back from the server. */
len = recv(socket, (char*)buf, bufsize, 0);
if (len < 0) {
LOG_I("Reading data socket error!");
return -1;
} else {
*getlen = len;
return 0;
}
}
/**
* @description: Send data to FTP server
* @param socket - Data socket
* @param buf - Buffer for storing data
* @param bufsize - Buffer size
* @param getlen - Actual length of data sended
* @return 0: success; others: failure
*/
static int ftp_uploaddata(int socket, char *buf, int bufsize, int *getlen)
{
int len = 0;
len = send(socket, (char*)buf, bufsize, 0);
if (len < 0) {
LOG_I("Writing data socket error!");
return -1;
} else {
*getlen = len;
return 0;
}
}
/**
* @description: Login to FTP server
* @param addr - Server IP
* @param username - FTP username
* @param password - FTP password
* @return ftp_client pointer
*/
ftp_client *ftp_login(char *addr, char *username, char *password)
{
int ret;
int fd = -1;
ftp_client *ftp = NULL;
int ack = 0;
char cmd[64] = {0};
struct addrinfo hints, *result = NULL, *cur = NULL;
/* Initialize FTP client */
ftp = malloc(sizeof(ftp_client));
if(!ftp) {
LOG_I("Malloc failed.\n");
return NULL;
}
memset(ftp, 0, sizeof(ftp_client));
ftp->control_socket = -1;
ftp->data_socket = -1;
/* Save username password and ip */
strncpy(ftp->username, username, strlen(username));
strncpy(ftp->password, password, strlen(password));
strncpy(ftp->server_ip, addr, strlen(addr));
strncpy(ftp->control_port, CONTROL_PORT, strlen(CONTROL_PORT));
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
/* Connect to control port */
if (getaddrinfo(ftp->server_ip, ftp->control_port, &hints, &result) != 0) goto __exit;
for (cur = result; cur != NULL; cur = cur->ai_next) {
fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
if (fd < 0) continue;
if (connect(fd, cur->ai_addr, cur->ai_addrlen) == 0) {
ftp->control_socket = fd;
break;
}
}
freeaddrinfo(result);
/* Verify user name and password */
if (ftp->control_socket >= 0) {
if (ftp_cmd(ftp->control_socket, 10000, NOCMD, NULL, NULL) != 0) {
goto __exit;
}
if (ftp_cmd(ftp->control_socket, 10000, USER, ftp->username, NULL) != 0) {
goto __exit;
}
if (ftp_cmd(ftp->control_socket, 10000, PASS, ftp->password, NULL) != 0) {
goto __exit;
}
// if (ftp_cmd(ftp->control_socket, 10000, TYPE, NULL, NULL) != 0) {
// goto __exit;
// }
return ftp;
}
__exit:
if (ftp->control_socket >= 0) {
closesocket(ftp->control_socket);
ftp->control_socket = -1;
}
free(ftp);
LOG_I("Something wrong.\n");
return NULL;
}
/**
* @description: Download file from FTP server
* @param ftp - ftp_client pointer
* @param file_name - Name of the file to be downloaded
* @return 0: success; others: failure
*/
int ftp_downloadfile(ftp_client *ftp, char *file_name)
{
int ret = 0, fd = 0;
struct addrinfo hints, *result = NULL, *cur = NULL;
int getlen = 0;
int file_pos = 0;
int port = 0;
int file_size = 0;
char filebuf[128] = {0};
/* Enter the pasv mode */
if (ftp_cmd(ftp->control_socket, 10000, PASV, NULL, &port) != 0) {
ret = -1;
goto __exit;
}
/* Creat the data socket link */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
sprintf(ftp->data_port, "%d", port);
/* Do name resolution with both IPv6 and IPv4 */
if (getaddrinfo(ftp->server_ip, ftp->data_port, &hints, &result) != 0) {
goto __exit;
}
for (cur = result; cur != NULL; cur = cur->ai_next) {
fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
if (fd < 0) {
continue;
}
if (connect(fd, cur->ai_addr, cur->ai_addrlen) == 0) {
ftp->data_socket = fd;
break;
}
closesocket(fd);
}
freeaddrinfo(result);
if(ftp->data_socket < 0)
{
ret = -1;
goto __exit;
}
/* Send download the file command */
if (ftp_cmd(ftp->control_socket, 10000, RETR, file_name, &file_size) != 0) {
ret = -1;
goto __exit;
}
/* Waite the download file finish */
if (ftp_cmd(ftp->control_socket, 10000, FINISH, NULL, NULL) != 0) {
ret = -1;
} else {
ret = 0;
}
/* Read the file */
while(file_size > file_pos) {
if (ftp_downloaddata(ftp->data_socket, 10000, filebuf, 128, &getlen) == 0) {
if (getlen) {
printf("%s\n", filebuf); // Temporarily process the document as a printout
} else {
ret = -1;
goto __exit;
}
} else {
ret = -1;
goto __exit;
}
file_pos = file_pos + getlen;
}
printf("Total length: %d\n", file_pos); // Compare the size of the file previously returned by the server
__exit:
if(ftp->data_socket >= 0)
{
closesocket(ftp->data_socket);
ftp->data_socket = -1;
}
return ret;
}
/**
* @description: Upload file to FTP server
* @param ftp - ftp_client pointer
* @param file_name - Name of the file to be uploaded
* @return 0: success; others: failure
*/
int ftp_uploadfile(ftp_client *ftp, char *file_name)
{
int ret = 0, fd = 0;
struct addrinfo hints, *result = NULL, *cur = NULL;
int getlen = 0;
int file_pos = 0;
int port = 0;
int file_size = 0;
char filebuf[128] = {0};
/* Enter the pasv mode */
if (ftp_cmd(ftp->control_socket, 10000, PASV, NULL, &port) != 0) {
ret = -1;
goto __exit;
}
/* Creat the data socket link */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
sprintf(ftp->data_port, "%d", port);
/* Do name resolution with both IPv6 and IPv4 */
if (getaddrinfo(ftp->server_ip, ftp->data_port, &hints, &result) != 0) {
goto __exit;
}
for (cur = result; cur != NULL; cur = cur->ai_next) {
fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
if (fd < 0) {
continue;
}
if (connect(fd, cur->ai_addr, cur->ai_addrlen) == 0) {
ftp->data_socket = fd;
break;
}
closesocket(fd);
}
freeaddrinfo(result);
if(ftp->data_socket < 0)
{
ret = -1;
goto __exit;
}
/* Send upload the file command*/
if (ftp_cmd(ftp->control_socket, 10000, STOR, file_name, NULL) != 0) {
ret = -1;
goto __exit;
}
/* TODO: update file_size */
while(file_size > file_pos) {
/* TODO: read file and update filebuf */
if (ftp_uploaddata(ftp->data_socket, filebuf, 128, &getlen) == 0) {
if (!getlen) {
ret = -1;
goto __exit;
}
} else {
ret = -1;
goto __exit;
}
file_pos = file_pos + getlen;
}
printf("Total length: %d\n", file_pos);
/* Uploading a file requires the client to actively close the socket. */
if(ftp->data_socket >= 0)
{
closesocket(ftp->data_socket);
ftp->data_socket = -1;
}
/* Waite the upload file finish */
if (ftp_cmd(ftp->control_socket, 10000, FINISH, NULL, NULL) != 0) {
ret = -1;
} else {
ret = 0;
}
__exit:
if(ftp->data_socket >= 0)
{
closesocket(ftp->data_socket);
ftp->data_socket = -1;
}
return ret;
}
/**
* @description: Switch the directory
* @param ftp - ftp_client pointer
* @param dir - Name of the directory
* @return 0: success; others: failure
*/
int ftp_changedir(ftp_client *ftp, char *dir)
{
if (ftp_cmd(ftp->control_socket, 10000, CWD, dir, NULL) == 0) {
return 0;
}
return -1;
}
/**
* @description: Quit the FTP client
* @param ftp - ftp_client pointer
* @return 0: success; others: failure
*/
int ftp_quit(ftp_client *ftp)
{
ftp_cmd(ftp->control_socket, 10000, QUIT, NULL, NULL);
closesocket(ftp->control_socket);
ftp->control_socket = -1;
free(ftp);
return 0;
}

View File

@ -0,0 +1,40 @@
/**
* @file: ftp_client.h
* @brief: Implement FTP client
* @version: 1.0
* @date: 2023/8/30
*/
#include <transform.h>
#define CONTROL_PORT "21"
#define NOCMD 0
#define PASV 1
#define CWD 2
#define SIZE 3
#define RETR 4
#define FINISH 5
#define USER 6
#define PASS 7
#define TYPE 8
#define QUIT 9
#define STOR 10
#define LOG_I printf
typedef struct ftp_client_struct
{
char username[64];
char password[64];
char server_ip[64];
char data_port[6];
char control_port[6];
int control_socket;
int data_socket;
}ftp_client;
ftp_client *ftp_login(char *addr, char *username, char *password);
int ftp_downloadfile(ftp_client *ftp, char *file_name);
int ftp_changedir(ftp_client *ftp, char *dir);
int ftp_quit(ftp_client *ftp);

View File

@ -0,0 +1,27 @@
#include "ftp_client/ftp_client.h"
#include "lwip/sys.h"
void TestFtpClient() {
/* Initialize the network IP */
char self_ipaddr[] = {192, 168, 130, 77};
char self_netmask[] = {255, 255, 254, 0};
char self_gwaddr[] = {192, 168, 130, 1};
lwip_config_tcp(0, self_ipaddr, self_netmask, self_gwaddr);
/* Use the appropriate username and password as login parameters */
ftp_client *f = ftp_login("192.168.130.78", "why", "7355");
/* Choosing the right path */
ftp_changedir(f, "test");
/* Download 10 files of about 4kb in size */
for (int i = 1; i <= 10; ++i) {
char filename[] = {0};
sprintf(filename, "file_%d.txt", i);
ftp_downloadfile(f, filename);
}
/* Close FTP client */
ftp_quit(f);
}
PRIV_SHELL_CMD_FUNCTION(TestFtpClient, Download 10 files from server, PRIV_SHELL_CMD_MAIN_ATTR);

@ -1 +1 @@
Subproject commit a94c007cb4ee726cc29b10626f8bbfc19c989b89
Subproject commit d21965b1cbcfa99b2d36acd029a37f3f2eba612e

@ -1 +1 @@
Subproject commit 254754bc7d06011cbec4655cd229c8ccfb95240b
Subproject commit 2896d7234688de77992e7e1872a7e67a9456b420