Linux文件服务器上传下载

268 阅读4分钟

成功有内容回复ok#<数据>

成功无内容回复ok#

用户发来命令后,解析并通过exec的方式执行相关命令,返回相应结果。可能需要返回执行命令的结果,比如命令打印到屏幕的信息需要返回给用户,这时我们把子进程写入stdout的数据写入无名管道(需要用dup系列方法将管道的写端覆盖stdout),父进程从无名管道中拿到数据返回给用户

交互部分直接使用exec的方式实现,上传下载的操作需要封装函数完成

int dup(int oldfd)

int dup2(int oldfd, int newfd)

vscode调试的时候需要将launch.jsonprogram修改为需要调试的ELF文件

目前execvp方法不支持通配符*

  1. 管道需要循环读取
  2. 支持cd等命令

设计文件传输协议:注意粘包问题(两个数据包一次recv的收到了,此时就会阻塞在下一次recv)

服务器必须先告知客户端文件的大小,因为客户端如果不知道需要接收多少数据,就不知道何时停止。 只有当连接关闭的时候,recv返回值才为0,而服务器不会主动断开连接

需要注意的是,当客户端socket关闭时,服务器再写入数据则会收到内核发送的SIGPIPE,导致服务器崩溃

解决方法可以是服务器发一次数据,然后接收一次数据,若recv返回值为0,则表示客户端关闭。缺点是需要发送更多的次数

获取文件的权限

struct stat  
{   
    dev_t       st_dev;     /* ID of device containing file -文件所在设备的ID*/  
    ino_t       st_ino;     /* inode number -inode节点号*/    
    mode_t      st_mode;    /* protection -保护模式?*/    
    nlink_t     st_nlink;   /* number of hard links -链向此文件的连接数(硬连接)*/    
    uid_t       st_uid;     /* user ID of owner -user id*/    
    gid_t       st_gid;     /* group ID of owner - group id*/    
    dev_t       st_rdev;    /* device ID (if special file) -设备号,针对设备文件*/    
    off_t       st_size;    /* total size, in bytes -文件大小,字节为单位*/    
    blksize_t   st_blksize; /* blocksize for filesystem I/O -系统块的大小*/    
    blkcnt_t    st_blocks;  /* number of blocks allocated -文件所占块数*/    
    time_t      st_atime;   /* time of last access -最近存取时间*/    
    time_t      st_mtime;   /* time of last modification -最近修改时间*/    
    time_t      st_ctime;   /* time of last status change - */    
};  
struct stat file_info = {0};
stat(file_name, &file_info);
*mode = file_info.st_mode;

获取文件的大小的两种方式:

int get_size_1(char* file_name){
    struct stat file_info = {0};
    stat(file_name, &file_info);
    return file_info.st_size;
}

int get_size_2(char* file_name){
    int fp = open(file_name, O_RDONLY);
    int size = lseek(fp, 0, SEEK_END);;
    close(fp);
    return size;
}
int fp = open(file_name, O_RDONLY);
// 将读写位置移到文件开头,并返回0
lseek(fp, 0, SEEK_SET); 
// 将读写位置移到文件末尾,返回文件末尾的偏移量
int file_size = lseek(fp, 0, SEEK_END); 
// 获取文件读写位置相对于开头的偏移量
int cur = lseek(fp, 0, SEEK_CUR);

断点续传实现思路

  1. 客户端下载的时候,在没下载完成前先创建一个临时的.tmp文件,下载完成才将这个临时文件重命名成和服务器端相同的文件名。
  2. 下载文件的时候,客户端首先在本地目录查找是否有对应的临时文件,如果有则把这个文件的大小发送给服务器。服务器偏移对应的字节数发送即可,服务器把文件以追加的形式打开再次写入即可。

一个的设计问题:服务器和客户端之间的交互

服务器只有一台,且协议版本是不断升级的,然而客户端所用的版本可能不太统一,有的可能使用老的传输协议,有的使用新的传输协议。服务器升级后,再和老版本的客户端交互可能就会造成数据解析错误的问题。

解决方法可以是:客户端连接的时候就把自己的版本号发送给服务器,服务器根据客户端的版本号选择相应的函数与客户端进行交互,解析客户端的数据。

image.png

关于文件校验

客户端发送下载请求后,服务器除了发送文件的大小还需要发送该文件的MD5值给客户端,客户端下载完成后,计算自己本地文件的MD5,对比两个MD5值即可完成校验。然后给用户相应的提示即可。

image.png

关于秒传

为节省服务器磁盘空间以及节省带宽,客户端上传文件的时候,首先让客户端把该文件的加密串发给服务器,服务器校验这个加密串是否已经存在,若存在则可以给这个用户的账户新建一个快捷方式,告诉客户端已经上传完成。

一个用户上传一个文件后,若后面的用户也上传相同的文件,即可使用校验加密串以及新建快捷方式的方法实现秒传。若其中的用户想自己上传的删除文件,服务器删除快捷方式即可,源文件依然存在。若这个文件没人引用了,那么服务器可以删除源文件,或者保留一定时间后依然没人引用即可删除。

其实秒传的加密算法一般不使用MD5,因为MD5的加密串是32位的,如果服务器的文件数量超过2322^{32} ,就肯定会出现MD5值冲突的情况

image.png

源代码仓库:github.com/BugMaker-sh…