Linux 15:基于C/S架构——微云盘


1. 项目简介

(1)项目名称:基于C/S架构模型——微云盘

(2)项目环境:Linux,vim,gcc
(3)项目描述:

  • 上传功能:将文件从客户端上传到服务器,显示百分比,实现文件校验,秒传
  • 下载功能:将文件从服务器端下载到客户端,显示下载文件百分比,断点续传,文件校验
  • 断点续传:当文件在下载中中断,下次可接着上次中断地方继续下载
  • 秒传:当服务器已经存在某个文件时,当别的用户再次上传时,服务器直接返回下载成功

2. 图解

3. 代码serfile&clifile(初始版)

my.conf

#ip地址
ipstr=127.0.0.1
#端口号
port=6000
#监听队列
lismax=5

client.c

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<sys/socket.h>
  6 #include<arpa/inet.h>
  7 #include<netinet/in.h>
  8 
  9 int main()
 10 {
 11     int sockfd=socket(AF_INET,SOCK_STREAM,0);
 12     if(sockfd==-1)
 13     {
 14         exit(0);
 15     }
 16 
 17     struct sockaddr_in saddr;
 18     memset(&saddr,0,sizeof(saddr));
 19     saddr.sin_family=AF_INET;
 20     saddr.sin_port=htons(6000);
 21     saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
 22 
 23     int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
 24     if(res==-1)
 25     {
 26         close(sockfd);
 27         exit(0);
 28     }
 29     while(1)
 30     {
 31         printf("connect>>");
 32         fflush(stdout);
 33 
 34 
 35         char buff[128]={0};
 36         fgets(buff,128,stdin);
 37         if(strncmp(buff,"end",3)==0)
 38         {
 39             break;
 40         }
 41         send(sockfd,buff,strlen(buff),0);
 42         memset(buff,0,128);
 43         recv(sockfd,buff,127,0);
 44         printf("buff=%s\n",buff);
 45     }
 46 }

makefile(注意:gcc前面不是空格,是tab键,否则会报错)

  1 all:ser
  2 
  3 ser:ser.o socket.o work_thread.o
  4     gcc -o ser ser.o socket.o work_thread.o -lpthread
  5 
  6 ser.o:ser.c
  7     gcc -c ser.c
  8 
  9 socket.o:socket.c
 10     gcc -c socket.c
 11 
 12 work_thread.o:work_thread.c
 13     gcc -c work_thread.c
 14 clean:
 15     rm -f *.o ser

ser.c

  1 #include"socket.h"
  2 #include"work_thread.h"
  3 int main()
  4 {
  5     int sockfd=socket_init();
  6     if(sockfd==-1)
  7     {
  8         printf("create socket err\n");
  9         exit(0);
 10     }
 11 
 12     struct sockaddr_in caddr;
 13     while(1)
 14     {
 15         int len=sizeof(caddr);
 16         int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
 17         if(c<0)
 18         {
 19             continue;
 20         }
 21         printf("accept c=%d\n",c);
 22         start_thread(c);//启动线程
 23     }
 24 }

socket.h

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<sys/socket.h>
  6 #include<arpa/inet.h>
  7 #include<netinet/in.h>
  8 
  9 //创建套接字
 10 int socket_init();

socket.c

  1 #include"socket.h"
  2 
  3 #define IPS "ipstr="
  4 #define PORT "port="
  5 #define LISMAX "lismax=" 
  6 
  7 //存储用户信息(为监听套接字准备好需要需要的环境)
  8 struct sock_info
  9 {
 10     char ips[32];//ip地址
 11     short port;//port端口号
 12     short lismax;//监听队列大小
 13 };
 14 
 15 //读取配置文件
 16 int read_conf(struct sock_info*pa)
 17 {
 18     if(pa==NULL)
 19     {
 20         return -1;
 21     }
 22     FILE*fp=fopen("my.conf","r");//文件名,打开方式
 23     if(fp==NULL)
 24     {
 25         printf("open my.conf failed\n");
 26         return -1;
 27     }
 28     //记录错误行
 29     int index =0;
 30     char buff[128]={0};
 31     while(fgets(buff,128,fp)!=NULL)//fgets(存放读取的数据,指针大小,从哪里读取)
 32     {
 33         index++;
 34         //异常处理:跳过注释
 35         if(strncmp(buff,"#",1)==0)
 36         {
 37             continue;
 38         }
 39         if(strncmp(buff,"\n",1)==0)
 40         {
 41             continue;
 42         }
 43         //记录错误信息
 44         buff[strlen(buff)-1]='\0';
 45 
 46         if(strncmp(buff,IPS,strlen(IPS))==0)
 47         {
 48             strcpy(pa->ips,buff+strlen(IPS));
 49         }
 50         else if(strncmp(buff,PORT,strlen(PORT))==0)
 51         {
 52             pa->port=atoi(buff+strlen(PORT));
 53         }
 54         else if(strncmp(buff,LISMAX,strlen(LISMAX))==0)
 55         {
 56             pa->lismax=atoi(buff+strlen(LISMAX));
 57         }
 58         else
 59         {
 60             printf("未识别的配置项在%d行:%s\n",index,buff);
 61         }
 62     }
 63     fclose(fp);
 64 }
 65 
 66 //套接字初始化
 67 int socket_init()
 68 {
 69     //读取文件:获取ip,port,lismax
 70     struct sock_info a;
 71     if(read_conf(&a)==-1)
 72     {
 73         printf("read conf err\n");
 74         return -1;
 75     }
 76     printf("ip:%s\n",a.ips);
 77     printf("port:%d\n",a.port);
 78     printf("lismax:%d\n",a.lismax);
 79 
 80     int sockfd=socket(AF_INET,SOCK_STREAM,0);
 81     if(sockfd==-1)
 82     {
 83         return sockfd;
 84     }
 85     struct sockaddr_in saddr;
 86     memset(&saddr,0,sizeof(saddr));
 87     saddr.sin_family=AF_INET;
 88     saddr.sin_port=htons(a.port);
 89     saddr.sin_addr.s_addr=inet_addr(a.ips);
 90 
 91     int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
 92     if(res==-1)
 93     {
 94         return -1;
 95     }
 96     res=listen(sockfd,a.lismax);
 97     if(res==-1)
 98     {
 99         return -1;
100     }
101     return sockfd;
102 }       

work_thread.h

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<pthread.h>
  6 #include<sys/socket.h>
  7 #include<arpa/inet.h>
  8 #include<netinet/in.h>
  9 
 10 void start_thread(int c);                                  

work_thread.c

  1 #include"work_thread.h"
  2 
  3 void*work_thread(void*arg)
  4 {
  5     int c=(int)arg;
  6     while(1)
  7     {
  8         char buff[128]={0};
  9         int n=recv(c,buff,127,0);
 10         if(n<=0)
 11         {
 12             break;
 13         }
 14         printf("read:%s\n",buff);//测试
 15         send(c,"ok",2,0);
 16     }
 17     close(c);
 18     printf("client close\n");
 19 }
 20 
 21 void start_thread(int c)
 22 {
 23     pthread_t id;
 24     pthread_create(&id,NULL,work_thread,(void*)c);
 25 }

运行结果

测试错误代码看程序是否可以识别
在这里插入图片描述
在这里插入图片描述

4. 协议

问题提出:如何实现命令?
方法一:自己实现获取当前目录下有哪些文件:实现灵活
方法二:调用已有的命令如ls,mv等实现获取当前目录下有哪些文件:可扩展性比较高,实现接口通用,代码可移植性高。

本项目采用多线程编程,用创建监听套接字 socket, 用TCP协议实现交互方式,在父进程中先创建无名管道,然后fork产生子进程,在子进程中用exec函数进行替换命令,用管道写端dup2覆盖替换标准输出,然后父进程在管道中将数据读出,进而用管道实现父子间通信,比如命令ls执行结果返回给父进程,父进程发送给客户端,客户端收到后打印出来,然后close关闭。实现下载方式,用自定义协议,服务器端给客户端发送两次接收两次,用 sendfile 函数零拷贝发送,进度条显示下载进度,断点续传,上传文件过程中实现秒传。

5. 客户端

  • ls命令执行:服务器端执行ls命令,服务器端fork复制产生子进程,在把子进程替换成ls(exec),服务器端发给客户
  • rm命令执行:fork客户端发给服务器端
  • 为了防止客户端和服务器端不能交互,所以当每次执行命令时候,客户端给服务器端发送"ok#…"(仅仅代表命令执行成功,不代表其他)
  • 无名管道:子进程把输出信息写入无名管道中,父进程从无名管道中把信息读出来,然后发送给客户端,客户端在打印出来
  • dup2方法:复制文件描述符
    先创建管道再复制一下,管道就会被自动复制到子进程中
  • 服务器端:输入客户端发送过来的命令,然后解析命令

5.1 代码图解

协议
在这里插入图片描述

在这里插入图片描述

6. 断点续传(服务器-客户端)

问题提出

首先需要区分文件是否需要断点续传?

(1)通过后缀区分:下载结束的a.txt,没下载结束的a.txt.tmp
(2)修改协议:get a.txt 0代表从起始位置下载,get a.txt size(size代表偏移量)偏移量的大小代表上次传输量,然后与文件进行校验,将其余部分进行上传填充到字符串后,将整体发送。
(3)将当前已经下载文件的部分大小获取,发送给服务器,服务器打开文件后进行大小偏移,然后从当前位置进行数据传输。

问题:如何让服务器可以兼容所有的客户端版本? 答:使用协议版本,产生一个接口去进行兼容。

7. 秒传(客户端-服务器)

  • 文件数据没有发送给服务器,我们所看到的仅仅只是快捷方式而已,因为服务器已经存在这个文件,在一个服务器上面只需要存一份文件就ok,因为服务器端是公用的。
  • 秒传就是计算出所有文件的MD5值然后进行保存,每个要上传到服务器的文件都要进行校验MD5值,如果不存在,那此文件便是新文件,服务器会新建文件。

7.1 秒传优点

  1. 节省磁盘空间
  2. 节省网络带宽

7.2 MD5校验

7.2.1 定义和内部结构

  • 一个散列函数,比如 MD5,是一个将任意长度的数据字符串转化成短的固定长度的值的单向操作。任意两个字符串不应有相同的散列值(即,有“很大可能”是不一样的,并且要人为地创造出来两个散列值相同的字符串应该是困难的)。
  • 一个 MD5 校验和(checksum)通过对接收的传输数据执行散列运算来检查数据的正确性。计算出的散列值拿来和随数据传输的散列值比较。如果两个值相同,说明传输的数据完整无误、没有被窜改过(前提是散列值没有被窜改),从而可以放心使用。

7.2.2 作用和机制

  • MD5校验(checksum)是通过对接收的传输数据执行散列运算来检查数据的正确性。
  • MD5算法常常被用来验证网络文件传输的完整性,防止文件被人篡改。MD5 全称是报文摘要算法(Message-Digest Algorithm 5),此算法对任意长度的信息逐位进行计算,产生一个二进制长度为128位(十六进制长度就是32位)的“指纹”(或称“报文摘要”),不同的文件产生相同的报文摘要的可能性是非常非常之小的。
  • 在linux或Unix上,md5sum是用来计算和校验文件报文摘要的工具程序。一般来说,安装了Linux后,就会有md5sum这个工具,直接在命令行终端直接运行。

7.2.3 用途

MD5校验可以应用在多个领域,比如说机密资料的检验,下载文件的检验,明文密码的加密等。

8. 项目扩展

如果要增加链接客户端数量,一个客户端要用一个套接字进行连接
ulimit -a可以查看一个服务器最多连接文件数量

9. 下载

结果调试阻塞
在这里插入图片描述
运行结果
在这里插入图片描述


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值