你也可以手敲一个高速下载器(十四)保存下载块(下)

157 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情


你也可以手敲一个高速下载器(十四)保存下载块(下)

前言

上一节我们提到了保存下载块的方案,其中包括什么时候该选择实例保存下载块,怎么进行保存,我们提到了在保存的时候可以把每一块下载的数据直接保存到相对应的位置上面去,这样的话还需要剩余内存做临界点吗?其实我们都忽略了一个数据,就是concurrency这个数据是用来控制并发的,也就是说可以同时下载多少数据,使用这个数据就可以用着最大可以占用多少内存,所以我们上一节提到的方案中剩余内存还是可以用到的

最大并发数

我们这里采用默认64并发,最大剩余内存数 / 2 / 最大块大小10M并发数的方案,然后在命令中添加并发量参数,示例代码如下:

    	self.max_block_size = 10 * 1024 * 1024
    	
    	# 根据剩余内存大小最大并发数,并初始化信号量
        free_memory = psutil.virtual_memory().available
        max_concurrency = int(free_memory / 2 / self.max_block_size)
        concurrency = max(concurrency, max_concurrency)
        self._sem = asyncio.Semaphore(concurrency)

保存数据

上一节说到,可以使用f.seek()控制偏移量的方式,而我们的分段下载也有一个开始和结束段,所以可以直接使用这个开始字节数量作为偏移量,我们稍微修改一下试试:

    async def start(self):
        """
        开始下载
        :return:
        """
        ......
		# 保存文件
        # await self.save_file(self.save_path / self.save_name, b''.join([d['content'] for d in result]))

    
    async def save_file(self, filepath, content, offset=0):
        """
        保存文件
        :param offset:
        :param filepath: 文件路径
        :param content: 内容
        :return:
        """
        async with aiofiles.open(filepath, mode='wb') as f:
            await f.seek(offset)
            await f.write(content)
            
	async def get_content(self, index, start=None, end=None):
        ......
        req = Request(self.method, self.url, sem=self._sem, headers=headers, data=self.data)
        try:
            ......
        else:
            await resp.aclose()
            await self.save_file(self.save_path / self.save_name, content, offset=start)
		......

如上所示我们修改了保存文件的函数,并在里面加了offset参数,用来控制偏移量,在进入写入之前先设置一次偏移量。接下来在获取数据的时候直接调用保存文件的方法,进行保存,然后偏移量的值就是开始的字节,看起来一起都很好,让我们来试一下:

运行第一次:

运行第二次:

我们会发现一个神奇的现象:就是同样的代码,同样的文件,但每次下载下来的大小却不同,这时由于对同一个文件多次打开并进行写入操作造成的,我们只要修改为,使用同一个文件描述符即可,修改代码如下:

	def __init__(...):
        ......
        # 文件对象
        self.file = open(self.save_path / self.save_name, 'wb')
    
    async def start(self):
        """
        开始下载
        :return:
        """
        ......
        self.file.close()
    
    async def save_file(self, content, offset=0):
        """
        保存文件
        :param offset:
        :param content: 内容
        :return:
        """
        self.file.seek(offset)
        self.file.write(content)

这里我们把文件在初始化的时候进行打开,然后在下载结束的函数进行关闭就好了,同时由于我们只操作一个文件,而且是在最后操作的,直接使用自带的文件就可以了,无需使用aiofiles操作文件了,这次在看下运行结果:

这次就直接下载成功。

结语

我们这次的保存分块就结束了,同时这也是为后面的重启续传的铺垫,欢迎持续关注后续开发,重启续传、下载记录、命令查看日志、清空日志、GUI界面等更多功能敬请期待!!!