goLang 实现并发切片上传和下载文件

本文介绍了一个使用Go实现的并发切片文件上传和下载系统,旨在熟悉Go语言,涉及知识点包括goroutine、通道chan、结构体和方法等。系统支持断点续传、分片失败重传、最大并发数控制,并通过HTTP协议传输文件。文章详细阐述了上传和下载流程,以及项目代码结构和使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.实现目的

主要目的是用来熟悉go语言,通过该项目可以熟悉到的go知识点:

  1. go语言语法;
  2. go的goroutine使用方式;
  3. go通道chan的使用;
  4. 等待所有goroutine结束的同步信号使用;
  5. go的结构体定义和方法使用;

2.实现的功能点

  1. 支持批量上传下载文件,并进行md5值校验;
  2. 支持查看文件列表;
  3. 支持断点续传和下载文件;
  4. 支持大文件切片上传和大文件切片下载;
  5. 支持分片失败重传和失败重下载;
  6. 支持控制每个文件上传和下载的最大goroutine数量;
  7. 服务端根据配置文件读取保存文件目录、绑定IP和端口号信息等。

3.实现设计

3.1.总体上传下载文件结构图

3.2.上传文件流程图

流程概述:

(1)上传文件时首先判断文件是否大于1M,如果小于1M则没必要进行分片,直接整个文件通过HTTP请求发送到Server端进行保存;

(2)如果文件大于1M,则首先判断该文件是不是上传了一部分的文件,这个通过查找当前目录下有没有对应元数据文件来判断,如果没有则是全新要上传的文件,否则是需要断点续传的文件;

(3)如果是全新要上传的文件,则会先对该文件进行计算,比如该文件能被切分成多个个分片,生成上传uuid,作为唯一上传标识,并把这些数据保存到本地文件(创建一个隐藏文件,后缀加上.uploading);

(4)同时还需向服务端先发送一个请求,让其先在保存目录创建一个uuid的目录,用来待会保存分片文件使用,同时服务端也会生成一个元数据文件;

(5)如果是需要断点续传的文件,则需要请求服务端获取该文件还缺少哪些文件分片没有上传(服务端只需从保存目录中已保存的序号结合文件元数据即可识别到哪些序号还没上传),并将这些切片序号发送回给客户端,假设有1,5,6,7,没发送,则服务端只需要发送1,5,-1即可标识,1分片和5到最后一个分片都没上传成功;

(6)在上传前会先启动一个goroutine,专门用来重传失败分片的,它会不断的从RetryChannel通道中读分片数据,如果没有则阻塞,如果有则重传该分片,如果再失败则再发送到通道中,可以看做当队列使用;

(7)开始读文件,如果是续传的,还可以根据续传情况进行偏移量偏移,跳过指定的切片段,读时每次只读1M,然后判断该切片是否已被上传过,如果上传过则无须再上传,可直接跳过,否则就创建一个goroutine进行异步上传;

(8)当所有goroutine都运行完成表示切片都上传完成了则发送请求告诉服务端切片已上传完成,服务端也会把文件状态置为active。

3.3.下载文件流程图

流程概述:实现思路跟上传相似,这里不再概述。

3.4.核心的设计点

(1)文件分片

将大文件进行分片,定义分片规格为1M,比如有5M大小的文件,那么就会分成文件名为0,1,2,3,4,5这6个文件,上传到服务端后,服务端会创建一个uuid的文件夹用来保存这5个分片文件,并且会记录一个元数据文件,里面保存着该元数据文件对应哪个目录,文件切片大小、文件大小和文件md5等原信息。对于小于1M的普通文件则不进行切片处理,就是正常的一个文件,所以我程序里规定了文件名为.slice结尾的文件则为切片文件,否则为普通文件。

(2)断点续传和断点续载

首先是断点续传,在开始传文件时,我会创建一个隐藏文件作为上传元数据文件,比如文件名是file.txt,则我创建的元数据文件名为.file.txt.uploading,里面记录着文件元数据信息,比如上传UUID、文件大小和文件md5等信息,如果用户上传完成,则起最后会被删除掉,表示整个文件都上传完成了,假设上传过程中出现了中断,则下次重新上传该文件时我检测到该隐藏文件就知道它是还未上传完整的文件,会先去服务端请求看缺少哪些分片数据,比如该文件一共有1,2,3,4,5片,服务端响应回来说只收到了1,3,5片,那么待会我就只需要把0,2,4片重传一次即可。

断点续载同理,也是需要在客户端维护元数据,且通过查找已下载的分片来找出未下载的分片序号,然后只需要重新下载没有的分片即可。

(3)失败重传

上传器和下载器的结构体定义我都会定义一个RetryChannel,这是一个分片结构体类型的通道,当分片上传或下载失败时,会将分片发送到这个通道,在上传或下载开始时我都会启动一个goroutine,专门负责从这个通道读数据,读到了就对这个分片进行重新上传或下载。

(4)并发上传或下载

并发使用了go的goroutine,并发单位以文件切片为单位,同时通过通道(申请有数量限制的通道)的方式控制运行的goroutine的数量,同时采用go里的同步信号量来控制是否所有goroutine都运行完成了。

3.5.项目代码结构

客户端目录结构:

上传器结构体定义:

// Uploader 上传器
type Uploader struct {
    common.FileMetadata             // 文件元数据
    common.SliceSeq                 // 需要重传的序号
    waitGoroutine   sync.WaitGroup  // 同步goroutine
    NewLoader       bool            // 是否是新创建的上传器
    FilePath        string          // 上传文件路径
    SliceBytes      int             // 切片大小
    RetryChannel    chan *FilePart  // 重传channel通道
    MaxGtChannel    chan struct{}   // 限制上传的goroutine的数量通道
    StartTime       int64           // 上传开始时间
}

下载器结构体定义:

// Downloader 下载器
type Downloader struct {
    common.FileMetadata                 // 文件元数据
    common.SliceSeq                     // 需要重传的序号
    waitGoroutine   sync.WaitGroup      // 同步goroutine
    DownloadDir     string              // 下载文件保存目录
    RetryChannel    chan int            // 重传channel通道
    MaxGtChannel    chan struct{}       // 限制上传的goroutine的数量通道
    StartTime       int64               // 下载开始时间
}

服务端目录结构:

文件的传输采用的是HTTP协议,服务端的工作主要是起一个HTTP Server,然后监听对应URL,绑定对应的响应方法,同时把接收到的文件数据保存到指定目录下。

4.使用示例

使用方法可用–help查看:

上传、列出和下载使用方法示例(图中的下载路径和文件路径可以自行修改,且确保服务端FtpServer目录下的etc目录下的config.json文件里指定的StoreDir指定目录存在):

服务端先启动:进入项目目录,执行:go run main.go

上传文件示例:

客户端运行:

服务端响应:

 列出文件列表示例:

下载文件示例:

5.可改进点

当然,这个小项目可改进点还有很多,我这里列出几个我想到的:

(1)需重传的序号计算算法还可以实现的更好点,比如还没传的,可以用1-3,5-9这样来表示;

(2)当前的md5计算是计算整个文件的,但其实可以给每个分片都赋予一个md5,这样就不用再最后累计一边整个文件的md5,降低读IO次数;

(3)下载文件时,其实可以开辟一个指定size的空洞文件,然后接收到文件分片可以按照偏移量写到给新文件中,避免了最后一步的合并过程的IO。

6.项目下载地址

Client端项目github地址:GitHub - luohaixiannz/FtpClient

Server端项目github地址:GitHub - luohaixiannz/FtpServer

本书作者带你一步一步深入这些方法。你将理解 Go语言为何选定这些并发模型,这些模型又会带来什么问题,以及你如何组合利用这些模型中的原语去解决问题。学习那些让你在独立且自信的编写与实现任何规模并发系统时所需要用到的技巧工具。 理解Go语言如何解决并发难以编写正确这一根本问题。 学习并发与并行的关键性区别。 深入到Go语言的内存同步原语。 利用这些模式中的原语编写可维护的并发代码。 将模式组合成为一系列的实践,使你能够编写大规模的分布式系统。 学习 goroutine 背后的复杂性,以及Go语言的运行时如何将所有东西连接在一起。 作者简介 · · · · · · Katherine Cox-Buday是一名计算机科学家,目前工作于 Simple online banking。她的业余爱好包括软件工程、创作、Go 语言(igo、baduk、weiquei) 以及音乐,这些都是她长期的追求,并且有着不同层面的贡献。 目录 · · · · · · 前言 1 第1章 并发概述 9 摩尔定律,Web Scale我们所陷入的混乱 10 为什么并发很难? 12 竞争条件 13 原子性 15 内存访问同步 17 死锁、活锁饥饿 20 确定并发安全 28 面对复杂性的简单性 31 第2章 对你的代码建模:通信顺序进程 33 并发与并行的区别 33 什么是CSP 37 如何帮助你 40 Go语言的并发哲学 43 第3章 Go语言并发组件 47 goroutine 47 sync包 58 WaitGroup 58 互斥锁读写锁 60 cond 64 once 69 池 71 channel 76 select 语句 92 GOMAXPROCS控制 97 小结 98 第4章 Go语言的并发模式 99 约束 99 for-select循环103 防止goroutine泄漏 104 or-channel 109 错误处理112 pipeline 116 构建pipeline的最佳实践 120 一些便利的生成器 126 扇入,扇出 132 or-done-channel 137 tee-channel 139 桥接channel模式 140 队列排队143 context包 151 小结 168 第5章 大规模并发 169 异常传递169 超时取消 178 心跳 184 复制请求197 速率限制199 治愈异常的goroutine 215 小结 222 第6章 goroutineGo语言运行时 223 工作窃取223 窃取任务还是续体 231 向开发人员展示所有这些信息 240 尾声 240 附录A 241
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值