linux学习之旅(32)----c语言实现局域网内的文件上传和下载(模拟ftp)

二、局域网内的文件共享系统
项目功能: 模拟 FTP 的匿名模式,实现局域网内的文件共享。
项目介绍: 该项目是在 linux 系统环境下,利用 c 语言和 linux 系统函数编写的,是 C/S 结构。
用户利用客户端可以将本地的文件上传到服务器中或将服务器上的文件下载到本地,同时也可以
查看服务器中的文件有哪些,以便用户在下载时选择。
系统功能: (1)上传/下载文件(支持同时上传/下载多个文件)、(2)大文件的传输、(3)断点
续传、(4)文件内容校验、(5)共享目录查看、(6)文件重名解决策略。

 

FTP简介

1、FTP协议:

FTP(File Transfer Protocol,文件传输协议)是TCP/IP协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通过利用FTP协议把网页或程序传到WEB服务器上。此外,由于FTP传输效率非常高,在网络上传输大的文件一般也可以采用该协议。

默认情况下FTP协议使用TCP端口中20和21这两个端口,其中20用于传输数据,21用于传输控制信息。但是,是否使用20作为传输数据的端口与FTP使用的传输模式有关,如果采用主动模式,那么数据传输端口就是20;如果采用被动模式,则具体最终使用那个端口要服务器和客户端协商决定。

2、FTP存在的问题

但传统的FTP采用单线程线程上传文件,在网络带宽有限的条件下,对带宽的抢占优势小,导致是上传速度慢;同时,一旦发生意外中断,就需要重新上传文件的全部数据,极大的影响了数据上传的效率。

主要来说FTP传输的问题:

(1)单线程传输对于大文件来说速度非常有限。

(2)传输中断后需要重新开始,效率低。

(3)上传文件到服务器是一次只能有一个端操作,导致传输效率低。

项目简述

项目名称:文件传输系统

项目功能:实现局域网内文件的上传和下载。系统支持的功能有:(1)用户查看服务器端共享目录的内容。(2)大文件的传输。(3)文件传输中断后的续传。(4)文件传输完毕后的内容校验。(5)同时上传(下载)多个文件。

一、项目功能简述

1、文件上传:将用户指定的文件上到到服务器上。

使用命令:

put fileName [filePath(文件所在的路径)]

当用户不指定文件的路径是默认为程序所在的目录下。上传目录为:SharedFolders。

2、文件下载:将用户指定的文件从服务器上下载到本地。

get fileName [filePath(文件下载的路径)]

当用户不指定文件存储时的路径时,默认下载目录为:ReceiveFolders。

3、共享目录内容查看

sls(server ls)

通过该命令可以查看系统共享目录下的有那些文件,帮助用户下载。

4、客户退出服务器

quit

通过该命令用户客户可以安全的退出系统。

5、断点上传和下载。

系统在每次上传或下载时,需要在接收文件的那一端,计算出文件已经接收了多少字节,然后将已经接收的字节数,发送给文件的发送方,文件的发送在接受到消息后从该处开始传输消息。这样就实现了文件的断点上传和下载(这会为客户的上传造成一定的问题)。

6、支持大文件传输。

程序在运行时系统会为用户提供一个4g(32位)的空间,其中3-4g为内核空间(用户无法使用),0-3g为用户空间。而在这0-3g的需要存储一些程序在运行时必要的数据,所以留给用户真正使用的部分就不够3g(对于本系统大约2.5G左右),所以一旦文件过大就会导致内存中无法加载,从而出错。

解决方法:利用mmap函数将文件分块映射到内存中,每次传输一块,这样不仅可以解决上面文件太大无法传输的问题,还可以提高文件读写的速度。(不过文件传输的太快会导致粘包问题)

7、内容校验。

系统采用MD5消息摘要算法,MD5算法是一种被广泛使用的密码散列函数,可以产生一个128位(16字节)的散列值,用于确保信息传输完整一致。文件的MD5值就像这个文件的“数字指纹”,内容不同的文件的MD5值是不同的,所以我们可以利用它来校验上传(下载)的文件内容是否正确。(在Linux系统中,可以通过md5sum的命令计算文件的MD5值。)

8、文件重名问题。

当你在上传(下载)的文件时,对应的接受方的接受目录下存在和当前上传(下载)同名的文件。这个问题在下载时很好解决,就是在下载完成后,将文件在本地重命名了(然后消息通知用户),因为不能因为一个用户而去修改服务器上的文件名。但如果时发生在上传时期,就不太好解决。这里我总结了三种方法:

1、强制客户修改。当上传文件时,服务端检测该文件在服务上有没有同名文件(在打开文件时为open函数加上O_EXCL选项,文件如果存在就会报错),如果有就发给客户端一个消息,客户端在接收到该消息后就强制用户修改当前上传的文件名,然后再次上传。

       存在问题:当服务端的文件过多,可能会导致用户多次重命名。这样就会降低用户体验。

2、服务自动重命名。当系统检测到文件已经存在时,系统自动在文件后面加上0,1,2...的方法命名。

        存在问题:在其他系统中使用可能没问题,但在本系统再,由于之前的续传功能时根据文件检测的,所以,一旦服务修改了文件名,就会导致服务端和客户端内容相同文件的文件名不同,这样就续传时就会导致系统判断出现误差,就可能导致将客户端的a文件的内容,续传到服务器的a文件中,这样就会导致错误。而且一个系统中的文件过多服务器处理的时间可能会很长,从而导致服务器响应过慢。

3、混合策略:就是混合以上两种思想。主要思路就是再用户上传文件时为文件加上一个不可重复的前缀或后缀,这样就会大大的降低用户个其他用户文件重名的问题,当自己上传的文件发生重名时,再强制客户进行修改,这样就会降低两方的压力。

二、项目设计

本系统是一个C/S模型,即客户端/服务器模型。

1、服务器端设计

服务器模型采用了多路I/O复用中的poll。选择poll的原因:

(1)操作比select简单一些,不需要每次都传入客户机的状态。

(2)对于局域网在的用户数量poll完全可以处理,没有必要使用epoll。

(3)多线程是不稳定的,一旦再数据传输的过程中一个客户端出错,就可能会导致整个系统崩溃,这无疑是非常危险的。而且再linux系统中的线程并不是真正意义上的线程,而是被称为“轻量级进程”,这里就说明,在Linux系统下,线程就只是可以共享数据的进程而已。

(4)其实是对于本系统来说,完全可以采用多进程,一:本系统目前支持32个客户,二:多线程稳定,三:各个客户端不需要交互,所以每个进程只需要负责监听自己的socket,处理自己的事务,就会使过程变得简单。但多线程的对资源的消耗过大,一旦系统需要支持更多的客户就会使得服务器的响应变慢。

在本系统一共为用户实现了4个命令。分别是:get(下载)、put(上传)、sls(查看)、quit(退出)。

系统在处理前三个命令都是通过frok()函数,创建出子进程,然后通过子进程去处理相应的事务,这样作的原因是基于:

对于一些比较大的文件,文件上传(下载)可能需要一些时间才能完成,这样就会导致服务器无法相应其他客户的请求。利用进程可以避免这个问题,当子进程在传输时,父进程可以继续去监听。

但是由于进程之间通信比较麻烦,所以就会导致下面这个问题:

这个问题主要是体现在文件上传的时候,由于子进程进程会拷贝父进程的数据,而且各个进程之间相对独立,所以当在子进程中监听对应客户端的socket时,父进程同样也会监听,这样就会导致消息混乱(分不清数据是被那个进程接收了),这样系统就会出错。

这里我采用的解决办法时,通过管道解决这个问题。具体操作就是在子进程准备接收数据的时候,在父进程中的poll监听队列中将该socket提出队列(即暂停对该socket的监听),等待子进程完成传输任务后,通过管道将该socket写回给父进程,在被父进程回收时,父进程通过管道对出相应的socket在恢复对它的监听。

2、客户端设计

在客户端启用两个进程,一个负责接收用户输入的消息,一个负责接收服务端发送的消息。这样作的原因:

(1)因为我们无法确定用户和服务器发送数据的时间,所以不知何时该处理那个函数。

(2)如果采用非阻塞的轮询方式,就会导致客户端的负担增大,因为需要不停的判断。

但这样再上传文件时(在接受用户数据进程中实现)会出现一个问题:上面说过为了实现断点续传功能,系统会在检测服务器上是否存在已有的文件,并会客户端为发送文件的偏移量,但是由于接收服务器消息的功能再另一个进程中,所以就会导致该消息无法被接收到,从而导致客户端和服务器两端同时阻塞。即客户端在等待偏移量,服务在等待客户端发送数据。这里的解决方法还是通过管道,在接受服务器消息的进程接收到消息通过管道将数据发送给接收用户消息的进程。

当用户输入数据后,客户端会对用户输入的数据进行判断,如果输入合法直接将处理后的数据发送给服务器,如果不合法就直接丢弃数据,然后返回给客户错误信息,这样以减少服务器端的压力。

当客户端上传(下载)文件时:一般的命令,由于数据量小是通过主进程直接传输,但由于文件的数据量一般较大,所以这里再处理上传文件时,单独为此模块开个一个线程,这样用户就可以同时上传(下载)多个文件。但由于只有一个端口,所有的数据都是通过一个端口传输的,所以速度和一个一个发是差不多的,但却不是一个发完一个再发,发送的文件是随机的。

粘包问题:

在文件传输系统中,在数据的发送方会连接多次发送数据,但数据并不是在调用write函数后,就会被发送的,而是会先放入网卡的缓冲区中等待缓冲区满,然后再发送数据,这就导致多次数据可能被一次发送,从而导致对应的相应动作少相应或无法相应。就会导致问题。

三、项目改进

提高文件发送速度。

本系统目前还是单socket收发数据,再发送大文件时,速度就会变得很慢,那如何解决这个问题那?

socket时IP和端口的集合,通过IP可以找到对应的主机,通过端口可以找到对应的应用程序,那么,就可以利用这一原理,在一台主机上创建多个socket为他们绑定多个端口,这样就可以将文件分成多块,然后通过不同的端口发送(接收),然后在接收方将这些文件在合并起来。从理论上说,再不考虑带宽的情况下,socket越多,文件传输的速度越快。那么需要和谁建立多个socket连接那?

(1)和单服务器建立多个socket通道。

这时很容易想到的,也比较好实现,因为客户端本来就需要和服务器建立连接,而且服务上存在客户所需要的所有的文件。但这样会使得服务端的压力增大,可能会导致服务响应变慢。

(2)和多个服务器建立socket通道(分布式)。

这个单纯从技术来说,这个选择是最好的,客户端从不同的服务器上下载文件的不同部分,各个服务器的负担也不会增大。这里存在一个文件存储的问题。就是服务器上对于同一个文件的存储策略。

(1)每台服务器储存的内容都相同,这样就很大程度上简化的文件上传和下载的操作。因为每台服务器上的内容都相同,所以也就不同考虑需要从那一台主机上去那一块。而且,就算服务器集群中的一台或多台出现问题,但只要还剩余一台,整个服务器就还可以正常的工作,但这样会浪费服务器上的空间。

(2)每台服务器只存储那个文件的一部分,客户端分别从不同的主机上拿到不同的内容,这样就会节省出很大的空间。但只要服务区集群中的一台出现了问题,就会导致用户无法下载到完整的文件。

(3)多数服务器只存储文件的一部分,已小部分服务器存储文件的全部内容,这样就结合了两方面的优点。

但服务本身就需要一定的成本,所以这个方法本身成本就很高。

(3)和当前正在传输该文件的客户端建立多个socket。

当多个客户端在同一服务器上下载同一文件时,系统通过算法,使得不同客户下载的部分不同,然后每个客户端和其他客户端建立连接,再各个客户端上交互数据。这样的系统的特点就是,下载同一个文件的用户越多,下载的速度越快。

优点:不用布置大的服务器集群就可以增加文件的传输速度。

缺点:若下载的文件为冷门资源,下载的速度就会降低。

同时如果增加了客户端的socket数量,还可以实现真正意义上的同时上传和下载。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值