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.rhkb.cn/news/482363.html

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

相关文章

多线程篇-8--线程安全(死锁,常用保障安全的方法,安全容器,原子类,Fork/Join框架等)

1、线程安全和不安全定义 &#xff08;1&#xff09;、线程安全 线程安全是指一个类或方法在被多个线程访问的情况下可以正确得到结果&#xff0c;不会出现数据不一致或其他错误行为。 线程安全的条件 1、原子性&#xff08;Atomicity&#xff09; 多个操作要么全部完成&a…

【css实现收货地址下边的平行四边形彩色线条】

废话不多说&#xff0c;直接上代码&#xff1a; <div class"address-block" ><!-- 其他内容... --><div class"checked-ar"></div> </div> .address-block{height:120px;position: relative;overflow: hidden;width: 500p…

【Python网络爬虫笔记】5-(Request 带参数的get请求) 爬取豆瓣电影排行信息

目录 1.抓包工具查看网站信息2.代码实现3.运行结果 1.抓包工具查看网站信息 请求路径 url:https://movie.douban.com/typerank请求参数 页面往下拉&#xff0c;出现新的请求结果&#xff0c;参数start更新&#xff0c;每次刷新出20条新的电影数据 2.代码实现 # 使用网络爬…

「Mac畅玩鸿蒙与硬件35」UI互动应用篇12 - 简易日历

本篇将带你实现一个简易日历应用&#xff0c;显示当前月份的日期&#xff0c;并支持选择特定日期的功能。用户可以通过点击日期高亮选中&#xff0c;还可以切换上下月份&#xff0c;体验动态界面的交互效果。 关键词 UI互动应用简易日历动态界面状态管理用户交互 一、功能说明…

elastic net回归

Elastic Net回归是一种结合了**岭回归&#xff08;Ridge Regression&#xff09;和Lasso回归&#xff08;Lasso Regression&#xff09;**的回归方法&#xff0c;旨在同时处理多重共线性和变量选择问题。Elastic Net通过惩罚项&#xff08;正则化&#xff09;对模型进行约束&am…

CSP-J初赛不会备考咋办?

以下备考攻略仅供参考&#xff0c;如需资料请私信作者&#xff01;求支持&#xff01; 目录 一、编程语言基础 1.语法知识 -变量与数据类型 -运算符 -控制结构 -函数 2.标准库的使用 -输入输出流 -字符串处理 -容器类&#xff08;可选&#xff09; 二、算法与数据结构 1.基…

IDEA全局设置-解决maven加载过慢的问题

一、IDEA全局设置 注意&#xff1a;如果不是全局设置&#xff0c;仅仅针对某个项目有效&#xff1b;例在利用网上教程解决maven加载过慢的问题时&#xff0c;按步骤设置却得不到解决&#xff0c;原因就是没有在全局设置。 1.如何进行全局设置 a.在项目页面&#xff0c;点击f…

JAVAWeb之CSS学习

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

基于PHP的香水销售系统的设计与实现

摘 要 时代科技高速发展的背后&#xff0c;也带动了经济的增加&#xff0c;人们对生活质量的要求也不断提高。香水作为一款在人际交往过程中&#xff0c;给对方留下良好地第一印象的产品&#xff0c;在生活中也可以独自享受其为生活带来的点缀。目前香水市场体量庞大&#xff…

3. STM32_串口

数据通信的基础概念 什么是串行/并行通信&#xff1a; 串行通信就是数据逐位按顺序依次传输 并行通信就是数据各位通过多条线同时传输。 什么是单工/半双工/全双工通信&#xff1a; 单工通信&#xff1a;数据只能沿一个方向传输 半双工通信&#xff1a;数据可以沿两个方向…

网络安全相关证书资料

网络安全相关证书有哪些&#xff1f; 网络安全相关证书有哪些呢&#xff1f;了解一下&#xff01; 1. CISP &#xff08;国家注册信息安全专业人员&#xff09; 说到CISP&#xff0c;安全从业者基本上都有所耳闻&#xff0c;算是国内权威认证&#xff0c;毕竟有政府背景给认证…

【目标检测】YOLO:深度挖掘YOLO的性能指标。

YOLO 性能指标 1、物体检测指标2、性能评估指标详解2.1 平均精度&#xff08;mAP&#xff09;2.2 每秒帧数&#xff08;FPS&#xff09;2.3 交并比&#xff08;IoU&#xff09;2.4 混淆矩阵&#xff08;Confusion Matrix&#xff09;2.5 F1-Score2.6 PR曲线&#xff08;Precisi…

基于rpcapd与wireshark的远程实时抓包的方法

基于rpcapd与wireshark的远程实时抓包的方法 服务端安装wireshark侧设置 嵌入式设备或服务器上没有图形界面&#xff0c;通常使用tcpdump抓包保存为pcap文件后&#xff0c;导出到本地使用wireshark打开分析&#xff0c;rpcapd可与wireshark配合提供一种远程实时抓包的方案&…

如何从 Hugging Face 数据集中随机采样数据并保存为新的 Arrow 文件

如何从 Hugging Face 数据集中随机采样数据并保存为新的 Arrow 文件 在使用 Hugging Face 的数据集进行模型训练时&#xff0c;有时我们并不需要整个数据集&#xff0c;尤其是当数据集非常大时。为了节省存储空间和提高训练效率&#xff0c;我们可以从数据集中随机采样一部分数…

深入解析 MySQL 启动方式:`systemctl` 与 `mysqld` 的对比与应用

目录 前言1. 使用 systemctl 启动 MySQL1.1 什么是 systemctl1.2 systemctl 启动 MySQL 的方法1.3 应用场景1.4 优缺点优点缺点 2. 使用 mysqld 命令直接启动 MySQL2.1 什么是 mysqld2.2 mysqld 启动 MySQL 的方法2.3 应用场景2.4 优缺点优点缺点 3. 对比分析结语 前言 MySQL …

会议直击|美格智能亮相2024紫光展锐全球合作伙伴大会,融合5G+AI共拓全球市场

11月26日&#xff0c;2024紫光展锐全球合作伙伴大会在上海举办&#xff0c;作为紫光展锐年度盛会&#xff0c;吸引来自全球的众多合作伙伴和行业专家、学者共同参与。美格智能与紫光展锐竭诚合作多年&#xff0c;共同面向5G、AI和卫星通信为代表的前沿科技&#xff0c;聚焦技术…

本地学习axios源码-如何在本地打印axios里面的信息

1. 下载axios到本地 git clone https://github.com/axios/axios.git 2. 下载react项目, 用vite按照提示命令配置一下vite react ts项目 npm create vite my-vue-app --template react 3. 下载koa, 搭建一个axios请求地址的服务端 a.初始化package.json mkdir koa-server…

7、递归

一、概念/理解 递归&#xff1a;某个函数直接或者间接的调用自身。--->函数调用 函数调用&#xff1a;创建副本 递归函数&#xff1a;直接或者间接调用自身的函数叫 递归函数: 边界条件/递归出口&#xff1a;递归调用的终止条件。避免出现死循环或者爆栈的情况。//报错显…

【python】图像、音频、视频等文件数据采集

【python】图像、音频、视频等文件数据采集 先安装所需要的工具一、Tesseract-OCRTesseract-OCR环境变量设置验证是否配置成功示例语言包下载失败 二、ffmpeg验证是否安装成功示例 先安装所需要的工具 一、Tesseract-OCR Tesseract是一个 由HP实验室开发 由Google维护的开源的…

【青牛科技】2K02 电动工具专用调速电路芯片描述

概述&#xff1a; 2K02 是电动工具专用调速电路。内置稳压电路&#xff0c;温度系数好&#xff0c;可以调节输出频率以及占空比的振荡输出&#xff0c;广泛的应用于小型电钻&#xff0c;割草机等工具。 主要特点&#xff1a; ● 电源电压范围宽 ● 占空比可调 ● 温度系数好 …