文章目录
前言:功能实现
1.用户登录
2.文件上传,下载(包含大文件)
3.断点续传,秒传
4.零拷贝
tcp文件传输的基本过程:
在传输文件数据之前,发送端会把文件名称和文件长度等信息的数据包发送至接收端,接收端收到文件名称和文件长度信息后会创建好空白文件。接着开始传输文件数据。
服务端有一个创建新线程的函数,一旦服务端监听有连接请求,就创建新线程去连接该请求,即每个客户端都是一个线程在服务器中运行,互不干扰,基本满足局域网内的高并发访问。
1.用户登录
1.1创建数据库
采用mysql,新建三个数据库
- loginUser数据库,表为user,用于保存用户名和密码,用于登录匹配。
其中,**user **表如下:
创建代码如下:
create table user(id int(11) primary key AUTO_INCREMENT ,name varchar(20),passwd varchar(20));
- md5数据库,表为md5table,用于存储文件信息:(文件名,md5值,文件属于哪个用户,文件完整标志位)
因为我们考虑到断点续传和秒传,需要用md5值记录该文件在服务端是否存在。
输入任意长度的信息,经过处理,输出为128位的信息(数字指纹);
不同的输入得到的不同的结果(唯一性),采用是的散列函数,hash算法。
程序中计算md5值的方法,是利用LINUX支持OPENSSL并提供如下MD5函数。
MD5_Init初始化MD5_CTX结构。
MD5_Update计算摘要。
MD5_Final输出摘要值。
其中,md5table表如下:
创建代码如下:
create table md5table(filename varchar(50) primary key ,md5Value varchar(50),username varchar(20),intact varchar(20));
- serverLog数据库,表为serverStartInfo,用于记录服务器的启动时间,启动次数。
其中,serverStartInfo表如下:
创建代码如下:
create table serverStartInfo(times int(11) primary key AUTO_INCREMENT ,date varchar(50));
-
如果出现启动数据库无法访问,可能是要开启权限,具体参照下文。
启动Mysql数据库报错误:-bash: ./start.sh: Permission denied -
利用Shell编程实现多个接口用于用户注册,登录,插入文件信息到数据库,删除某个文件记录,md5值匹配后秒传,判断文件所属是否是当前用户等等。同时,系统也应该有一个root用户,拥有最高权限,可用来删除文件,关闭服务器等操作。设置root用户的方法也很简单,只需根据用户名是否为root来设置一个标志位即可,用于标记该用于是否拥有管理员权限。
2.文件普通下载和上传的实现:
(注:下载是指客户端从服务器下载文件,上传是指客户端上传文件到服务器)
考虑到下载或者上传过程中有时会出现中断,之后还要继续下载,我们需要设置一个文件完整标志位来表明操作完成的文件是否是完整的。
2.1 普通下载
普通下载在这里指的是一次性完成文件下载工作,无中断。分析后可知,对于普通下载,我们需要在服务器端判断待下载的文件是否完整—即进入数据库中查询文件完整标志位,完整的话就开始下载。
2.2 普通上传
普通上传在这里指的是一次性完成文件上传工作,无中断。由于考虑到多个用户同时连接服务器的问题,服务器端的数据库是要存储文件名,文件所属(用户),文件完整标志位,以及md5值。这里md5值相当于每个文件的数字指纹,用来标识文件是否已经存在于服务器端,用于判断还需不需要继续上传该文件(即秒传功能的实现),后面会详细提到。
注:Message-Digest Algorithm 5(信息-摘要算法5)
接下来,普通上传的实现就很简单,当上传文件成功后,文件完整标志位置1,使得用户可以下载该文件。
2.3 文件秒上传的实现
前面提过每个文件都会有一个md5值。在客户端上传文件的时候,会先计算该文件的md5值,并把文件信息和md5值发送给服务器,服务器会从数据库中匹配该文件名和md5值以及文件是否完整。若服务器中存在满足上述三个条件的文件,说明服务器中已经存在该文件,保留这一个即可。若不满足,再上传文件后根据文件完整标志位判断使用普通上传亦或是断点上传。
2.断点下载和断点上传的实现:
所谓断点下载,类似于我们在app商城中下载应用,下载期间因某事中断该下载后,继续下载可以接着原来的进度进行下载,可提升传输效率。断点上传同理
2.1 断点下载
断点下载,类似于接力赛,2道的选手要接力1道选手完成后序的任务。在这里,我们需要在客户端计算出文件已经有多大了,然后把文件大小发给服务端,服务端再将文件指针偏移到收到的文件大小处后,开始发送给客户端,即完成断点下载。
2.2 断点上传
断点上传的话相比于下载稍微复杂一些。由于是多用户访问服务器端,我们在上传文件的时候,需要区分文件所属(即是哪个用户上传的)。例如,用户1上传文件a,传到一半暂停后(这时,服务器端的数据库里已经有了用户1和文件a的相关信息),有某一个用户也上传文件a。这时,服务器端需要判断这“ 某一个用户”是否为用户1。若是的话,就可以进行断点上传。若不是,则说明是其他用户在上传该文件a,这时文件a是需要完整重传的。
同样,断点上传完毕后文件完整标志位置1,方便后面用户下载。
最后来说说下载上传的实现细节
对于客户端的文件下载方法:
- 判断输入的文件名是否存在。存在则进行下一步
- 发送下载命令给服务器,收到服务器回复的消息(包括服务器端该文件是否存在,以及文件是否完整,不完整则不允许下载)
- 判断客户端是否已存在该文件,若不存在,则需要新建该文件用于后面从服务端来的文件内容写入该新建文件中。若存在,则需要判断客户端文件大小和服务端文件大小,这二者之间的关系。若客>服,说明客户端该文件已被人为修改过,需要重新从服务端下载(客户端发送ok告诉服务器开启传输,传输完成后发送over告诉其终止传输)。若客=服,说明不需要下载(客户端发送over告诉服务器终止传输)。若客<服,说明文件内容不完整需要断点下载(待传输完成后发送over告诉其终止传输)。
对于服务端响应客户端文件下载的方法:
- 判断收到的文件名在服务端是否存在或是否能被打开
- 服务器端开辟多个线程,根据客户端发送的ok或是over进行文件传输,传输过程中用poll监测异常断开事件。
//
对于客户端的文件上传方法:
- 判断文件名是否存在(包括是否能打开文件和文件名是否存在)。存在则进行下一步
- 发送上传命令给服务器,同时开一个进程用以计算文件的md5值并发送给服务器。服务器根据md5值判断是否已经存在该文件,若存在且完整(服务器返回exist表示存在且完整),客户端告知服务器结束传输完成秒传(向服务端发送over终止)。若不存在亦或是存在但不完整,客户端发服务端发送begin告知服务端开始上传文件,同时根据服务端对文件是否完整选择是重传还是断点上传。
对于服务端响应客户端文件上传的方法:
- 服务端接收到begin开始准备上传文件
- 先匹配md5值且判断文件是否完整,来进行是否秒传的选择
- 若md5不匹配或者文件不完整,服务器先接收客户端已存在部分文件大小与服务端本地文件大小进行比较来选择重传或者断点上传(注:这里作者的逻辑我有些没看懂,好像只实现了断点重传时对不同用户的文件进行判断的问题(不同用户对断点重传是采用删除重传的方法,感觉有点bug)。
2.3大文件传输
Linux下open函数只能打开2G以内的文件,若大于2G,open会执行失败。
综合了一些网上相关文章,原因在于:
32位Linux系统内部处理文件档案是采用的指标定义为long,在32位系统上,long为4字节即32位,因此只能寻址2^(32-1)=2G的范围。
更具体的,使用open,lseek函数操作文件时,lseek原型为
long lseek(int handle, long offset, int fromwhere);
lseek函数用于指定文件操作指针的偏移量,其中offset参数表示偏移量大小,可用来将文件指针偏移用于对文件进行写入等操作。
那么要实现超过2G文件的传输,可采用如下方法:
open函数原型:
int open(const char *pathname, int flags, mode_t mode);
flags参数表示打开文件时所采用的操作,必选项包括只读,只写,可读可写三种操作之一;
另外用追加、截断、创建等操作与三个必选项按位或。
第三个参数表示文件访问的权限,只有在第二个参数为创建时才有效。
- 在open函数中,flags 使用O_LARGEFILE 表示打开大文件。(或者直接使用open64函数打开)
- 改用lseek64, ftruncate64(),atoi() 函数要改为 atoll()等。
- 获取文件大小由int类型改为long long 类型,输出文件大小用%lld。
3.sendfile 零拷贝
在作者提供的源码v9版本中,作者使用了sendfile技术;而v12版本中,我没看到sendfile的使用,而是采用多线程实现分布式,采用IO多路复用中的poll实现对异常断开事件的监测。
所谓的零拷贝技术,就是指在传统的read/write中,会发生多次CPU拷贝操作。而采用零拷贝技术后,直接将磁盘里的数据读取到操作系统的内核缓冲区,这样子就减少了数据拷贝,和上下文切换的次数。
具体参见文章:Linux IO复用技术与零拷贝。
4 演示图:
4.1 启动服务端:
4.2 启动客户端并注册新用户
登录成功:
4.3 上传小文件:
客户端
服务端:
4.4 上传大文件并断点续传:
客户端上传过程中强制中止:
服务端显示
客户端重新登录并继续上传该文件:(会先得到已经上传的文件大小)
服务端显示:
4.5 秒传的实现:
客户端重新上传300M.mp4文件:
服务器端显示,(因为之前已上传过该文件)
4.6 不同用户上传同一文件,其中某一个用户中途退出
删除3_4G.mp4文件后,使用用户1重新上传3_4G.mp4,中途中止,
接着用户2上传3_4G.mp4,
服务器显示:
由于属于不同用户,该文件必须重传而不能续传。
5 待完善的地方
5.1 一点小bug
通过rm-f命令在bash界面删除文件,md5表中仍然保留了文件的md5值。这算是一个小bug。
5.2 自己运行源码时有问题的地方
源代码下载下来后,直接运行会出现各种各样的问题。其中有一个关于mysql的问题,提示
错误如下:
2058: This handle is already connected. Use a separate handle for each connet
该问题是由于源代码中出现多次下列语句,比如client/user.c文件中27行,已经建立了一个mysql连接,后面if语句里面又来了一个判断,所以会报错,mysql的句柄重复使用。
该问题在源代码中出现次数比较多,亲测删除后就可以运行了。
修改过后的代码链接如下:
https://download.csdn.net/download/qq_35027690/21736932