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

项目需求

  • 客户端能够通过调用“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; //表示准备uploadsprintf(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){ //如果客户端打出了quitret = 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;}//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//bindmy_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 formatret = bind(sockfd, (struct sockaddr *)&my_addr, len);if(ret == -1){perror("bind");return 1;}else{printf("bind success\n");}//listenret = listen(sockfd,10);if(ret == -1){perror("listen");return 1;}else{printf("listening...\n");}while(1){//acceptconn_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 requestwhile(1){//readmemset(&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; //立刻将两个标识位重新置0flag_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.cchar* 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); //只有文件存在,才向服务器发送existif(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 serverret = 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; //fifochar 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));//socketsockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd == -1){perror("socket");return 1;}else{printf("socket success, sockfd = %d\n",sockfd);}//connectserver_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");}//fifoif(mkfifo("./fifo",S_IRWXU) == -1 && errno != EEXIST){perror("fifo");}//forkfork_return = fork();if(fork_return > 0){//father keeps writing msgwhile(1){//writememset(&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); //非阻塞的只读打开FIFOlseek(fd, 0, SEEK_SET); //光标移到最前read(fd,&fifo_readbuf,20); //从FIFO读取数据if(strcmp(fifo_readbuf,"quit")==0){ //如果FIFO中是quitexit(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)); //重要,不要忘记//readmemset(&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.mzph.cn/news/74124.shtml

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

相关文章

Redis发布订阅机制学习

【IT老齐151】Redis发布订阅机制是如何实现的&#xff1f;_哔哩哔哩_bilibili go-redis的发布与订阅 - 知乎 (zhihu.com) 前置&#xff1a; 先输入 redis-server.exe 启动redis&#xff0c;否则对应接口不开放 再输入 redis-cli 命令启动客户端服务 1.机制示意图 当一…

AR产业变革中的“关键先生”和“关键力量”

今年6月的WWDC大会上&#xff0c;苹果发布了头显产品Vision Pro&#xff0c;苹果CEO库克形容它&#xff1a; 开启了空间计算时代。 AR产业曾红极一时&#xff0c;但因为一些技术硬伤又减弱了声量&#xff0c;整个产业在起伏中前行。必须承认&#xff0c;这次苹果发布Vision P…

七大排序算法

目录 直接插入排序 希尔排序 直接选择排序 堆排序 冒泡排序 快速排序 快速排序优化 非递归实现快速排序 归并排序 非递归的归并排序 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作. 常见的排序算法有插入排序(直接插入…

队列(Queue)的顶级理解

目录 1.队列(Queue) 的概念 2.单链表模拟实现队列 2.1创建队列 2.2入队列 2.3判断是否为空 2.4出队列 2.5获取队头元素 2.6完整代码&#xff1a; 2.7双向链表模拟实现队列代码 3.数组模拟实现队列代码 3.1创建队列 3.2判断是否为满 3.3检查是否为空 3.4插入元素 3…

ctfshow 反序列化

PHP反序列化前置知识 序列化和反序列化 对象是不能在字节流中传输的&#xff0c;序列化就是把对象转化为字符串以便存储和传输&#xff0c;反序列化就是将字符串转化为对象 魔术方法 __construct() //构造&#xff0c;当对象new时调用 __wakeup() //执行unserialize()时&am…

mysql 增量备份与恢复使用详解

目录 一、前言 二、数据备份策略 2.1 全备 2.2 增量备份 2.3 差异备份 三、mysql 增量备份概述 3.1 增量备份实现原理 3.1.1 基于日志的增量备份 3.1.2 基于时间戳的增量备份 3.2 增量备份常用实现方式 3.2.1 基于mysqldump增量备份 3.2.2 基于第三方备份工具进行增…

【QT】QMessageBox消息框的使用(16)

在实际项目中&#xff0c;弹出消息框是一个很常见的操作&#xff0c;包含错误信息提示、警告信息提示、关于信息提示、还包括判断信息选择等操作&#xff0c;那么今天通过这一节来好好了解下消息框的使用方法。 一.环境配置 1.python 3.7.8 可直接进入官网下载安装&#xf…

前端基础5——UI框架Layui

文章目录 一、基本使用二、管理后台布局2.1 导航栏2.2 主题颜色2.3 字体图标 三、栅格系统四、卡片面板五、面包屑六、按钮七、表单八、上传文件九、数据表格9.1 table模块常用参数9.2 创建表格9.3 表格分页9.4 表格工具栏9.5 表格查询9.5.1 搜索关键字查询9.5.2 选择框查询 9.…

记录窗体关闭位置(从窗体上次关闭的位置启动窗体)

从上次关闭位置启动窗体 基础类 using Microsoft.Win32; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Linq;namespace WindowsFormsApp1 {public class Reg{public static voi…

Mac电脑怎么使用NTFS磁盘管理器 NTFS磁盘详细使用教程

Mac是可以识别NTFS硬盘的&#xff0c;但是macOS系统虽然能够正确识别NTFS硬盘&#xff0c;但只支持读取&#xff0c;不支持写入。换句话说&#xff0c;Mac不支持对NTFS硬盘进行编辑、创建、删除等写入操作&#xff0c;比如将Mac里的文件拖入NTFS硬盘&#xff0c;在NTFS硬盘里新…

BGP路由属性

任何一条BGP路由都拥有多个路径属性&#xff08;Path Attributes&#xff09;&#xff0c;当路由器通告BGP路由给它的对等体时&#xff0c;该路由将会携带多个路径属性&#xff0c;这些属性描述了BGP路由的各项特征&#xff0c;同时在某些场景下也会影响BGP路由优选的决策。 一…

cudnn-windows-x86_64-8.6.0.163_cuda11-archive 下载

网址不太好访问的话,请从下面我提供的分享下载 Download cuDNN v8.6.0 (October 3rd, 2022), for CUDA 11.x 此资源适配 cuda11.x 将bin和include文件夹里的文件&#xff0c;分别复制到C盘安装CUDA目录的对应文件夹里 安装cuda时自动设置了 CUDA_PATH_V11_8 及path C:\Progra…

jvs-智能bi(自助式数据分析)9.1更新内容

​jvs-智能bi更新功能 1.报表增加权限功能&#xff08;服务、模板、数据集、数据源可进行后台权限分配&#xff09; 每个报表可以独立设置权限&#xff0c;通过自定义分配&#xff0c;给不同的人员分配不同的权限。 2.报表新增执行模式 可选择首次报表加载数据为最新数据和历…

怎样把英语视频字幕翻译成中文

我们知道&#xff0c;随着中外文化交流日益频繁&#xff0c;视频翻译作为一种重要的跨文化交流方式&#xff0c;也越来越受到重视。那么&#xff0c;怎样把英语视频翻译成中文&#xff0c;北京视频翻译哪里比较专业&#xff1f; 据了解&#xff0c;视频翻译是直接将一种语言的音…

报考浙江工业大学MBA项目如何选择合适的辅导班?

浙江工业大学MBA项目每年有数百人报考&#xff0c;在浙江省内除了浙大以外算是人数比较多的一个项目。2023级的招生中第一志愿也通过复试刷掉了百来人&#xff0c;在省内其实作为第一志愿报考的风险在逐渐增大&#xff0c;考生们如果坚持报考&#xff0c;则在针对联考初试的备考…

deepin 如何卸载软件

文章目录 卸载软件&#xff08;正文&#xff09; 通常来讲在官方的应用商场卸载即可。 但是呢&#xff1f; 很不幸的是&#xff0c;没能够彻底删除软件。还是能够在启动器界面上看到应用。 这时候&#xff0c;你右键卸载&#xff0c;会提示“卸载失败”。如下图&#xff1a; …

SQL Server2022安装教程

SQL Server 是一个关系数据库管理系统。它最初是由Microsoft、Sybase 和Ashton-Tate三家公司共同开发的&#xff0c;于1988 年推出了第一个OS/2版本。在Windows NT 推出后&#xff0c;Microsoft与Sybase 在SQL Server 的开发上就分道扬镳了&#xff0c;Microsoft 将SQL Server移…

滑动窗口的最大值(双端队列,单调队列)

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 class Solution {public int[] maxSlidingWindow(int[] nums, int k) {LinkedList<Integer> deque new LinkedList<>();//双端队列&#xff0c;存储单调队列的下标int ans[] new int[nu…

公开游戏、基于有向图的游戏

目录 〇&#xff0c;背景 一&#xff0c;公开游戏、策梅洛定理 1&#xff0c;公开游戏 2&#xff0c;策梅洛定理 二&#xff0c;有向图游戏 1&#xff0c;狭义有向图游戏 2&#xff0c;广义有向图游戏 3&#xff0c;狭义有向图游戏的SG数 4&#xff0c;Bash Game 力扣…

LVGL Animations(动画)的简单使用

一、前言 哈喽&#xff0c;大家好。在进行界面设计的时候&#xff0c;动画的使用是必不可少的&#xff0c;今天这篇文章就跟大家分享一下 LVGL Animations&#xff08;动画&#xff09;的简单使用。笔者将在模拟器上运行演示&#xff0c;LVGL 版本号为 8.3.0。 二、Animation…