目录
一、前言
在嵌入式系统开发中,有时候需要实现文件传输功能,而 FTP(File Transfer Protocol)是一种常用的文件传输协议。LWIP(Lightweight IP)是一个轻量级的 TCP/IP 协议栈,非常适合在资源受限的嵌入式系统中使用。本文将介绍如何使用 LWIP 实现一个简单的 FTP 服务端。
二、LWIP 和 FTP 简介
1.LWIP
LWIP 是一个开源的轻量级 TCP/IP 协议栈,它具有占用内存少、可裁剪性强等特点,非常适合在嵌入式系统中使用。LWIP 支持多种网络接口,包括以太网、Wi-Fi 等,可以方便地与各种硬件平台进行集成。
2.FTP
FTP 是一种用于在网络上进行文件传输的协议,它采用客户端 - 服务器模式。客户端通过向服务器发送命令来请求文件传输、目录列表等操作,服务器则根据客户端的请求进行相应的处理,并返回结果。FTP 支持两种传输模式:主动模式和被动模式。在主动模式下,服务器主动发起数据连接;在被动模式下,服务器等待客户端发起数据连接。
三、实现 FTP 服务端的主要步骤
1.初始化 LWIP
- 在嵌入式系统中,首先需要正确初始化 LWIP 协议栈。这包括配置网络接口、IP 地址、子网掩码、网关等网络参数。
- 确保网络硬件(如以太网控制器)正常工作,并与 LWIP 进行正确的交互。
2.创建 FTP 服务器任务
- 在应用程序中创建一个专门的任务来处理 FTP 服务端的功能。这个任务可以使用 LWIP 的 API 来监听特定端口(通常是 FTP 的默认端口 21),等待客户端的连接请求。
- 例如,可以使用 LWIP 的
socket
函数创建一个 TCP 套接字,并使用bind
函数将其绑定到指定的端口,然后使用listen
函数开始监听连接请求。
3.处理客户端连接
- 当有客户端连接请求到达时,接受这个连接并创建一个新的任务来处理与该客户端的通信。
- 对于每个客户端连接,可以使用 LWIP 的
accept
函数来接受连接,并为每个连接分配一个独立的任务或线程(取决于系统的支持)来处理后续的 FTP 命令和数据传输。
4.实现 FTP 命令处理
- 在处理客户端连接的任务中,实现对各种 FTP 命令的解析和处理。常见的 FTP 命令包括
USER
(用户登录)、PASS
(密码验证)、CWD
(改变目录)、LIST
(列出目录)、RETR
(下载文件)、STOR
(上传文件)等。 - 根据接收到的命令,执行相应的操作,并向客户端发送适当的响应码和消息。例如,对于
LIST
命令,可以遍历当前目录下的文件和子目录,并将其列表发送给客户端。
5.文件系统操作
- 为了实现文件传输和目录操作,需要与嵌入式系统中的文件系统进行交互。这可能涉及到读取文件内容、写入文件、列出目录中的文件等操作。
- 可以使用现有的文件系统库(如 FatFs)来简化文件系统的操作,或者根据具体的存储设备(如内部 Flash、SD 卡等)实现自定义的文件系统操作函数。
6.错误处理和日志记录
- 在 FTP 服务端的实现中,需要考虑各种错误情况,如连接失败、文件不存在、权限不足等。对于这些错误情况,需要向客户端发送相应的错误响应码,并进行适当的日志记录,以便于调试和故障排除。
- 可以使用 LWIP 的错误处理机制和日志记录功能,或者在应用程序中实现自己的错误处理和日志记录机制。
四、示例代码
本文对lwip的移植代码不做示例,以单片机网络可以正常访问ip可以ping通后为基础。以ucosIII系统为例,创建的FTP服务代码是一个独立的任务,如果是freertos系统的话把时间调度那句代码替换一下就行了。
1.创建FTP任务
//FTP任务
#define FTP_TASK_PRIO 9
//任务堆栈大小
#define FTP_STK_SIZE 1024
//任务控制块
OS_TCB FTPTaskTCB;
//任务堆栈
CPU_STK FTP_TASK_STK[FTP_STK_SIZE];
//任务函数
void ftpd_thread(void *pdata);
//创建FTP线程
//返回值:0 FTP创建成功
// 其他 FTP创建失败
u8 FTP_demo_init(void)
{
OS_ERR err;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();//进入临界区
//创建FTP任务
OSTaskCreate((OS_TCB * )&FTPTaskTCB,
(CPU_CHAR * )"FTP task",
(OS_TASK_PTR )ftpd_thread,
(void * )0,
(OS_PRIO )FTP_TASK_PRIO,
(CPU_STK * )&FTP_TASK_STK[0],
(CPU_STK_SIZE)FTP_STK_SIZE/10,
(CPU_STK_SIZE)FTP_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OS_CRITICAL_EXIT(); //退出临界区
return err;
}
注意任务堆栈大小,堆栈设置太小的话,有的时候会发生内存溢出导致程序崩溃,或者socked不能创建等问题。
2. FTP任务代码
void ftpd_thread(void *par)
{
OS_ERR err;
int numbytes;
int sockfd, maxfdp1;
struct sockaddr_in local;
fd_set readfds, tmpfds;
struct conn *conn;
u32 addr_len = sizeof(struct sockaddr);
#if 0
char *ftp_buf = (char *) malloc(FTP_BUFFER_SIZE);
#endif
printf("Mount OK\n\r");
local.sin_port = htons(FTP_CMD_PORT);
local.sin_family = PF_INET;
local.sin_addr.s_addr = INADDR_ANY;
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
printf("ERROR: Create socket\r\n");
return;
}
printf("SUCCESS: Create socket\r\n");
if (bind(sockfd, (struct sockaddr *) &local, addr_len) < 0) {
printf("ERROR: Bind socket\r\n");
}
printf("SUCCESS: Bind socket\r\n");
if (listen(sockfd, FTP_MAX_CONNECTION) < 0) {
printf("ERROR: Listen %d socket connections\r\n", FTP_MAX_CONNECTION);
}
printf("SUCCESS: Listen socket\r\n");
FD_SET(sockfd, &readfds);
for (;;) {
/* get maximum fd */
maxfdp1 = sockfd + 1;
conn = conn_list;
while (conn != NULL) {
if (maxfdp1 < conn->sockfd + 1)
maxfdp1 = conn->sockfd + 1;
FD_SET(conn->sockfd, &readfds);
conn = conn->next;
}
tmpfds = readfds;
if (select(maxfdp1, &tmpfds, 0, 0, 0) == 0)
continue;
if (FD_ISSET(sockfd, &tmpfds)) {
int com_socket;
struct sockaddr_in remote;
com_socket = accept(sockfd, (struct sockaddr *) &remote, (socklen_t *) & addr_len);
if (com_socket == -1) {
printf("Error on accept()\nContinuing...\r\n");
continue;
} else {
printf("Got connection from %s\r\n", inet_ntoa(remote.sin_addr));
send(com_socket, FTP_WELCOME_MSG, strlen(FTP_WELCOME_MSG), 0);
FD_SET(com_socket, &readfds);
/* new conn */
if(conn!=NULL){
destroy_conn(conn);
conn=NULL;
}
conn = alloc_new_conn();
if (conn != NULL) {
strcpy(conn->currentdir, FTP_SRV_ROOT);
conn->sockfd = com_socket;
conn->remote = remote;
}
}
}
{
struct conn *next;
conn = conn_list;
while (conn != NULL) {
next = conn->next;
if (FD_ISSET(conn->sockfd, &tmpfds)) {
numbytes = recv(conn->sockfd, ftp_buf, FTP_BUFFER_SIZE, 0);
if (numbytes == 0 || numbytes == -1) {
printf("Client %s disconnected\r\n", inet_ntoa(conn->remote.sin_addr));
FD_CLR(conn->sockfd, &readfds);
if(conn!=NULL){
close(conn->sockfd);
destroy_conn(conn);
conn=NULL;
}
if(sockfd_ft!=-1){
closesocket(sockfd_ft);
sockfd_ft=-1;
}
} else {
ftp_buf[numbytes] = 0;
if (ftp_process_request(conn, ftp_buf) == -1) {
printf("Client %s disconnected\r\n", inet_ntoa(conn->remote.sin_addr));
if(conn!=NULL){
close(conn->sockfd);
destroy_conn(conn);
conn=NULL;
}
if(sockfd_ft!=-1){
closesocket(sockfd_ft);
sockfd_ft=-1;
}
}
OSTimeDlyHMSM(0,0,0,1,OS_OPT_TIME_HMSM_STRICT,&err); //延时1ms
}
}
conn = next;
}
}
}
}
static struct conn *alloc_new_conn(void)
{
struct conn *conn;
conn = (struct conn *) mymalloc(SRAMEX,sizeof(struct conn));
conn->next = conn_list;
conn->offset = 0;
conn_list = conn;
/* printf("Alloc addr: %p\r\n", conn); */
return conn;
}
static void destroy_conn(struct conn *conn)
{
struct conn *list;
if (conn_list == conn) {
conn_list = conn_list->next;
conn->next = NULL;
} else {
list = conn_list;
while (list->next != conn)
list = list->next;
list->next = conn->next;
conn->next = NULL;
}
myfree(SRAMEX,conn);
}
创建一个 TCP 服务器套接字,并监听 FTP 端口(默认端口为 21),有数据传给服务器端以后,通过ftp_process_request函数做处理。
3.处理交互数据
static int ftp_process_request(struct conn *conn, char *buf)
{
OS_ERR err;
FRESULT rc;
FIL file;
ftp_cmd_user cmd;
int num_bytes;
char *spare_buf;
char *msgsend;
spare_buf=(char *) mymalloc(SRAMEX,256);
msgsend=(char *) mymalloc(SRAMEX,256);
memset(msgsend,0,256);
memset(spare_buf,0,256);
struct timeval tv;
fd_set readfds;
char *sbuf;
char *parameter_ptr, *ptr;
struct sockaddr_in pasvremote, local;
u32 addr_len = sizeof(struct sockaddr_in);
int ret = 0;
int led_r = 0, led_w = 0;
sbuf = (char *) mymalloc(SRAMEX,FTP_BUFFER_SIZE);
if (sbuf == NULL) {
printf("ERROR: mymalloc for conn\r\n");
return -1;
}
/* printf("SUCCESS: mymalloc for conn\r\n"); */
tv.tv_sec = 3;
tv.tv_usec = 0;
/* remove \r\n */
ptr = buf;
while (*ptr) {
if (*ptr == '\r' || *ptr == '\n')
*ptr = 0;
ptr++;
}
parameter_ptr = strchr(buf, ' ');
if (parameter_ptr != NULL)
parameter_ptr++;
/* debug: */
printf("%s requested \"%s\"\r\n", inet_ntoa(conn->remote.sin_addr), buf);
cmd = (ftp_cmd_user) do_parse_command(buf);
#if 0
if (cmd > CMD_PASS && conn->status != LOGGED_STAT) {
do_send_reply(conn->sockfd, "550 Permission denied.\r\n");
myfree(SRAMEX,sbuf);
return 0;
}
#endif
switch (cmd) {
case CMD_USER:
#if 0
printf("%s sent login \"%s\"\r\n", inet_ntoa(conn->remote.sin_addr), parameter_ptr);
/* login correct */
if (strcmp(parameter_ptr, FTP_USER) == 0) {
do_send_reply(conn->sockfd, "331 Password required for \"%s\"\r\n", parameter_ptr);
conn->status = USER_STAT;
} else {
/* incorrect login */
do_send_reply(conn->sockfd, "530 Login incorrect. Bye.\r\n");
ret = -1;
}
#endif
sprintf(msgsend,"331 Password required for \"%s\"\r\n", parameter_ptr);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "331 Password required for \"%s\"\r\n", parameter_ptr);
break;
case CMD_PASS:
#if 0
printf("%s sent password \"%s\"\r\n", inet_ntoa(conn->remote.sin_addr), parameter_ptr);
/* password correct */
if (strcmp(parameter_ptr, FTP_PASSWORD) == 0) {
do_send_reply(conn->sockfd, "230 User logged in\r\n");
conn->status = LOGGED_STAT;
printf("%s Password correct\r\n", inet_ntoa(conn->remote.sin_addr));
} else {
/* incorrect password */
do_send_reply(conn->sockfd, "530 Login or Password incorrect. Bye!\r\n");
conn->status = ANON_STAT;
ret = -1;
}
#endif
sprintf(msgsend,"230 Password OK. Current directory is %s\r\n", FTP_SRV_ROOT);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "230 Password OK. Current directory is %s\r\n", FTP_SRV_ROOT);
break;
case CMD_LIST:
sprintf(msgsend,"150 Opening Binary mode connection for file list.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "150 Opening Binary mode connection for file list.\r\n");
do_full_list(conn->currentdir, conn->pasv_sockfd);
close(conn->pasv_sockfd);
conn->pasv_active = 0;
sprintf(msgsend,"226 Transfer complete.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "226 Transfer complete.\r\n");
break;
case CMD_NLST:
sprintf(msgsend,"150 Opening Binary mode connection for file list.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "150 Opening Binary mode connection for file list.\r\n");
do_simple_list(conn->currentdir, conn->pasv_sockfd);
close(conn->pasv_sockfd);
conn->pasv_active = 0;
sprintf(msgsend,"226 Transfer complete.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "226 Transfer complete.\r\n");
break;
case CMD_TYPE:
if (strcmp(parameter_ptr, "I") == 0) {
sprintf(msgsend,"200 Type set to binary.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "200 Type set to binary.\r\n");
} else {
sprintf(msgsend,"200 Switching to ASCII mode.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
// do_send_reply(conn->sockfd, "200 Type set to ascii.\r\n");
}
break;
case CMD_RETR:
strcpy(spare_buf, buf + 5);
do_full_path(conn, parameter_ptr, spare_buf, 256);
num_bytes = do_get_filesize(spare_buf);
if (num_bytes == -1) {
sprintf(msgsend,"550 \"%s\" : not a regular file\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "550 \"%s\" : not a regular file\r\n", spare_buf);
conn->offset = 0;
break;
}
rc = f_open(&file, spare_buf, FA_READ);
if (rc != FR_OK) {
printf("ERROR: open file %s for reading\r\n", spare_buf);
break;
}
if (conn->offset > 0 && conn->offset < num_bytes) {
f_lseek(&file, conn->offset);
sprintf(msgsend,"150 Opening binary mode data connection for partial \"%s\" (%d/%d bytes).\r\n",
spare_buf, num_bytes - conn->offset, num_bytes);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "150 Opening binary mode data connection for partial \"%s\" (%d/%d bytes).\r\n",
// spare_buf, num_bytes - conn->offset, num_bytes);
} else {
sprintf(msgsend,"150 Opening binary mode data connection for \"%s\" (%d bytes).\r\n", spare_buf, num_bytes);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "150 Opening binary mode data connection for \"%s\" (%d bytes).\r\n", spare_buf, num_bytes);
}
led_r = 0;
do {
f_read(&file, sbuf, FTP_BUFFER_SIZE, (unsigned *) &num_bytes);
if (num_bytes > 0) {
send(conn->pasv_sockfd, sbuf, num_bytes, 0);
if (led_r++ % 50 == 0) {
//led_toggle(LED2);
}
}
OSTimeDlyHMSM(0,0,0,1,OS_OPT_TIME_HMSM_STRICT,&err); //延时1ms
} while (num_bytes > 0);
sprintf(msgsend,"226 Finished.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "226 Finished.\r\n");
f_close(&file);
close(conn->pasv_sockfd);
break;
case CMD_STOR:
do_full_path(conn, parameter_ptr, spare_buf, 256);
rc = f_open(&file, spare_buf, FA_WRITE | FA_OPEN_ALWAYS);
if (rc != FR_OK) {
sprintf(msgsend,"550 Cannot open \"%s\" for writing.\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "550 Cannot open \"%s\" for writing.\r\n", spare_buf);
break;
}
sprintf(msgsend,"150 Opening binary mode data connection for \"%s\".\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "150 Opening binary mode data connection for \"%s\".\r\n", spare_buf);
FD_ZERO(&readfds);
FD_SET(conn->pasv_sockfd, &readfds);
printf("Waiting %d seconds for data...\r\n", tv.tv_sec);
led_w = 0;
while (select(conn->pasv_sockfd + 1, &readfds, 0, 0, &tv) > 0) {
if ((num_bytes = recv(conn->pasv_sockfd, sbuf, FTP_BUFFER_SIZE, 0)) > 0) {
unsigned bw;
f_write(&file, sbuf, num_bytes, &bw);
if (led_w++ % 50 == 0) {
//led_toggle(LED1);
}
} else if (num_bytes == 0) {
f_close(&file);
//do_send_reply(conn->sockfd, "226 Finished.\r\n");
sprintf(msgsend,"226 Finished.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
break;
} else if (num_bytes == -1) {
f_close(&file);
ret = -1;
break;
}
}
close(conn->pasv_sockfd);
break;
case CMD_SIZE:
do_full_path(conn, parameter_ptr, spare_buf, 256);
num_bytes = do_get_filesize(spare_buf);
if (num_bytes == -1) {
sprintf(msgsend,"550 \"%s\" : not a regular file\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "550 \"%s\" : not a regular file\r\n", spare_buf);
} else {
//do_send_reply(conn->sockfd, "213 %d\r\n", num_bytes);
sprintf(msgsend,"213 %d\r\n", num_bytes);
do_send_reply_me(conn->sockfd,msgsend);
}
break;
case CMD_MDTM:
sprintf(msgsend,"550 \"/\" : not a regular file\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "550 \"/\" : not a regular file\r\n");
break;
case CMD_SYST:
sprintf(msgsend,"215 UNIX Type: L8\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "215 UNIX Type: L8\r\n");
break;
case CMD_PWD:
sprintf(msgsend,"257 \"%s\" is current directory.\r\n", conn->currentdir);
//do_send_reply(conn->sockfd, "257 \"%s\" is current directory.\r\n", conn->currentdir);
do_send_reply_me(conn->sockfd,msgsend);
break;
case CMD_CWD:
do_full_path(conn, parameter_ptr, spare_buf, 256);
sprintf(msgsend,"250 Changed to directory \"%s\"\r\n", spare_buf);
//do_send_reply(conn->sockfd, "250 Changed to directory \"%s\"\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
memset(conn->currentdir,0,256);
strcpy(conn->currentdir, spare_buf);
//printf("CWD: Changed to directory %s\r\n", spare_buf);
break;
case CMD_CDUP:
//sprintf(spare_buf, "%s/%s", conn->currentdir, "..");
sprintf(spare_buf, "%s", conn->currentdir);
do_step_down(spare_buf);
memset(conn->currentdir,0,256);
strcpy(conn->currentdir, spare_buf);
sprintf(msgsend,"250 Changed to directory \"%s\"\r\n", spare_buf);
//printf("path: %s\r\n", conn->currentdir);
//do_send_reply(conn->sockfd, "250 Changed to directory \"%s\"\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
printf("CDUP: Changed to directory %s\r\n", spare_buf);
break;
case CMD_PORT:
{
int portcom[6];
num_bytes = 0;
portcom[num_bytes++] = atoi(strtok(parameter_ptr, ".,;()"));
for (; num_bytes < 6; num_bytes++) {
portcom[num_bytes] = atoi(strtok(0, ".,;()"));
}
sprintf(spare_buf, "%d.%d.%d.%d", portcom[0], portcom[1], portcom[2], portcom[3]);
FD_ZERO(&readfds);
if ((conn->pasv_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
sprintf(msgsend,"425 Can't open data connection.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "425 Can't open data connection.\r\n");
close(conn->pasv_sockfd);
conn->pasv_active = 0;
break;
}
local.sin_port = htons(FTP_DATA_PORT);
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = PF_INET;
if (bind(conn->pasv_sockfd, (struct sockaddr *) &local, addr_len) < 0) {
printf("ERROR: Bind socket\r\n");
}
printf("SUCCESS: Bind socket to %d port\r\n", FTP_DATA_PORT);
pasvremote.sin_addr.s_addr = ((u32) portcom[3] << 24) | ((u32) portcom[2] << 16) | ((u32) portcom[1] << 8) | ((u32) portcom[0]);
pasvremote.sin_port = htons(portcom[4] * 256 + portcom[5]);
pasvremote.sin_family = PF_INET;
if (connect(conn->pasv_sockfd, (struct sockaddr *) &pasvremote, addr_len) == -1) {
pasvremote.sin_addr = conn->remote.sin_addr;
if (connect(conn->pasv_sockfd, (struct sockaddr *) &pasvremote, addr_len) == -1) {
//do_send_reply(conn->sockfd, "425 Can't open data connection.\r\n");
sprintf(msgsend,"425 Can't open data connection.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
close(conn->pasv_sockfd);
break;
}
}
conn->pasv_active = 1;
conn->pasv_port = portcom[4] * 256 + portcom[5];
printf("Connected to Data(PORT) %s @ %d\r\n", spare_buf, portcom[4] * 256 + portcom[5]);
sprintf(msgsend,"200 Port Command Successful.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "200 Port Command Successful.\r\n");
}
break;
case CMD_REST:
if (atoi(parameter_ptr) >= 0) {
conn->offset = atoi(parameter_ptr);
//do_send_reply(conn->sockfd, "350 Send RETR or STOR to start transfert.\r\n");
sprintf(msgsend,"350 Send RETR or STOR to start transfert.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
}
break;
case CMD_MKD:
do_full_path(conn, parameter_ptr, spare_buf, 256);
if (f_mkdir(spare_buf)) {
sprintf(msgsend,"550 File \"%s\" exists.\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "550 File \"%s\" exists.\r\n", spare_buf);
} else {
sprintf(msgsend,"257 directory \"%s\" was successfully created.\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "257 directory \"%s\" was successfully created.\r\n", spare_buf);
}
break;
case CMD_DELE:
do_full_path(conn, parameter_ptr, spare_buf, 256);
if (f_unlink(spare_buf) == FR_OK){
sprintf(msgsend,"250 file \"%s\" was successfully deleted.\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "250 file \"%s\" was successfully deleted.\r\n", spare_buf);
}else {
sprintf(msgsend,"550 Not such file or directory: %s.\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "550 Not such file or directory: %s.\r\n", spare_buf);
}
break;
case CMD_RMD:
do_full_path(conn, parameter_ptr, spare_buf, 256);
if (f_unlink(spare_buf)) {
sprintf(msgsend,"550 Directory \"%s\" doesn't exist.\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "550 Directory \"%s\" doesn't exist.\r\n", spare_buf);
} else {
sprintf(msgsend,"257 directory \"%s\" was successfully deleted.\r\n", spare_buf);
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "257 directory \"%s\" was successfully deleted.\r\n", spare_buf);
}
break;
#if 1
case CMD_PASV:
do {
int dig1, dig2;
extern struct ip_addr my_ipaddr;
unsigned char optval = 1;
if(FTP_PASSV_PORT!=1499&&FTP_PASSV_PORT<=10000){
FTP_PASSV_PORT=FTP_PASSV_PORT+1;
}else if(FTP_PASSV_PORT==1499){
FTP_PASSV_PORT=FTP_PASSV_PORT+2;
}else{
FTP_PASSV_PORT=1025;
}
conn->pasv_port = FTP_PASSV_PORT;
conn->pasv_active = 1;
local.sin_port = htons(conn->pasv_port);
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = PF_INET;
dig1 = (int) (conn->pasv_port / 256);
dig2 = conn->pasv_port % 256;
FD_ZERO(&readfds);
sockfd_ft = socket(PF_INET, SOCK_STREAM, 0);
if (sockfd_ft == -1) {
sprintf(msgsend,"425 Can't open data connection.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "425 Can't open data connection.\r\n");
printf("Can't make passive TCP socket\r\n");
ret = 1;
break;
}
if (bind(sockfd_ft, (struct sockaddr *) &local, addr_len) == -1) {
sprintf(msgsend,"425 Can't open data connection.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "425 Can't open data connection.\r\n");
printf("Can't bind passive socket\r\n");
ret = 3;
break;
}
if (listen(sockfd_ft, 1) == -1) {
sprintf(msgsend,"425 Can't open data connection.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "425 Can't open data connection.\r\n");
printf("Can't listen passive socket\r\n");
ret = 4;
break;
}
sprintf(msgsend,"227 Entering passive mode (%d,%d,%d,%d,%d,%d)\r\n",
sys_save_dat.lan_devive_ip[0],
sys_save_dat.lan_devive_ip[1],
sys_save_dat.lan_devive_ip[2],
sys_save_dat.lan_devive_ip[3],
dig1, dig2);
do_send_reply_me(conn->sockfd,msgsend);
FD_SET(sockfd_ft, &readfds);
select(sockfd_ft + 1, &readfds, 0, 0, &tv);
if (FD_ISSET(sockfd_ft, &readfds)) {
conn->pasv_sockfd = accept(sockfd_ft, (struct sockaddr *) &pasvremote, (socklen_t *) & addr_len);
if (conn->pasv_sockfd < 0) {
printf("Can't accept socket\r\n");
sprintf(msgsend,"425 Can't open data connection.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "425 Can't open data connection.\r\n");
ret = 5;
break;
} else {
printf("Got Data(PASV) connection from %s\r\n", inet_ntoa(pasvremote.sin_addr));
conn->pasv_active = 1;
if(sockfd_ft!=-1){
closesocket(sockfd_ft);
sockfd_ft=-1;
}
}
} else {
ret = 6;
break;
}
} while (0);
if (ret) {
if(sockfd_ft!=-1){
closesocket(sockfd_ft);
sockfd_ft=-1;
}
printf("Select err\r\n");
close(conn->pasv_sockfd);
conn->pasv_active = 0;
ret = 0;
}
break;
#endif
case CMD_FEAT:
sprintf(msgsend,"211 No-features\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "211 No-features\r\n");
break;
case CMD_QUIT:
sprintf(msgsend,"221 Adios!\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "221 Adios!\r\n");
ret = -1;
break;
default:
sprintf(msgsend,"502 Not Implemented yet.\r\n");
do_send_reply_me(conn->sockfd,msgsend);
//do_send_reply(conn->sockfd, "502 Not Implemented yet.\r\n");
break;
}
if (sbuf) {
myfree(SRAMEX,sbuf);
}
if (spare_buf) {
myfree(SRAMEX,spare_buf);
}
if (msgsend) {
myfree(SRAMEX,msgsend);
}
return ret;
}
/*目录处理*/
int do_step_down(char *path)
{
int len, i;
int flag;
len = strlen(path);
if(len==1){
path[0]='/';
path[1]=0;
}else if(len>1){
for(i=len-2;i>=0;i--){
if(path[i]=='/'){
flag=i;
break;
}
}
for(i=len-1;i>=flag;i--){
if(i!=0){
path[i]=0;
}
}
}
return 0;
}
int do_full_path(struct conn *conn, char *path, char *new_path, size_t size)
{
int len = strlen(path);
if(path[0]=='.'&&path[1]==0x00){
sprintf(new_path,"%s", conn->currentdir);
}else if(path[0]!='/'){
if(path[len-1]!='/'){
if(conn->currentdir[2]==0){
sprintf(new_path, "%s%s",conn->currentdir,path);
}else{
sprintf(new_path, "%s/%s",conn->currentdir,path);
}
}else{
sprintf(new_path, "/%s",path);
}
}else{
sprintf(new_path, "%s",path);
}
return 0;
}
关键点在于 USER
(用户登录)、PASS
(密码验证)、CWD
(改变目录)、LIST
(列出目录)、RETR
(下载文件)、STOR
(上传文件)这几个命令的解析,尤其是CWD,不同FTP客户端软件会发送不同的CWD命令,有的发送命令前面带/,有的不带/,要根据实际客户端做好解析,不然不能进入正确的目录,PASV命令被动连接会创建一个UDP,如果分配的堆栈不足或者lwip设置的udp连接数和内存过少,会导致PASV的UDP失败,导致不能正常运行。
4.成功截图
(1)XFTP8通讯
(2)MobaXterm通讯
两种客户端CWD发送的地址不同(带不带/),上面代码已经通过目录解析适配了。
五、总结
本文介绍了如何使用 LWIP 实现一个简单的 FTP 服务端。通过本文的介绍,你可以了解到 LWIP 和 FTP 的基本概念,以及如何使用 LWIP 实现 FTP 服务端的步骤。在实际应用中,你可以根据自己的需求对 FTP 服务端进行扩展和优化,以满足不同的应用场景。