关于网盘项目的反思和各种思路

一.关于一期各种命令的实现的不同方法的优缺点比较。

1.基于linux文件系统

服务器给每个用户生成一个用户主目录,并提供给用户操作该目录的权限。然后使用数据库记录每个用户的主目录以及相应信息。

优点:

  • 设计简单,可以直接使用linux各种命令,通过将linux系统返回信息处理后,直接返回客户端即可。各种指令可以衔接到linux上添加新命令也比较方便。
  • 用户之间相对独立,不会影响到其他。
  • 可以支持用户对服务器上文件直接修改(这个不确定)。

缺点:

  • 难以实现文件共享,即一份大文件在服务器中只存在一份,而被所有用户共享。由于各种命令基于linux文件系统,类似于rm命令之类的可能需要自己重新设计。或许可以用软连接来代替。
  • 用户之间存在资源竞争,即必须使用每一个进程对应一个用户,因为当多个用户登录,并且在不同目录下,要么改变进程的当前目录,要么就是通过数据库或者设计相应的结构体存放各个用户的当前目录。

 

2.通过数据库实现虚拟文件目录。

关于网盘项目的反思和各种思路_第1张图片

我的具体实现,其中dirID就是文件ID,但是由于一开始设计写成这样加上外键,和已经写成的代码。现在改的话,很容易出错也会创造很多bug,就一直保持这样了。这里特意说明一下。

关于网盘项目的反思和各种思路_第2张图片

优点:

  • 为每个用户在数据库中建立一项纪录,添加和删除用户只需要在数据库中修改相应纪录即可,方便快捷。
  • 所有文件存放在同一个指定目录下面,文件名以文件的md5码存放,保证文件唯一性。当客户端需要下载文件的时候,服务器只需要通过MD5码打开文件,并将文件发送给客户端即可。当然为了保证客户端文件名为自身设定的文件名,我们需要在数据库中维护一个文件表,用来记录文件id,父目录id,文件类型,大小,MD5码,文件在客户端的名字。
  • 这样做有很多优点。首先当用户上传一个文件时,通过检索MD5码,发现存在相同的文件,只需要给客户端添加相应的记录,并将该记录的父目录id设置为对应用户的目录即可,上传操作是不必要的。其次,当一个用户删除某一项资源时,也只需要删除这条记录即可,具体文件可以不用改变。当然这也会产生相应的问题,那就是文件何时删除。这需要通过具体实际情况进行分析。在磁盘空间充足的情况下,可以暂时不用理会文件的删除,哪怕没有用户具有该文件的记录,这么做有个两个好处,一是当有客户上传该文件的时候,我们可以将直接新建记录,而不用文件传输,减轻服务器网络压力。二是我们可以将文件的删除工作放在服务器空闲的时候,将每个文件名(MD5码)依次在数据库中检索,如果文件MD5码不存在与数据库之中,我们便可以将文件删除掉。此外,通过这一设计,可以完成回收站功能,我没有实现,就不乱说了。

缺点:

  • 需要在数据库中维护各种表,插入和删除都需要时间和空间的花销。
  • 设计相对前种比较困难。

 

3.虚拟目录下基础命令的实现思路:

  • 对于各种基础命令(cd, pwd, ls, rm, puts, gets,mkdir)的实现上,通过对数据库文件表操作是比较方便的事情。先忽略下具体的实际操作,只是考虑当我们执行这项操作时,在数据库中我们该怎么做。在设计上,我使用每一个线程对应一个用户,并使用结构体记录了用户的用户id(也可以用户名)和当前目录。上传下载操作会由其他的线程负责。换句话说,每个线程具有处理用户短命令功能,当接收到上传下载命令时,会将任务抛给空闲线程。这里的设计也有一定的技巧,后面再说。
  • cd命令,进入某个目录。就是对于用户虚拟当前目录的转换。分两种情况,一是跳到上一级目录,我们只需要检索当前目录的prevID(父目录ID),将当前目录ID转换为当前目录的prevID(父目录ID)即可。二是进入当前目录的某一个目录之中,我们可以通过检索父目录ID为用户当前目录ID,文件类型为'd'(表示目录),客户端文件名为用户指定的文件,如果存在,将用户当前目录切换至该文件的ID,如果不存在,这一文件,将错误信息返回给客户端即可。对于一下跳跃多层目录的命令,我没有实现,就不扩展了。
  • pwd命令,列出当前文件路径。可以通过对当前目录id取出当前目录文件名,然后通过父目录id找到上一层目录,递归处理,将所有文件名拼接起来。这时候我们可以得到一个反向的目录信息,因为我们是从下往上去寻找,而正常当前目录显示是从上往下。可以怎么做呢?这里可以使用字符串处理的一个算法,先将字符串整个翻转,再将每个目录名单独翻转,这就得到了当前目录路径了。
  • ls命令,列出当前目录文件的信息。就是找出父目录id为用户当前目录id的所有文件,并将需要的信息检索出来,发送给客户端。这里需要注意字符串长度可能会很长,需要分多次发送。
  • rm命令,删除指定文件。通过用户名(或用户id),用户当前目录和文件名(客户端指定)来确定一条记录,将该记录删掉即可。
  • mkdir命令,创建一个目录文件。由于是虚拟目录,只需通过插入父目录id指定为用户当前目录id,文件类型为'd'的文件即可,其他信息无所谓。(备注:目录文件没有md5码)
  • gets命令,客户端向服务器下载某个文件。通过用户id,客户端文件名和父目录id为用户当前目录id检索出对应文件的记录,将文件重要信息发送给客户端,然后完成下载操作即可。
  • puts命令,客户端向服务器上传某个文件。为了避免重复上传文件,首先根据客户端计算MD5码发送至服务器端,服务器检索该MD5码,如果存在,则不用上传,给客户端添加记录即可;如果不存在,则打开文件名为MD5码的文件,如果已经存在该文件,将文件大小作为偏移量,发送给客户端,两边都从偏移量开始传输,避免多余的重复传送(断点续传机制);如果不存在,偏移量为0,不影响上传操作。最后当可以通过计算文件MD5码确认上传成功与否,成功将文件信息写入文件表;失败,代表没有传输完毕,不写入文件信息。当然如果使用的文件空洞+mmap技术,那么需要维护一个上传表,记录接收到的信息,用作下次上传的偏移量。这个设计有个好处,多个用户之间上传的进度是可以共享的,只要一个用户上传好,其他用户也就上传好了。

 

4.客户端多线程下载与上传:

关于网盘项目的反思和各种思路_第3张图片

    刚开始想客户端多线程想得是类似于服务器多线程将下载任务通过消息队列给子线程就好。但是后来发现这样做是可以,但是在这中间如果客户端主线程也和服务器通信的话,服务器无法区分主线程和子线程的区别,这会污染数据。由此,我们必须要给子线程一个身份,一个服务器能知道用户是谁,而又能够跟主线程区分的身份。

    首先,可以使用token技术。token是一个特定的字符串,由服务器发送给客户端,客户端通过token可以在一定时间内确认用户身份(记录在数据库中)。每个用户的token值应该不一样,不然无法区别身份。可以是用户名+登录时间通过某种加密方式生成token值(具有唯一性)。在发送的每个数据包(tcp是字节流,但我们可以在应用层设计相应协议),都携带token值,这样哪怕是不同ip或者不同端口,服务器也会将这个数据包识别为同一个用户。

    其次,我们需要让主线程(短命令)和子线程(上传或下载)区分开来。对此,我们可以通过消息队列,将服务器ip地址,端口号传给子线程,子线程重新建立tcp连接(新建socket描述符,重新connect),操作系统会分配一个新的端口号给子线程。这时候,由于子线程端口号与主线程端口号的不同,我们就可以区分开主线程和子线程传递的消息了。

5.断点续传(下载上传)

    断点续传其实就是让服务器在断点处开始发送数据,客户端在断点处开始接收数据。有以下两个重要的问题:1.我们如何确定断点?2.我们如何让客户端和服务器从断点处开始发送数据或接收数据。

先回答第一个问题:1.我们如何确定断点?

    我们可以通过服务器记录发送的数据(这是我一开始的想法),但是当客户端与服务器连接突然中断时,send返回值是写入发送缓冲区的数据的大小而不是客户端接收数据写入文件的大小,这样无法确定客户端到底收到了多少数据。这样这一个想法就被我放弃了。

    那么我们只能通过客户端来记录收到的数据了。客户端接收数据有两种比较好的方法。

1.通过ftruncate构建文件空洞和mmap映射的零拷贝技术来传输,这种方法是可行的,但是由于ftruncate会使文件大小不是实际大小,而是虚拟大小,故需要我们额外记录接收数据大小,这添加了额外的操作。所以基于这种考虑,我使用了第二种方法。mmap实现偏移是通过mmap返回的指针加上偏移量,从而将接收数据点移到断点处。

2.通过splice技术实现零拷贝传输,这时候文件大小就是我们需要的断点了。我们只需要通过打开文件,获取文件属性,就可以知道文件大小,也就是断点。将断点作为偏移量,用lseek进行偏移,就可以断点续传了。

3.服务器操作是类似的,这里就不再废话了。

 

6.多点下载

    1.其实,懂了上面的断点续传和客户端多线程下载,多点下载就已经不复杂了。我们可以通过客户端先向发送文件名,从服务器获取基本信息(文件MD5值,文件大小,其他资源服务器IP地址和端口号,对应服务器的秘钥)然后将需要的文件分割成不同的部分,而客户端将每一段作为不同的下载任务发送给不同的线程,每个线程通过lseek偏移到文件中不同的起点,分别连接不同的资源服务器,(一个线程连接一个资源服务器,负责一段任务的下载)然后将文件每一段的起点和终点以及文件信息发送给资源服务器(一段对应一个资源服务器),资源服务器先返回确认信息,然后从起点(偏移量)开始传递文件,文件大小为 终点 - 起点

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(项目,Linux)