Bootstrap

LWIP和FATFS 实现 FTP 服务端

目录

一、前言

二、LWIP 和 FTP 简介

1.LWIP

2.FTP

三、实现 FTP 服务端的主要步骤

1.初始化 LWIP

2.创建 FTP 服务器任务

3.处理客户端连接

4.实现 FTP 命令处理

5.文件系统操作

6.错误处理和日志记录

四、示例代码

1.创建FTP任务

2. FTP任务代码

3.处理交互数据

4.成功截图

五、总结


一、前言

        在嵌入式系统开发中,有时候需要实现文件传输功能,而 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 服务端进行扩展和优化,以满足不同的应用场景。

;