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 0char *ftp_buf = (char *) malloc(FTP_BUFFER_SIZE);	
#endifprintf("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 0if (cmd > CMD_PASS && conn->status != LOGGED_STAT) {do_send_reply(conn->sockfd, "550 Permission denied.\r\n");myfree(SRAMEX,sbuf);return 0;}
#endifswitch (cmd) {case CMD_USER:
#if 0printf("%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;}
#endifsprintf(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 0printf("%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;}
#endifsprintf(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 1case 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;
#endifcase 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 服务端进行扩展和优化,以满足不同的应用场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/62759.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java基础访问修饰符全解析

一、Java 访问修饰符概述 Java 中的访问修饰符用于控制类、方法、变量和构造函数的可见性和访问权限&#xff0c;主要有四种&#xff1a;public、protected、default&#xff08;无修饰符&#xff09;和 private。 Java 的访问修饰符在编程中起着至关重要的作用&#xff0c;它…

llvm源码编译

0x00 获取llvm源码 获取llvm项目源码&#xff1a;git clone https://github.com/llvm/llvm-project.git 但是&#xff0c;该项目较大&#xff0c;且直接从github下载源码可能会超时失败。可利用gitee的镜像项目进行clone&#xff1a;git clone --depth 1 https://gitee.com/m…

SpringBoot源码-Spring Boot启动时控制台为何会打印logo以及自定义banner.txt文件控制台打印

1.当我们启动一个SpringBoot项目的时候&#xff0c;入口程序就是main方法&#xff0c;而在main方法中就执行了一个run方法。 SpringBootApplication public class StartApp {public static void main(String[] args) {// testSpringApplication.run(StartApp.class);} }publi…

Uniad复现学习

在优云平台部署训练&#xff0c;加速训练。 关于UCloud(优刻得)旗下的compshare算力共享平台 UCloud(优刻得)是中国知名的中立云计算服务商&#xff0c;科创板上市&#xff0c;中国云计算第一股。 UCloud&#xff08;优刻得&#xff09;旗下的Compshare算力共享平台具有以下优点…

数学建模——Topsis法

数模评价类&#xff08;2&#xff09;——Topsis法 概述 Topsis:Technique for Order Preference by Similarity to Ideal Solution 也称优劣解距离法&#xff0c;该方法的基本思想是&#xff0c;通过计算每个备选方案与理想解和负理想解之间的距离&#xff0c;从而评估每个…

基于单片机的四位数码管检测有毒气体

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过滑动变阻器连接ADC0832数模转换器模拟有毒气体浓度检测&#xff0c;通过数码管实时显示&#xff0c;如果超过阈值&#xff0c;则蜂鸣器报警&#xff0c;灯光亮起。按…

小程序 - 比较数字大小

小程序交互练习 - 比较数字大小的小程序 目录 比较数字大小 功能描述 准备工作 页面内容 设置页面事件 页面绑定事件 比较大小 按钮绑定事件 比较事件 设置结果显示 页面样式 功能截图 总结 比较数字大小 本案例将实现“比较数字大小”微信小程序&#xff0c;它的…

windows下用mysqld启动免安装mysql

windows系统可以下载免安装版本&#xff0c;就是绿色版&#xff0c;里面包含mysql运行的所有必要条件。 ![[Pasted image 20241128231459.png]] 启动步骤&#xff1a; 解压&#xff0c;然后在解压目录创建my.ini。 [mysqld] # 设置13306端口 port13306# 设置mysql的安装目录…

windows安装itop

本文介绍 win10 安装 itop 安装WAMP集成环境前 先安装visual c 安装itop前需要安装WAMP集成环境(windowsApacheMysqlPHP) 所需文件百度云盘 通过网盘分享的文件&#xff1a;itop.zip 链接: https://pan.baidu.com/s/1D5HrKdbyEaYBZ8_IebDQxQ 提取码: m9fh 步骤一&#xff1…

Leetcode - 周赛425

目录 一&#xff0c;3364. 最小正和子数组 二&#xff0c; 3365. 重排子字符串以形成目标字符串 三&#xff0c;3366. 最小数组和 四&#xff0c;3367. 移除边之后的权重最大和 一&#xff0c;3364. 最小正和子数组 本题可以直接暴力枚举&#xff0c;代码如下&#xff1a; …

微服务即时通讯系统的实现(服务端)----(2)

目录 1. 语音识别子服务的实现1.1 功能设计1.2 模块划分1.3 模块功能示意图1.4 接口的实现 2. 文件存储子服务的实现2.1 功能设计2.2 模块划分2.3 模块功能示意图2.4 接口的实现 3. 用户管理子服务的实现3.1 功能设计3.2 模块划分3.3 功能模块示意图3.4 数据管理3.4.1 关系数据…

Matlab Simulink HDL Coder开发流程(一)— 创建HDL兼容的Simulink模型

创建HDL兼容的Simulink模型 一、使用Balnk DUT模板二、从HDL Coder库中选择模块三、为DUT开发算法/功能四、为设计创建Testbench五、仿真验证设计功能六、Simulink模型生成HDL代码 这个例子说明了如何创建一个用于生成HDL代码的Simulink模型。要创建兼容HDL代码生成的MATLAB算法…

mfc110u.dll是什么意思,mfc110u.dll丢失解决方法大全详解

mfc110u.dll是Microsoft Foundation Classes (MFC)库的一个特定版本&#xff08;版本11.0&#xff09;的Unicode动态链接库文件。MFC是Microsoft为C开发者设计的一个应用程序框架&#xff0c;主要用于简化Windows应用程序的开发工作。这个框架封装了很多Windows API函数&#x…

debian 11 虚拟机环境搭建过坑记录

目录 安装过程系统配置修改 sudoers 文件网络配置换源安装桌面mount nfs 挂载安装复制功能tab 无法补全其他安装 软件配置eclipse 配置git 配置老虚拟机硬盘挂载 参考 原来去 debian 官网下载了一个最新的 debian 12&#xff0c;安装后出现包依赖问题&#xff0c;搞了半天&…

JAVAWeb之CSS学习

前引 CSS&#xff0c;层叠样式表&#xff08;Cascading Style Sheets&#xff09;&#xff0c;能够对网页中元素位置的排版进行像素级精确控制&#xff0c;支持几乎所有的字体字号样式&#xff0c;拥有网页对象和模型样式编辑的能力&#xff0c;简单来说&#xff0c;美化页面。…

macos下brew安装redis

首先确保已安装brew&#xff0c;接下来搜索资源&#xff0c;在终端输入如下命令&#xff1a; brew search redis 演示如下&#xff1a; 如上看到有redis资源&#xff0c;下面进行安装&#xff0c;执行下面的命令&#xff1a; brew install redis 演示效果如下&#xff1a; …

element ui select绑定的值是对象的属性时,显示异常.

需要声明 value-key"value",如果还不行可能是数据类型不一致数字0和字符串0是不一致的. el-select v-model"value" clearable placeholder"Select" value-key"value" style"width: 240px"><!-- <el-option v-for&…

黑马程序员Java笔记整理(day06)

1.继承的特点 2.继承的权限 3. 4.小结 5.方法重写 6.子类构造器 7.兄弟构造器 8.多态 9.小结

VPC9527同步整流控制器,相对最大电压检测与强力自供电,与MP6908完全PIN TO PIN

VPC9527 是一款高性能的同步整流控制器,它兼容 CCM 和 DCM 两种模式,最大工作频率高达 700kHz;可 通过 SEL 引脚的逻辑电压来选择 400nS 或 800nS 两个关断检测的屏蔽时间;可通过 VLC 引脚来调整限压导通的 参数,以便与所选同步整流管的参数相匹配,获得适应的最优性能;它…

万字长文解读深度学习——多模态模型BLIP2

&#x1f33a;历史文章列表&#x1f33a; 深度学习——优化算法、激活函数、归一化、正则化 深度学习——权重初始化、评估指标、梯度消失和梯度爆炸 深度学习——前向传播与反向传播、神经网络&#xff08;前馈神经网络与反馈神经网络&#xff09;、常见算法概要汇总 万字长…