基于Linux并结合socket网络编程的ftp服务器的实现

news2024/11/25 12:25:31

项目需求

  • 客户端能够通过调用“get”指令,来获取服务器的文件
  • 客户端能通过“server_ls”指令,来获取服务器路径下的文件列表
  • 客户端能通过“server_cd”指令,进入服务器路径下的某文件夹
  • 客户端可以通过“upload”指令,上传自己的本地文件到服务器
  • 客户端可以通过“client_ls”指令,来查看本地路径下的文件列表
  • 客户端可以通过“client_cd”指令,进入本地路径下的某文件夹

预备知识

  • strtok函数,用于字符串的分割(详见:C语言 字符串分割_c语言分割字符串_Genven_Liang的博客-CSDN博客)
  • sprintf函数,用于字符串的拼接(详见:sprintf()函数简要介绍_sprintf函数_做最完美的自己的博客-CSDN博客) 
char s1={'A','B','C'};
sprintf(str,"aaa %.*s",2,s1); //取字符串s1的前2个字符并和“aaa ”拼接
执行完后str字符串为“aaa AB”
  •  chdir函数。system("cd XXX")并不能成功修改当前程序的路径,因为system本质上是fork了一个子进程,在子进程中执行指令,所以cd是在子进程中执行的,和当前程序无关,想要成功修改当前的路径,所以需要使用chdir(XXX),这样就可以修改当前的目录了(详见:在程序里面执行system(“cd /某个目录“),为什么路径切换不成功?_一口Linux的博客-CSDN博客)
  • recv函数,和send函数一组用于网络套接字的收发,和read/write不同,read/write返回的是已读/写的字节数;而send/recv函数返回的是以接受/发送的字节数所以当网络连接关闭时,recv会返回0,所以recv函数可以用于检测网络连接是否关闭(但是客户端似乎还是要使用进程间通讯来退出)
  • find指令,可以用来判断一个文件是否存在,显然也可以直接用system调用(详见:(超详解)Linux系统find命令用法_linux命令find用法详解_是大姚呀的博客-CSDN博客)
  • 使用lseek函数计算文件大小
size = lseek(fd_file, 0L, SEEK_END);

实现思路

get”指令实现思路:

对于客户端:

检测用户是否输入了“get”,若输入之后,就让服务器发送文件过来,并自己创建一个同名文件写入服务器发来的数据

对于服务端:

检测客户端是否发来了“get”,如果是,就检测服务器要哪个文件,并将文件的内容全部读取然后发给客户端

server_ls”指令实现思路:

对于客户端:

啥也不用干,输入指令后等着接受

对于服务端:

检测客户端是否发来了“server_ls”,如果是,就popen执行“ls -l”并读取运行结果,将结果发给客户端

server_cd”指令实现思路:

对于客户端:

啥也不用干,输入指令后等着接受

对于服务端:

测客户端是否发来了“server_cd”,如果是,就检测后面cd的地址,然后调用chdir函数执行这个地址,然后popen执行“pwd”并读取运行结果,将结果发给客户端

upload”指令实现思路:

对于客户端:

检测用户是否输入了“upload”,若输入之后,就检测用户要上传哪个文件,将文件内容全部复制并发送给服务器

对于服务端:

检测客户端是否发来了“upload”,如果是,就检测用户上传的文件名并接受所有文件数据,然后创建一个同名的文件将数据全部写进去

client_ls”指令实现思路:

对于客户端:

检测用户是否输入了“clien_ls”,若输入之后,就调用system函数执行“ls -l”

对于服务端:

啥也不用干

client_cd”指令实现思路:

对于客户端:

检测用户是否输入了“clien_cd”,若输入之后,就检测cd之后的地址,然后调用chdir函数执行这个地址,然后调用system函数执行“pwd”

对于服务端:

啥也不用干

具体代码

 注意,为了接收到带空格的字符串,我使用了fgets函数,但是fgets函数在长度足够的情况下,会将换行符“\n”也读进字符串,所以strlen也会多计算一位,所以服务器使用fgets得到字符串并发送给客户端,客户端接受之后打印就不需要再换行了;反之亦然,服务器的接收端再接受之后也不需要再加换行符了!

切记切记切记!!以下的写法是完全错误的!!!

char *file_name;
char *readbuf = "tyytt";

file_name = readbuf; //错错错错错错错错

原因: 字符串常量本质是一个指针,而变量名相当于首地址,这句话相当于地址的赋值!!所以只要写了这句代码,不管readbuf的值变成了什么样,file_name就会跟着变成什么样,所以字符串的赋值一定要用strcpy!!血与泪的教训,自查了好久...

而使用strcpy则要注意,第一个参数不能是字符串常量!而需要是字符串变量!!

  • 同时,在代码逻辑中,服务端对于flag_upload的置位和对其的判断都在子进程中,所以可以直接用if判断,但是对于客户端,flag_get的置位在负责写的父进程,对flag_get的判断却在负责读的子进程,正如一直强调的,fork之后的存储空间是独立的,所以父进程将flag置位子进程并不知道,所以不能简单的使用flag_get,需要使用进程间的通信来解决!
  • 同样,file_name在服务端可以直接修改,但是客户端也需要使用进程间通信!

在子进程两个while中使用进程间通信就会涉及到上节出现的问题,即负责发数据的父进程写入fifo后,负责接数据的子进程还在阻塞等待服务器的消息,程序无法运行到检测fifo的代码处,解决方法就是让服务器立刻回复一个数据对于file_name的传输,服务器正好要回复file_name对于是否要跳转到get_handler,服务器正好要发送文件的内容。所以在这个代码逻辑中,不需要额外再设置服务器回复,如果在日后遇到了同样的情况,服务器又不需要回复什么,那么记得要随便写一条内容来回复。

server.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

int flag_upload = 0;
int flag_exist = 0;
char *file_name=NULL;

int cmd_handler(int fd, char readbuf[10000])
{
	int fd_file; 判断为get命令后,用来存放即将发送的文件的文件标识符
	int size; //判断为get命令后,用来存放即将发送的文件的大小
	char *file_buf = NULL; //判断为get命令后,用来存放即将发送的文件的内容
	int ret;
	int i = 0;
	char result[4096] = {0}; //用来存放popen执行指令的结果
	FILE *fp; //popen返回的文件流标识符
	char str[10000]; //将读到的数据备份在这里
	char* stat[5] ={NULL,NULL,NULL,NULL,NULL}; //用来存放拆分后的字符串
	char cmd[128]; //用来存放加工后的字符串,比如去除“\n”,比如再加上“./”

	strcpy(str,readbuf); //由于字符串的首地址是字符串的名字,所以此时相当于传入的地址,所有对字符串的操作都会影响它,所以需要进行备份,先备份再对备份的数据进行数据处理就不会影响原数据了

	char* ptr = strtok(str, " "); //通过空格符号分隔字符串
	for(; ptr != NULL; )
	{
		stat[i] = ptr;
		//printf("%s\n", stat[i]);
		i++;
		ptr = strtok(NULL, " ");
	}

	if(strcmp(stat[0],"get")==0){ //如果是get命令
		sprintf(cmd, "find . -name %.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
		fp = popen(cmd,"r"); //运行指令查看文件是否存在
		fread(&result, sizeof(char), sizeof(result), fp);
		pclose(fp);
		if(strlen(result)==0){
			ret = write(fd,"file not exist\n",20); //不存在就发送给客户端来通知
			if(ret == -1){
				perror("write");
				return 1;
			}

			return 1;
		}else{
			sprintf(cmd, "./%.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
			ret = write(fd,cmd,strlen(cmd)); //如果存在,就先将文件名传给客户端
			if(ret == -1){
				perror("write");
				return 1;
			}
		}
		memset(&result,0,sizeof(result));

		//sleep(2);

		sprintf(cmd, "./%.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
		fd_file = open(cmd,O_RDWR); //然后打开这个文件,将内容全部复制
		size = lseek(fd_file, 0L, SEEK_END);
		printf("file size = %d\n",size);
		file_buf = (char *)malloc(sizeof(char)*size + 1);
		lseek(fd_file, 0, SEEK_SET);
		ret = read(fd_file,file_buf,size);
		if(ret == -1){
			perror("read2");
			return 1;
		}
		close(fd_file);
		ret = write(fd,file_buf,size); //将文件的内容发送给客户端
		if(ret == -1){
			perror("write");
			return 1;
		}

	}else if(strcmp(stat[0],"server_cd")==0){ //如果是server_cd命令
		sprintf(cmd, "%.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
		chdir(cmd); //将目录cd到客户端要求的位置
		fp = popen("pwd","r"); //然后执行pwd,将结果发回给客户端
		fread(&result, sizeof(char), 1024, fp);
		pclose(fp);
		ret = write(fd,&result,strlen(result));
		if(ret == -1){
			perror("write");
			return 1;
		}

		memset(&result,0,sizeof(result));

	}else if(strcmp(stat[0],"upload")==0){ //如果是upload命令
		flag_upload = 1; //表示准备upload
		sprintf(cmd, "./%.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
		file_name = cmd; //接收客户端发来的,即将发送过来的文件名
	}else if(strcmp(stat[0],"exist")==0){
		flag_exist = 1;  //和准备upload的标识符配合使用,只有客户端判断用户输入的upload文件存在时,才会发送exist,此时服务端将文件存在的标识符也置1
	}else if(strcmp(stat[0],"server_ls\n")==0){ //如果是server_ls命令
		fp = popen("ls -l","r"); //执行ls-l命令,然后把结果发回客户端
		fread(&result, sizeof(char), 1024, fp);
		pclose(fp);
		ret = write(fd,&result,strlen(result));
		if(ret == -1){
			perror("write");
			return 1;
		}

		memset(&result,0,sizeof(result));
	}else if(strcmp(stat[0],"quit\n")==0){ //如果客户端打出了quit
		ret = write(fd,"Bye\n",4); //立刻回发一个Bye,目的是让客户端取消接收阻塞然后成功从FIFO读取到退出信息
		if(ret == -1){
			perror("write");
			return 1;
		}
	}


	return 0;
}

void upload_handler(char readbuf[10000]) //upload指令如果被最终判断为可以执行,则会调用这个函数来通过客户端发来的文件名和文件内容来创建文件
{
	int fd_file;
	int n_write;
	fd_file = open(file_name,O_RDWR|O_CREAT|O_TRUNC,S_IRWXU);
	n_write = write(fd_file, (char *)readbuf, strlen((char *)readbuf));
	printf("create new file %s, %d bytes have been written\n",file_name, n_write);

	close(fd_file);

}

int main(int argc, char **argv) //main函数
{
	int conn_num = 0;
	int flag = 0;
	int sockfd;
	int conn_sockfd;
	int ret;
	int n_read;
	int n_write;
	int len = sizeof(struct sockaddr_in);
	char readbuf[10000];
	char msg[10000];


	pid_t fork_return;
	pid_t fork_return_1;

	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	memset(&my_addr,0,sizeof(struct sockaddr_in));
	memset(&client_addr,0,sizeof(struct sockaddr_in));

	if(argc != 3){
		printf("param error!\n");
		return 1;
	}

	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}

	//bind
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));//host to net (2 bytes)
	inet_aton(argv[1],&my_addr.sin_addr); //char* format -> net format

	ret = bind(sockfd, (struct sockaddr *)&my_addr, len);
	if(ret == -1){
		perror("bind");
		return 1;
	}else{
		printf("bind success\n");
	}

	//listen
	ret = listen(sockfd,10);
	if(ret == -1){
		perror("listen");
		return 1;
	}else{
		printf("listening...\n");
	}

	while(1){
		//accept
		conn_sockfd = accept(sockfd,(struct sockaddr *)&client_addr,&len);
		if(conn_sockfd == -1){
			perror("accept");
			return 1;
		}else{
			printf("accept success, client IP = %s\n",inet_ntoa(client_addr.sin_addr));

		}

		fork_return = fork();

		if(fork_return > 0){//father keeps waiting for new request
			//do nothing	
		}else if(fork_return < 0){
			perror("fork");
			return 1;
		}else{//son deals with request
			while(1){
				//read
				memset(&readbuf,0,sizeof(readbuf));
				ret = recv(conn_sockfd, &readbuf, sizeof(readbuf), 0);
				if(ret == 0){ //如果recv函数返回0表示连接已经断开
					printf("client has quit\n");
					close(conn_sockfd);
					break;
				}else if(ret == -1){
					perror("recv");
					return 1;
				}

				if(flag_upload == 1 && flag_exist == 1){ //当准备upload的标识位和文件存在的标识位同时置1时,才会进入这段代码
					flag_exist = 0; //立刻将两个标识位重新置0
					flag_upload = 0;
					upload_handler(readbuf); //并执行upload所对应的函数
				}

				cmd_handler(conn_sockfd, readbuf); //对客户端发来的消息进行判断的总函数

				//printf("\nclient: %s",readbuf); //dont need to add"\n"


			}
			exit(2);
		}

	}


	return 0;
}

client.c:

#include <sys/types.h>     
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

int flag_quit = 0;

int cmd_handler(int fd,char msg[10000]) 
{
	int fd_file; //如果判断为upload,则用来存放即将发送的文件的标识符
	int size; //如果判断为upload,则用来存放即将发送的文件的大小
	int fd_fifo; //用来存放fifo的标识符
	char *fifo_msg = "quit";
	char *fifo_msg_get = "get";
	char *file_buf = NULL; //如果判断为upload,则用来存放即将发送的文件的内容
	char result[4096] = {0}; //用来存放popen函数调用指令后的结果
	FILE *fp; //用来存放popen返回的标识符
	int ret;
	int i = 0;
	char str[10000]; //用来存放客户端读取数据的备份,方便进行数据操作
	char* stat[5] ={NULL,NULL,NULL,NULL,NULL}; //用来存放分隔后的字符串
	char cmd[128]; //用来存放进行加工后的字符串

	strcpy(str,msg); //so split will not affect original data,原因详见server.c

	char* ptr = strtok(str, " "); //用空格分隔字符串
	for(; ptr != NULL; )
	{
		stat[i] = ptr;
		//printf("%s\n", stat[i]);
		i++;
		ptr = strtok(NULL, " ");
	}

	if(strcmp(stat[0],"client_ls\n")==0){ //如果是client_ls指令
		system("ls -l"); //执行ls-l
	}else if(strcmp(stat[0],"client_cd")==0){ //如果是client_cd指令
		sprintf(cmd, "%.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
		chdir(cmd); //cd到用户指定的目录
		system("pwd"); //然后执行pwd
	}else if(strcmp(stat[0],"get")==0){ //如果是get指令
		fd_fifo = open("./fifo",O_WRONLY); //阻塞的只写打开fifo,阻塞直到fifo被只读打开
		write(fd_fifo,fifo_msg_get,strlen(fifo_msg_get)); //向fifo中写信息,告诉负责读的子进程:用户调用了get指令
		close(fd_fifo);
	}else if(strcmp(stat[0],"quit\n")==0){ //如果是quit指令
		//printf("quit detected!\n");
		fd_fifo = open("./fifo",O_WRONLY); //阻塞的只写打开fifo,阻塞直到fifo被只读打开
		write(fd_fifo,fifo_msg,strlen(fifo_msg)); //向fifo中写信息,告诉负责读的子进程:用户调用了quit指令
		close(fd_fifo);
		close(fd); //关闭套接字
		wait(NULL); //等待子进程退出
		flag_quit = 1; //将退出标识符置1

	}else if(strcmp(stat[0],"upload")==0){ //如果是upload指令
		sprintf(cmd, "find . -name %.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
		fp = popen(cmd,"r"); //通过调用find查看要上传的文件是否存在
		fread(&result, sizeof(char), sizeof(result), fp);
		pclose(fp);
		if(strlen(result)==0){
			printf("file not exit!\n");
			return 1;
		}else{
			ret = write(fd,"exist",20); //只有文件存在,才向服务器发送exist
			if(ret == -1){
				perror("write");
				return 1;
			}
		}

		memset(&result,0,sizeof(result));

		sprintf(cmd, "./%.*s",(int)(strlen(stat[1])-1),stat[1]); //remove "\n"
		fd_file = open(cmd,O_RDWR); //打开要发送的文件,将内容全部复制,发送给服务器
		size = lseek(fd_file, 0L, SEEK_END);
		printf("file size = %d\n",size);
		file_buf = (char *)malloc(sizeof(char)*size + 1);
		lseek(fd_file, 0, SEEK_SET);
		ret = read(fd_file,file_buf,size);
		if(ret == -1){
			perror("read2");
			return 1;
		}
		close(fd_file);
		sleep(2); //ensure the "flag_upload" is set to 1 in server
		ret = write(fd,file_buf,size);
		if(ret == -1){
			perror("write");
			return 1;
		}

	}

	return 0;
}

void get_handler(char *file_name, char readbuf[10000]) //当确定要执行get命令时,会调用这个函数,通过服务器发来的文件名和文件内容创建文件
{
	int fd_file;
	int n_write;
	fd_file = open(file_name,O_RDWR|O_CREAT|O_TRUNC,S_IRWXU);
	n_write = write(fd_file, (char *)readbuf, strlen((char *)readbuf));
	printf("\ncreate new file %s, %d bytes have been written\n",file_name, n_write);

	close(fd_file);

}



int main(int argc, char **argv) //main函数
{
	int cnt = 0;
	char file_name[20]={0};
	int no_print = 0;
	int sockfd;
	int ret;
	int n_read;
	int n_write;
	char readbuf[10000];
	char msg[10000];

	int fd; //fifo
	char fifo_readbuf[20] = {0};
	int fd_get;


	pid_t fork_return;

	if(argc != 3){
		printf("param error!\n");
		return 1;
	}


	struct sockaddr_in server_addr;
	memset(&server_addr,0,sizeof(struct sockaddr_in));

	//socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return 1;
	}else{
		printf("socket success, sockfd = %d\n",sockfd);
	}

	//connect
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));//host to net (2 bytes)
	inet_aton(argv[1],&server_addr.sin_addr); 
	ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
		perror("connect");
		return 1;
	}else{
		printf("connect success!\n");
	}

	//fifo
	if(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST)
	{
		perror("fifo");
	}

	//fork
	fork_return = fork();

	if(fork_return > 0){//father keeps writing msg
		while(1){
			//write
			memset(&msg,0,sizeof(msg));
			fgets(msg,sizeof(msg),stdin);
			n_write = write(sockfd,&msg,strlen(msg));

			cmd_handler(sockfd,msg); //负责处理并判断用户输入的总函数
			if(flag_quit == 1){ //如果退出标识符被置位
				flag_quit = 0;
				break; //则父进程退出循环,然后退出
			}

			if(n_write == -1){
				perror("write");
				return 1;
			}else{
				//printf("%d bytes msg sent\n",n_write);
			}
		}
	}else if(fork_return < 0){
		perror("fork");
		return 1;
	}else{//son keeps reading 
		while(1){
			fd = open("./fifo",O_RDONLY|O_NONBLOCK); //非阻塞的只读打开FIFO
			lseek(fd, 0, SEEK_SET); //光标移到最前

			read(fd,&fifo_readbuf,20); //从FIFO读取数据
			if(strcmp(fifo_readbuf,"quit")==0){ //如果FIFO中是quit
				exit(1); //则子进程立刻退出
			}else if(cnt == 1){ //如果判断为get指令,服务器将发送两次消息,第一次为文件名,第二次为文件内容,使用cnt和fifo消息的读取来配合,第一次将读来的值赋值给文件名,第二次将读来的值作为文件内容传入get_handler用来创建文件
				get_handler(file_name, readbuf);
				cnt = 0;
				no_print = 0; //将静止打印标识符重新归0
			}else if(strcmp(fifo_readbuf,"get")==0){ //如上一个else if的注释所言,能进入这个循环,说明是get指令后服务器发送的第一次消息
				strcpy(file_name,readbuf); //重要!字符串的赋值要用strcpy!!
				if(strcmp(file_name,"file not exist\n")==0){ //如果服务器第一次发来的消息是文件不存在,则啥都不用干,就当这次get没发生
					//do nothing
				}else{ //此时,将cnt++,使得服务器下一次发送的消息会被准确的判断为文件的内容
					cnt++;
					no_print = 1; //并且使得静止打印的标识符为1,防止在界面中打印文件内容
				}
			}
			memset(&fifo_readbuf,0,sizeof(fifo_readbuf)); //重要,不要忘记

			//read
			memset(&readbuf,0,sizeof(readbuf));
			n_read = read(sockfd,&readbuf,sizeof(readbuf));
			if(ret == -1){
				perror("read1");
				return 1;
			}else if(ret != -1 && no_print == 0){ //只有静止打印标识符为0时,才打印服务器发来的消息,为了防止当get指令生效时,将服务器发来的大量文件内容打在屏幕上影响观感
				printf("\nserver: %s",readbuf); //dont need to add"\n"
			}

		}

	}

	return 0;
}

对于客户端中使用FIFO配合服务器的回复实现get指令的详细说明

实现效果

编译并运行服务端和客户端,建立连接:

server_ls”指令演示

观察server的目录来验证:

 

server_cd”指令演示

client_ls”指令演示

观察client的目录来验证:

client_cd”指令演示

upload”指令演示

客户端:

服务端:

 

再看服务端的目录,可见文件已经被成功上传:

 

也可以用“server_ls” 来验证: 

get”指令演示

客户端:

服务端:

再看客户端的目录,可见文件已经被成功获取:

也可以用“client_ls” 来验证:

“quit”指令演示:

客户端:

服务端:

 

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

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

相关文章

SMB协议详解之-SMB/CIFS/SMB2/NFS/Samba/Netbios/NTLM/Kerberos关系和区别

SMB协议是在windows环境中非常常见的一中协议&#xff0c;在学习SMB协议的过程中经常出现SMB2&#xff0c;CIFS&#xff0c;Samba&#xff0c;Netbios&#xff0c;NTLM&#xff0c;kerberos等概念&#xff0c;如下下图1&#xff0c;那么SMB和这些观念之间的关系是什么呢。 在详…

linux入门到精通-第二章-常用命令和工具

目录 概述命令格式帮助文档内建命令外部命令&#xff08;--help&#xff09;帮助文档查看man查看谁登陆过电脑 文件目录命令创建目录显示目录结构删除目录 文件相关命令ls命令touchcprm删除mv移动命令 文件查看命令cat 文件内容查看命令less 查看文件内容head 从文件头部查看ta…

Spark大数据分析与实战笔记(第一章 Scala语言基础-3)

文章目录 1.3 Scala的数据结构1.3.1 数组数组的遍历数组转换 1.3.2 元组创建元组获取元组中的值拉链操作 1.3.3 集合ListSetMap 1.3 Scala的数据结构 对于每一门编程语言来说&#xff0c;数组&#xff08;Array&#xff09;都是重要的数据结构之一&#xff0c;主要用来存储数据…

C# OpenCvSharp 通道分离

效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp; using OpenCvSharp.Extensions;namespac…

栅栏状蚀刻铜箔上的掺氟碳量子点界面层

引言 由于其卓越的能量密度和长寿命&#xff0c;锂离子电池(LIBs)广泛用于电动设备&#xff0c;如军用无人机、电动车辆和动力辅助服&#xff0c;这些设备需要高重量或体积能量密度。然而&#xff0c;尽管在各种电动装置中使用&#xff0c;它们仍然具有关键的问题&#xff0c;…

达观RPA实战-自定义控件基础

一、应用背景 当标准控件和实际需求相差较大时,可以设计自定义控件。 或者有些通用功能,也可以做成自定义控件调用,如EXCEL文件表头填充 或者是有些标准控件实际运行会出错,无法解决,如FTP下载控件。 二、知识介绍 在开始开发自定义控件前,先简要介绍几个相关知识: …

C#,数值计算——Midsqu的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Midsqu : Midpnt { private double borig { get; set; } 0.0; public new double func(double x) { return 2.0 * x * funk.funk(borig - x * x); } pub…

Mybatis 动态SQL – 使用if,set标签动态生成更新语句

上一篇我们介绍了使用Mybatis提供的if,where标签动态生成条件语句&#xff1b;本篇我们在上一篇的基础上介绍如何使用Mybatis提供的if,set标签动态生成更新语句。 如果您对if,where标签动态生成条件语句不太了解&#xff0c;建议您先进行了解后再阅读本篇&#xff0c;可以参考…

前端实现动态路由(后端返回权限路由)

实现思路 1、前端定义静态路由&#xff08;login登录页这种不需要权限的默认路由&#xff09; 2、用户登陆时调接口获取用户信息&#xff0c;然后登录到首页 3、前后端定义好路由返回的格式 4、在路由导航钩子beforeEach中去调接口获取动态路由&#xff0c;递归处理该数据为前…

AtCoder Beginner Contest 315 Ex. Typical Convolution Problem(分治NTT/全在线卷积)

题目 给定长为n(n<2e5)的序列a&#xff0c;第i个数ai(0<ai<998244353) 求序列f&#xff0c;满足式子如下&#xff1a; 思路来源 jiangly代码/力扣群友tdzl2003/propane/自己的乱搞 题解 分治NTT&#xff0c;考虑[l,mid]对[mid1,r]的贡献&#xff0c; 但是&#x…

RT-Thread I/O设备模型(二)

访问I/O设备 应用程序通过I/O设备管理接口来访问硬件设备&#xff0c;当设备驱动程序实现后&#xff0c;应用程序就可以访问该硬件。I/O设备管理接口与I/O设备的操作方法映射关系如下&#xff1a; 查找设备 应用程序根据设备名称获取设备句柄&#xff0c;进而操作设备。 rt_…

MybatisPlus基本配置查询操作

无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;如丝般顺滑损耗小&#xff1a;启动即会自动注入基本 CURD&#xff0c;性能基本无损耗&#xff0c;直接面向对象操作强大的 CRUD 操作&#xff1a;内置通用 Mapper、通用 Service&#…

手把手教你用Vite构建第一个Vue3项目

写在前面 在之前的文章中写过“如何创建第一个vue项目”&#xff0c;但那篇文章写的是创建vue2的 项目。 传送门如何创建第一个vue项目 打开Vue.js官网:https://cn.vuejs.org/&#xff0c;我们会发现Vue 2 将于 2023 年 12 月 31 日停止维护 虽然Vue2的项目还不少&#xff0…

day5 qt

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);timer_idthis->startTimer(100);//啓動一個定時器 每100ms發送一次信號ui->Edit1->setPlaceholderTex…

Leetcode:349. 两个数组的交集【题解超详细】

题目 给定两个数组 nums1 和 nums2 &#xff0c;返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 难度&#xff1a;简单 题目链接&#xff1a;349.两个数组的交集 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,2,1], nums2 [2,…

CentOS7上源码安装Redis6

CentOS7上源码安装Redis6 安装依赖开始安装下载安装包解压安装包编译源代码修改配置文件 启动并连接启动redis服务器本地连接远程连接 下篇预告 安装依赖 首先我们是源码包安装我们需要安装C语言编译器&#xff0c;顺便下载wget&#xff1a; yum install -y gcc wget开始安装…

Ab3d.PowerToys 11.0.8614 Crack

版本 11.0.8614 修补程序 使用 MouseCameraController 移动相机时防止旋转 FreeCamera。 版本 11.0.8585 重大更改&#xff1a;由于专利问题删除了 ViewCubeCameraController - 请联系支持人员以获取更多信息以及如果您想继续使用此控件。添加了 CameraNavigationCircles 控件…

第12节——生命周期

一、概念 生命周期指 React 组件从装载至卸载的全过程&#xff0c;这个过程内置多个函数供开发者在组件的不同阶段执行需要的逻辑。 状态组件主要通过 3 个生命周期阶段来管理&#xff0c;分别是 挂载阶段&#xff08;MOUNTING&#xff09;&#xff0c;更新阶段&#xff08;U…

AJAX学习笔记3练习

AJAX学习笔记2发送Post请求_biubiubiu0706的博客-CSDN博客 1.验证用户名是否可用 需求,用户输入用户名,失去焦点-->onblur失去焦点事件,发送AJAX POST请求,验证用户名是否可用 新建表 前端页面 WEB-INF下新建lib包引入依赖,要用JDBC 后端代码 package com.web;import jav…

ModaHub魔搭社区:自动化机器学习框架AutoML

AutoML 自动化机器学习AutoML 是机器学习中一个相对较新的领域,它主要将机器学习中所有耗时过程自动化,如数据预处理、最佳算法选择、超参数调整等,这样可节约大量时间在建立机器学习模型过程中。 自动机器学习 AutoML: 对于 ,令 表示特征向量, 表示对应的目标值。给定…