基于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.hqwc.cn/news/99361.html

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

相关文章

从0到1实现播放控制器

这系列文章主要讲诉如何从0到1使用QT实现带时间显示、滚动字幕等的自定义配置视频播放控制器。平时我们乘坐地铁经常看到各条线的播放控制器都大同小异。其实都是通过QT等界面开发软件来实现的。 在具体开发之前&#xff0c;需要明确我们需要做什么&#xff1f; 1. 开发一个可…

uni-app开发小程序,radio单选按钮,点击可以选中,再次点击可以取消

一、实现效果&#xff1a; 二、代码实现&#xff1a; 不适用官方的change方法&#xff0c;自己定义点击方法。 动态判断定义的值是否等于遍历的值进行回显&#xff0c;如果和上一次点击的值一样&#xff0c;就把定义的值改为null <template><view><radio-group&…

【广州华锐互动】利用AR远程指导系统进行机械故障排查,实现远程虚拟信息互动

随着工业自动化和智能化的不断发展&#xff0c;机械故障诊断已经成为了工业生产中的重要环节。为了提高故障诊断的准确性和效率&#xff0c;近年来&#xff0c;AR&#xff08;增强现实&#xff09;远程协助技术逐渐应用于机械故障诊断领域。本文将探讨AR远程协助技术在机械故障…

docker打包vue vite前端项目

打包vue vite 前端项目 1.打包时将测试删除 2.修改配置 3.打包项目 npm run build 显示成功&#xff08;黄的也不知道是啥&#xff09; 打包好的前端文件放入 4.配置 default.conf upstream wms-app {server 你自己的ip加端口 ;server 192.168.xx.xx:8080 ; } server { …

mysql数据库使用技巧整理

查看当前数据库已建立的client连接 mysql中执行 -- 查看数据库允许的最大连接数&#xff0c;不是实时正在使用的连接数 SHOW VARIABLES LIKE max_connections; mysql中执行 -- 查看当前数据库client的连接数 SHOW STATUS LIKE Threads_connected; mysql中执行 -- 查看具…

Linux中安装MySQL5.7.42

1. 首先&#xff0c;下载mysql5.7.42的安装包&#xff08;下方是下载地址&#xff09;&#xff0c;选择红色框框的下载&#xff08;注意的是&#xff0c;这个链接只提供5.7的版本下载&#xff0c;可能还会更新&#xff0c;不一定打开就是5.7.42的版本&#xff0c;后续可能会有4…

LeetCode 热题 100——找到字符串中所有字母异位词(滑动窗口)

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 该题目的意思简而言之就是说&#xff0c;从s字符串中寻找与p字符串含有相同字符(次数和种类均相同)的子串&#xff0c;并且将他们的首字符下标集合进数组中进行返回。 滑动窗口解…

AndroidTV端:酒店扫码认证投屏DLNA

被老板叼了几次了&#xff0c;最近实在忍不了&#xff0c;准备离职&#xff1b; 但是担心离职后长时间没有办法找到工作 就想贡献一套平时琢磨出来的程序&#xff0c;请各位有能力的话带我熬过这凛冽的寒冬。 目前写出来的&#xff0c;有三个端&#xff1a;安卓TV端&#xf…

【Windows 常用工具系列 11 -- 笔记本F5亮度调节关闭】

文章目录 笔记本 F 按键功能恢复 笔记本 F 按键功能恢复 使用笔记本在进行网页浏览时&#xff0c;本想使用F5刷新下网页&#xff0c;结果出现了亮度调节&#xff0c;如下图所示&#xff1a; 所以就在网上查询是否有解决这个问题的帖子&#xff0c;结果还真找到了&#xff1a;…

java IO流(三) 转换流 打印流

转换流 前面我们学习过FileReader读取文件中的字符&#xff0c;但是FileReader默认只能读取UTF-8编码格式的文件。如果使用FileReader读取GBK格式的文件&#xff0c;可能存在乱码&#xff0c;因为FileReader遇到汉字默认是按照3个字节来读取的&#xff0c;而GBK格式的文件一个…

【微服务部署】三、Jenkins+Maven插件Jib一键打包部署SpringBoot应用Docker镜像步骤详解

前面我们介绍了K8SDockerMaven插件打包部署SpringCloud微服务项目&#xff0c;在实际应用过程中&#xff0c;很多项目没有用到K8S和微服务&#xff0c;但是用到了Docker和SpringBoot&#xff0c;所以&#xff0c;我们这边介绍&#xff0c;如果使用Jenkinsjib-maven-plugin插件打…

数据结构day7栈-顺序栈的实现

用指针比用数组好&#xff0c;这样用户可以自己指定空间的大小&#xff0c;有参与感。 全部代码: main.c #include <stdio.h> #include <string.h> #include "sqstack.h"int main(int argc, char *argv[]) {sqstack *s;int i;s stack_create(100);if(…