文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-09-05 20:45

关注
char s1={'A','B','C'};sprintf(str,"aaa %.*s",2,s1); //取字符串s1的前2个字符并和“aaa ”拼接执行完后str字符串为“aaa AB”
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      #include #include #include #include #include #include #include #include #include #include #include #include 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      #include #include #include #include #include #include #include #include #include #include #include #include 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;}

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

server_ls”指令演示

观察server的目录来验证:

 

server_cd”指令演示

client_ls”指令演示

观察client的目录来验证:

client_cd”指令演示

upload”指令演示

客户端:

服务端:

 

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

 

也可以用“server_ls” 来验证: 

get”指令演示

客户端:

服务端:

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

也可以用“client_ls” 来验证:

“quit”指令演示:

客户端:

服务端:

 

来源地址:https://blog.csdn.net/mjmmm/article/details/132613439

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-人工智能
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯