当前位置: 首页 > news >正文

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 服务端进行扩展和优化,以满足不同的应用场景。

相关文章:

LWIP和FATFS 实现 FTP 服务端

目录 一、前言 二、LWIP 和 FTP 简介 1.LWIP 2.FTP 三、实现 FTP 服务端的主要步骤 1.初始化 LWIP 2.创建 FTP 服务器任务 3.处理客户端连接 4.实现 FTP 命令处理 5.文件系统操作 6.错误处理和日志记录 四、示例代码 1.创建FTP任务 2. FTP任务代码 3.处理交互数据…...

缓冲区的奥秘:解析数据交错的魔法

目录 一、理解缓存区的好处 &#xff08;一&#xff09;直观性的理解 &#xff08;二&#xff09;缓存区的好处 二、经典案例分析体会 &#xff08;一&#xff09;文件读写流&#xff08;File I/O Buffering&#xff09; BufferedOutputStream 和 BufferedWriter 可以加快…...

【MySQL — 数据库基础】MySQL的安装与配置 & 数据库简单介绍

数据库基础 本节目标 掌握关系型数据库&#xff0c;数据库的作用掌握在Windows和Linux系统下安装MySQL数据库了解客户端工具的基本使用和SQL分类了解MySQL架构和存储引擎 1. 数据库的安装与配置 1.1 确认MYSQL版本 处理无法在 cmd 中使用 mysql 命令的情况&a…...

如何使用Python解析从淘宝API接口获取到的JSON数据?

基本的 JSON 解析 当从淘宝 API 接口获取到数据后&#xff08;假设数据存储在变量response_data中&#xff09;&#xff0c;首先要判断数据类型是否为 JSON。如果是&#xff0c;就可以使用 Python 内置的json模块进行解析。示例代码如下&#xff1a; import json # 假设respon…...

Day1 生信新手笔记

生信新手笔记 生信学习第一天笔记打卡。 转录组学中&#xff1a; 上游分析-基于linux&#xff0c;包括质控、过滤、比对、定量&#xff1b; 下游分析-基于R语言&#xff0c;包括差异分析、富集分析、可视化。 1. 级别标题 一个井号加空格 就是一级标题&#xff0c;两个井号加…...

内网穿透步骤

步骤 第一次需要验证token window和linux的方法不同。 然后 启动 cpolar 服务&#xff1a; 在命令窗口中输入 cpolar.exe htttp 8080&#xff0c;启动内网穿透服务。确保命令窗口保持开启状态&#xff0c;以维持穿透效果。 cpolar.exe hhttp 8080 成功后 注意事项 命令窗口…...

docker启动容器,语句名词解释

#启动容器代码docker run -it -d --name dev_aios -v D:\project\aialign:/www/ -v D:\project\data\dev\aios:/myfile/data/dev/aios -w /www/stand-alone-aios/aios -p 9002:9000 --ulimit core0 aialign/python-base:1.0 bash名词解释 docker run: 这是 Docker 的命令&#…...

微服务之短信验证服务配置完后junit单元测试短信发送失败

总之岁月漫长&#xff0c;然而值得等待。 主要是版本冲突问题&#xff0c;具体报错与解决方法如下&#xff1a; 报错前&#xff1a; 启动失败 短信服务测试报错&#xff1a; 解决后&#xff1a; 启动成功 短信服务测试发送成功&#xff1a; 在使用 SpringBoot 开发时&am…...

QSqlTableModel的使用

实例功能 这边使用一个实例显示数据库 demodb 中 employee 数据表的内容&#xff0c;实现编辑、插入、删除的操作&#xff0c;实现数据的排序和记录过滤&#xff0c;还实现 BLOB 类型字段 Photo 中存储照片的显示、导入等操作&#xff0c;运行界面如下图&#xff1a; 在上图中…...

构建高可用系统设计OpenStack、Docker、Mesos和Kubernetes(简称K8s)

如果构建高可用、高并发、高效运维的大型系统 大型系统架构设计包括业务层设计、服务层设计、基础架层设计、存储层设计、网络层协同设计来完成。 一、业务层 根据主要业务范畴的分类和特征提取&#xff0c;抽象出独立的业务系统&#xff0c;分别统计系统的用户角色群体、访…...

CondaValueError: Malformed version string ‘~‘: invalid character(s).

问题描述&#xff1a;在window下使用conda安装任何包都会报错。报错信息是CondaValueError: Malformed version string ~: invalid character(s). 解决办法&#xff1a;把.condarc文件的源地址删除&#xff08;八成是源地址访问不了了&#xff09;&#xff0c;只保存默认的&am…...

uniapp图片上传预览uni.chooseImage、uni.previewImage

文章目录 1.上传图片2.预览图片 1.上传图片 uni.chooseImage(OBJECT) 从本地相册选择图片或使用相机拍照。 App端如需要更丰富的相机拍照API&#xff08;如直接调用前置摄像头&#xff09;&#xff0c;参考plus.camera 微信小程序从基础库 2.21.0 开始&#xff0c; wx.choos…...

代码随想录算法训练营第三十二天 | 509. 斐波那契数 | 70. 爬楼梯 | 746. 使用最小花费爬楼梯

Day 32 总结 自己实现中遇到哪些困难今日收获&#xff0c;记录一下自己的学习时间 12:30 - 21:30 理论基础 代码随想录 动态规划 问题有很多的重叠子问题需要解决&#xff0c;状态涉及推导 DP 5部曲 DP数组含义 状态转移公式 dp数组初始化 数组遍历顺序 举例推导 视频&a…...

Flink常见面试题

1、Flink 的四大特征&#xff08;基石&#xff09; 2、Flink 中都有哪些 Source&#xff0c;哪些 Sink&#xff0c;哪些算子&#xff08;方法&#xff09; 预定义Source 基于本地集合的source&#xff08;Collection-based-source&#xff09; 基于文件的source&#xff08;…...

SpringCloud之Config:从基础到高级应用

目录 一、SpringCloud Config 简介1、SpringCloud Config 概述&#xff08;1&#xff09;核心概念&#xff08;2&#xff09;SpringCloud Config 的特点&#xff08;3&#xff09;应用场景&#xff08;4&#xff09;工作原理&#xff08;5&#xff09;优势&#xff08;6&#x…...

redis 底层数据结构

概述 Redis 6 和 Redis 7 之间对比&#xff1a; Redis6 和 Redis7 最大的区别就在于 Redis7 已经用 listpack 替代了 ziplist. 以下是基于 Redis 7基础分析。 RedisObject Redis是⼀个<k,v>型的数据库&#xff0c;其中key通常都是string类型的字符串对象&#xff0c;⽽…...

机器学习之RLHF(人类反馈强化学习)

RLHF(Reinforcement Learning with Human Feedback,基于人类反馈的强化学习) 是一种结合人类反馈和强化学习(RL)技术的算法,旨在通过人类的评价和偏好优化智能体的行为,使其更符合人类期望。这种方法近年来在大规模语言模型(如 OpenAI 的 GPT 系列)训练中取得了显著成…...

openwrt利用nftables在校园网环境下开启nat6 (ipv6 nat)

年初写过一篇openwrt在校园网环境下开启ipv6 nat的文章&#xff0c;利用ip6tables控制ipv6的流量。然而从OpenWrt22版本开始&#xff0c;系统内置的防火墙变为nftables&#xff0c;因此配置方法有所改变。本文主要参考了OpenWRT使用nftables实现IPv6 NAT 这篇文章。 友情提示 …...

Vue3+node.js实现注册

文章目录 前端代码实现后端代码实现 效果图 前端代码实现 <template><div class"register-container"><el-card class"register-card"><template #header><div class"card-header"><span>注册</span&…...

LabVIEW将TXT文本转换为CSV格式(多行多列)

在LabVIEW中&#xff0c;将TXT格式的文本文件内容转换为Excel格式&#xff08;即CSV文件&#xff09;是一项常见的数据处理任务&#xff0c;适用于将以制表符、空格或其他分隔符分隔的数据格式化为可用于电子表格分析的形式。以下是将TXT文件转换为Excel&#xff08;CSV&#x…...

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…...

【笔记】软技能

硬技能&#xff1a;操控世界的能力&#xff0c;处理对象为【物】。软技能&#xff1a;影响他人的能力&#xff0c;处理对象为【人】。软技能包括一个人的情商、个性、社交礼仪、沟通、语言、个人习惯&#xff0c;还有解决问题的能力、领导能力、时间管理能力等一切非技术能力。…...

试题转excel;试题整理工具;试卷转excel;word转excel

一、问题描述 我父亲是一名教师&#xff0c;偶尔会需要将试卷转excel&#xff0c;方便管理处理一些特别重要的题目 于是&#xff0c;就抽空写一个专门将试题转excel的工具&#xff0c;便于各位教师从业者和教育行业的朋友更好的整理试题&#xff0c;减少一点重复枯燥的工作 …...

【热门主题】000072 分布式数据库:开启数据管理新纪元

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…...

常见靶场的搭建

漏洞靶场 渗透测试&#xff08;漏洞挖掘&#xff09;切忌纸上谈兵&#xff0c;学习渗透测试&#xff08;漏洞挖掘&#xff09;知识的过程中&#xff0c;我们通常需要一个包含漏洞的测试环境来进行训练。而在非授权情况下&#xff0c;对于网站进行渗透测试攻击&#xff0c;是触及…...

C语言——链表

1 链表基础 1 什么是链表 &#xff01;&#xff01;&#xff01;链表相当于多个结构体变量链接在一起&#xff01;&#xff01;&#xff01; //链表节点结构 struct Node //数据域和指针域 {int data; //数据域//struct Student data;数据尽量不写在链表结构体里面&#xf…...

【经典】星空主题的注册界面HTML,CSS,JS

目录 界面展示 完整代码 说明&#xff1a; 这是一个简单的星空主题的注册界面&#xff0c;使用了 HTML 和 CSS 来实现一个背景为星空效果的注册页面。 界面展示 完整代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8&…...

94.【C语言】解析预处理(2)

目录 1.带有副作用的宏参数 代码 一个判断最大值的宏代码 运行结果 分析 "副作用"的解释 2.宏替换规则 解释 3.宏和函数的对比 附一张对比表 承接93.【C语言】解析预处理(1)文章 1.带有副作用的宏参数 代码 一个判断最大值的宏代码 #define MAX(a, b) (…...

(数据结构与算法)如何提高学习算法的效率?面试算法重点有哪些?面试需要哪些能力?

面试官眼中的求职者 通过对你算法的考察&#xff01;&#xff01;&#xff01;&#xff01; 缩进太多&#xff01;&#xff01;一般不要超过三层&#xff01;&#xff01;&#xff01;缩进越少&#xff0c;bug越少&#xff1b;逻辑比较复杂&#xff0c;把这些包装成为函数&…...

STM32 BootLoader 刷新项目 (十二) Option Byte之FLASH_OPTCR-命令0x58

STM32 BootLoader 刷新项目 (十二) Option Byte之FLASH_OPTCR-命令0x58 STM32F407芯片的OPTION Byte全面解析 STM32F407芯片是STMicroelectronics推出的一款功能强大的微控制器&#xff0c;广泛应用于工业控制、通信和消费电子等领域。其中&#xff0c;OPTION Byte&#xff0…...

MySQL主从复制

华子目录 实验环境准备修改配置文件 实验主开启bin-log日志创建用于主从复制的用户master配置salve配置 测试 添加一台新的slave&#xff0c;如何实现数据的全部同步呢什么环境下主比较多&#xff0c;什么环境下从比较多&#xff1f;延迟复制测试 并行复制gtid模式未启用gtid时…...

贝叶斯统计:高斯分布均值μ的后验分布推导

使用贝叶斯统计方法 均值 ( μ \mu μ ) 的后验分布解析 在贝叶斯统计中&#xff0c;后验分布表示在观察到数据后&#xff0c;对参数的更新后的信念。本篇文章将结合高斯分布的假设&#xff0c;详细解析均值 ( μ \mu μ ) 的后验分布推导过程&#xff0c;并对 ( μ \mu μ…...

linux常用指令都是工作中遇到的

前端打war包 jar -cvf dist.war *创建 WAR 文件的命令 jar -cvf dist.war * 用于将当前目录下的所有文件和文件夹打包成一个名为 dist.war 的 WAR 文件。以下是该命令的详细解释&#xff1a; jar: Java Archive 工具&#xff0c;用于创建 JAR、WAR 或 EAR 文件。 -c: 创建新的…...

亚马逊自研大语言模型 Olympus 即将亮相,或将在 LLM 竞赛中掀起新波澜

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

droppath

DropPath 是一种用于正则化深度学习模型的技术&#xff0c;它在训练过程中随机丢弃路径&#xff08;或者说随机让某些部分的输出变为零&#xff09;&#xff0c;从而增强模型的鲁棒性和泛化能力。 代码解释&#xff1a; import torch import torch.nn as nn # 定义 DropPath…...

通信与网络安全之IPSEC

IPSec&#xff08;IP Security&#xff09;是IETF制定的为保证在Internet上传送数据的安全保密性能的三层隧道加密协议。IPSec在网络层对IP报文提供安全服务。IPSec协议本身定义了如何在IP数据包中增加字段来保证IP包的完整性、 私有性和真实性&#xff0c;以及如何加密数据包。…...

Linux内核编译流程(Ubuntu24.04+Linux Kernel 6.8.12)

万恶的拯救者&#xff0c;使用Ubuntu没有声音&#xff0c;必须要自己修改一下Linux内核中的相关驱动逻辑才可以&#xff0c;所以被迫学习怎么修改内核&编译内核&#xff0c;记录如下 准备工作 下载Linux源码&#xff1a;在Linux发布页下载并使用gpg签名验证 即&#xff1a…...

什么是GAN?

一、基本概念 生成对抗网络&#xff08;Generative Adversarial Network&#xff0c;GAN&#xff09;是一种由两个神经网络共同组成深度学习模型&#xff1a;生成器&#xff08;Generator&#xff09;和判别器&#xff08;Discriminator&#xff09;。这两个网络通过对抗的方式…...

torch_geometric使用手册-Creating Graph Datasets(专题四)

虽然 PyG 已经提供了很多有用的数据集,但你可能希望创建自己的数据集,使用自己收集的数据或非公开的数据。 自己实现数据集是非常简单的,你可能想查看源代码,了解各种数据集是如何实现的。然而,这里简要介绍一下如何设置你自己的数据集。 我们提供了两个抽象类用于数据集…...

编程语言之Python

Python是一种高级编程语言&#xff0c;自其诞生以来&#xff0c;便因其简洁明了的语法、强大的功能和广泛的应用领域而备受青睐。以下是对Python的详细介绍&#xff0c;包括其历史、特点、应用领域&#xff0c;以及通过代码示例展示其语法、库、函数等关键概念。同时&#xff0…...

【Go】-调度器简介

目录 数据结构 G M P 调度器启动 创建 Goroutine 初始化结构体 运行队列 调度信息 调度循环 小结 数据结构 Go的运行时调度器的三个重要组成部分 — 线程 M、Goroutine G 和处理器 P&#xff1a; 图 6-29 Go 语言调度器 G — 表示 Goroutine&#xff0c;它是一个待…...

在Ubuntu 22.04上设置Python 3的Jupyter Notebook

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 简介 Jupyter Notebook 是一个作为 Web 应用程序的交互式计算命令 shell。该工具可与多种语言一起使用&#xff0c;包括 Python、Julia…...

通讯专题4.1——CAN通信之计算机网络与现场总线

从通讯专题4开始&#xff0c;来学习CAN总线的内容。 为了更好的学习CAN&#xff0c;先从计算机网络与现场总线开始了解。 1 计算机网络体系的结构 在我们生活当中&#xff0c;有许多的网络&#xff0c;如交通网&#xff08;铁路、公路等&#xff09;、通信网&#xff08;电信、…...

将jar包导入maven

1.将jar包放repository 2.执行命令&#xff1a;mvn install:install-file -DgroupIdcom.oracle -DartifactIdojdbc7 -Dversion12.1.0.2 -Dpackagingjar -DfileD:\dev\utils\idea\repository\ojdbc7.jar -Dfile: 指定要安装的JAR文件的路径。 -DgroupId: 指定项目的groupId。 -…...

Mysql实现定时自动备份(Windows环境)

一.新建数据库备份目录 二.新建批处理文件 创建批处理文件mysql_backup.bat echo off set BACKUP_DIRD:\backup set MYSQL_USERroot set MYSQL_PASS123456 set MYSQL_HOSTlocalhost set DATABASE_NAMEphoenix set DATE%date:~0,4%-%date:~5,2%-%date:~8,2%_%time:~0,2%-%time…...

kafka数据在服务端时怎么写入的

学习背景 接着上篇&#xff0c;我们来聊聊kafka数据在服务端怎么写入的 服务端写入 在介绍服务端的写流程之前&#xff0c;我们先要理解服务端的几个角色之间的关系。 假设我们有一个由3个broker组成的kafka集群&#xff0c;我们在这个集群上创建一个topic叫做shitu-topic&…...

2024算法基础公选课练习七(BFS1)

一、前言 还是偏基础的bfs&#xff0c;但是有几个题不是很好写 二、题目总览 三、具体题目 3.1 问题 A: 数据结构-队列-奇怪的电梯 我的代码 可以看成求一维平面的bfs最短路 #include <bits/stdc.h> using i64 long long; using pii std::pair<int,int>; co…...

算法刷题Day1

BM47 寻找第k大 第一天就随便记录吧&#xff0c;万事开头难&#xff0c;我好不容易开的头&#xff0c;就别难为自己&#xff0c;去追求高质量了。嘿嘿嘿 题目 传送门 解题思路一&#xff1a;维护一个大小为k的最小堆。最后返回堆顶元素。 代码&#xff1a; # # 代码中的类名…...

你还没有将 Siri 接入GPT对话功能吗?

由于各种原因&#xff0c;国内ios用户目前无缘自带 AI 功能&#xff0c;但是这并不代表国内 ios 无法接入 AI 功能&#xff0c;接下来手把手带你为iPhone siri 接入 gpt 对话功能。 siri 接入 chatGPT 暂时还无法下载 ChatGPT app&#xff0c;或者没有账号的读者可以直接跳到…...

LabVIEW 标准状态机设计模式

LabVIEW 标准状态机设计模式 LabVIEW 软件框架介绍LabVIEW编程模式及其应用分析状态机模式的类型分析标准状态机设计模式及状态机应用学习顺序结构它的一个缺点是什么&#xff1f; 状态机结构 LabVIEW 软件框架介绍 源于虚拟仪器技术的LabVIEW程序设计语言&#xff0c;从被创建…...