文件差分服务设计

该博客介绍了如何利用hdiffpatch工具进行文件差分服务设计,以实现高效的OTA升级。通过计算差分包,设备仅需下载小体积的差分数据,从而降低带宽需求,加快升级速度,节约存储空间。文中详细阐述了升级流程、方案设计,包括技术路线、技术对比和选择hdiffpatch的原因,并进行了基本测试,验证了在不同情况下的性能优势。
摘要由CSDN通过智能技术生成

需求

OTA(Over-The-Air)升级是一种至关重要的技术,用于更新嵌入式设备的固件或软件,以确保设备具备最新功能和修复漏洞。在OTA升级过程中,使用差异算法工具(如bsdiff、hdiffpatch和xdelta3)能够有效减小升级包的大小,从而降低带宽消耗并提高升级效率。以下是对差异算法在OTA升级中的重要性的更详细说明:

  1. 带宽节省:通过使用差异算法,云端可以提取新旧升级包之间的差异数据,生成相对较小的差异包。这意味着只需传输差异包,而不是整个新升级包。这极大地降低了数据传输所需的带宽,尤其对于大型升级包而言。

  2. 成本降低:减少传输数据量不仅有助于带宽节省,还能减少数据传输费用。这对于云端提供OTA服务的成本管理非常关键。

  3. 快速升级:较小的差异包能更快速地下载到设备上,且设备更容易处理和应用差异数据。这减少了升级过程所需的时间,有助于确保用户设备迅速获取最新功能和修复。

  4. 存储空间节省:设备的存储空间是有限的,较小的差异包需要更少的存储空间,对设备而言非常有益。

  5. 版本控制:差异算法还能轻松进行版本控制,确保设备获取正确的升级。

升级流程

OTA升级流程:

  1. 计算差分包:云端根据升级需求,调用差分服务,输入文件 V1(当前版本)和文件 V2(新版本)来计算差分包 PATCH(差异数据)。hdiffpatch 将生成一个相对较小的 PATCH 差分包,其中包含了新版本相对于当前版本的差异信息。

  2. 通知设备下载差分包:云端通知设备有可用的升级。设备收到通知后,开始下载差分包 PATCH。

  3. 应用差分包:设备成功下载差分包 PATCH 后,它使用文件 V1(当前版本)和差分包 PATCH 来还原文件 V2(新版本)。设备内部的 hdiffpatch 工具将应用 PATCH 差异数据到当前版本文件 V1 上,生成新版本文件 V2。这个过程是快速的,因为 PATCH 包是相对较小的,只包含了需要修改的数据。

  4. 安全性检查:设备在应用差分包后,可以进行安全性检查来确保新版本文件 V2 完整和正确。这可以包括校验文件的哈希值或数字签名,以防止潜在的数据损坏或恶意修改。

  5. 更新完成通知:设备在成功应用差分包且通过安全性检查后,通知云端升级完成。云端记录设备已完成升级,以便进行版本跟踪和管理。

通过这一流程,我们能够充分利用 hdiffpatch 差异算法,将升级包的大小减小,降低了升级过程中的带宽需求,同时保持了升级的效率。这对于嵌入式设备的OTA升级是一个优化的、可行的方案。

方案设计

技术路线

  • BsDiff:BsDiff(Binary Software Differential)是一种用于生成二进制文件差异(差异数据)的算法。通常用于比较原始文件和新文件,生成差异文件,然后将差异文件应用到原始文件,生成新文件。BsDiff的核心思想是将二进制文件划分成块,计算块之间的差异,然后将这些差异编码成差异文件。BsDiff算法的目标是生成最小的差异数据,以在生成和应用差异时尽可能减小文件大小并提高效率。它在软件更新、版本控制等领域非常有用,因为它减小升级文件的大小,降低下载和存储成本。

  • hdiffpatch:hdiffpatch 是另一个差异生成和应用工具,专门用于减小文件大小,特别是用于固件和软件的更新。它被设计为高性能、低内存占用的工具,可以生成小差异文件以减小升级包的大小,从而节省带宽和提高升级效率。hdiffpatch的基本原理是将文件划分成块,找到相匹配的块,计算不匹配块之间的差异,将差异数据编码成差异文件,然后使用差异文件来应用差异数据以生成新文件。hdiffpatch强调高性能和低内存占用,适用于嵌入式设备和固件更新等场景。它的主要优势在于生成较小的差异文件,减小升级包的大小,降低带宽消耗,以及节省存储空间。

技术对比

BsDiff 和 hdiffpatch 都是用于生成和应用二进制文件差异的算法,用于减小升级包的大小,从而节省带宽和降低成本。它们有各自的特点和优势,下面对它们进行比较:

BsDiff:

  1. 基本原理:BsDiff 将文件划分成块,找到匹配的块,计算不匹配块之间的差异,然后编码差异文件。差异文件可以应用到原始文件上,生成新文件。

  2. 优势

    • 生成的差异文件通常较小,节省带宽。
    • 被广泛用于软件更新、版本控制等领域。
    • 相对成熟的开源实现可供使用。
  3. 劣势

    • 生成和应用差异的性能可能不如 hdiffpatch。
    • 对于某些文件类型,可能生成较大的差异文件。

hdiffpatch:

  1. 基本原理:hdiffpatch 也将文件划分成块,找到匹配的块,计算不匹配块之间的差异,然后编码差异文件。差异文件可以应用到原始文件上,生成新文件。

  2. 优势

    • 高性能,生成和应用差异的速度较快。
    • 低内存占用,适合嵌入式设备等资源受限的环境。
    • 生成的差异文件通常较小,节省带宽和存储空间。
    • 支持多平台,可在不同操作系统上使用。
  3. 劣势

    • 可能不如 BsDiff 在某些文件类型上表现出色。
    • 开源实现相对较新,可能需要更多的定制和集成工作。

技术选型

考虑到本次方案将面向多种嵌入式设备arm和x86架构,我们决定采用 hdiffpatch 作为差异算法的核心。主要原因如下:

  1. 通用性:hdiffpatch 是一种通用性较强的差异算法,适用于各种嵌入式设备。这意味着我们可以在不同设备上实现统一的OTA升级方案,而不需要为每种设备单独开发和维护不同的差异算法。

  2. 高性能:hdiffpatch 被设计为高性能工具,特别是在生成和应用差异数据时表现出色。对于无人车等对性能要求较高的设备,这一特点尤为重要,因为它可以加速升级过程,降低用户设备的升级时间。

  3. 低内存占用:嵌入式设备通常拥有有限的内存资源,hdiffpatch 的低内存占用使其非常适合这些资源受限的环境。这将有助于确保升级过程不会占用过多内存,从而不影响设备的正常运行。

  4. 小差异文件:hdiffpatch 生成的差异文件通常较小,减小了升级包的大小。这对于需要在有限带宽下进行OTA升级的设备(例如无人车)非常重要。

  5. 多平台支持:hdiffpatch 支持多种操作系统和平台,这意味着可以在不同嵌入式设备上使用,无论其运行的操作系统是什么。这种灵活性将简化方案的部署和维护。

  6. 版本控制:差异算法允许进行版本控制,确保设备得到正确的升级。这在嵌入式设备的OTA升级中至关重要,因为需要确保安全性和正确性。

因此,通过采用 hdiffpatch 差异算法,我们可以实现一种通用、高性能、低内存占用的OTA升级方案,适用于多种嵌入式设备。这将有助于确保设备快速、安全地获得最新的功能和修复,同时减小了部署和维护成本,提高了升级效率。

基本测试

升级示意图

下图展示了通过两种方式,将图1升级到图片2升级的过程
在这里插入图片描述

极端情况测试
  • 文件完全不一致

    在文件完全不一致的情况下,采用2张完全不一样的图片进行测试,1.jpg2.jpg 内容完全不一致,普通升级需要传输 596KB 数据,差分升级需要传输 488KB 数据。

    文件类型普通升级差分升级
    1.jpg629 KB629 KB
    2.jpg596 KB0 KB
    PATCHA文件0 KB488 KB
  • 文件增量

    在文件增量情况下,采用2份txt文件进行测试,2.txt1.txt 基础上进行了字符串增加,普通升级需要传输 119KB 数据,差分升级仅需要传输 0.11KB 数据。

    文件类型普通升级差分升级
    1.txt11.9 KB11.9 KB
    2.txt119 KB0 KB
    PATCHA文件0 KB0.11KB

参考资料

  • 项目地址

    https://github.com/sisong/HDiffPatch/releases/tag/v4.6.7

  • SDK

    linux64

    arm64

    android

    windows64

    source code

  • java参考代码

    HDiffPatch 的官方实现是基于 C/C++ 的库。虽然它没有官方的 Java 实现,但你可以通过 Java 的 JNI(Java Native Interface)机制来调用 C/C++ 库中的函数。以下是一个简单的示例,演示如何使用 JNI 在 Java 中调用 HDiffPatch 的 C/C++ 函数。

    首先,需要编写一个 Java 类,用于加载 HDiffPatch 动态链接库并定义与 C/C++ 函数的映射。以下是一个示例 Java 类:

    import java.io.*;
    import java.nio.file.*;
    import java.nio.ByteBuffer;
    
    public class HDiffPatch {
    	static {
    		System.loadLibrary("hdiff");  // 请根据你的库名称进行修改
    	}
    
    	// JNI 声明:生成差异数据
    	private static native int createDiff(byte[] oldData, byte[] newData, byte[] diffData);
    
    	// JNI 声明:应用差异数据
    	private static native int applyDiff(byte[] oldData, byte[] diffData, byte[] newData);
    
    	public static void generateDiffData(String oldFilePath, String newFilePath, String diffFilePath) {
    		try {
    			byte[] oldData = Files.readAllBytes(Paths.get(oldFilePath));
    			byte[] newData = Files.readAllBytes(Paths.get(newFilePath));
    
    			byte[] diffData = new byte[Math.max(oldData.length, newData.length)]; // 设置差异数据的大小
    
    			int result = createDiff(oldData, newData, diffData);
    
    			if (result == 0) {
    				Files.write(Paths.get(diffFilePath), diffData);
    				System.out.println("差异数据生成成功!");
    			} else {
    				System.out.println("差异数据生成失败。");
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void applyDiffData(String oldFilePath, String diffFilePath, String newFilePath) {
    		try {
    			byte[] oldData = Files.readAllBytes(Paths.get(oldFilePath));
    			byte[] diffData = Files.readAllBytes(Paths.get(diffFilePath));
    
    			byte[] newData = new byte[oldData.length + diffData.length]; // 设置新文件的最大可能大小
    
    			int result = applyDiff(oldData, diffData, newData);
    
    			if (result == 0) {
    				Files.write(Paths.get(newFilePath), newData);
    				System.out.println("差异数据应用成功!");
    			} else {
    				System.out.println("差异数据应用失败。");
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void main(String[] args) {
    		String oldFile = "old.bin";
    		String newFile = "new.bin";
    		String diffFile = "diff.bin";
    		String restoredFile = "restored.bin";
    
    		// 生成差异数据
    		generateDiffData(oldFile, newFile, diffFile);
    
    		// 应用差异数据
    		applyDiffData(oldFile, diffFile, restoredFile);
    	}
    }
    

    上述示例代码假定你已经编译了 HDiffPatch 的 C/C++ 动态链接库(.so 文件),并将其放在正确的位置。你需要根据你的库文件名和路径进行修改。请注意,这是一个简单的示例,你可能需要根据你的项目需求和环境进行更复杂的设置和错误处理。

  • c++参考代码

    下面是一个基本的 C++ 示例,展示如何使用 HDiffPatch 来生成差异数据和应用差异数据以还原文件。请注意,HDiffPatch 需要在项目中添加相关的头文件和链接到库文件。

    #include <iostream>
    #include <vector>
    #include "HDiff/diff.h"  // 请根据你的项目实际情况包含正确的头文件
    
    int main() {
    	// 原始文件数据
    	std::vector<uint8_t> oldData = {1, 2, 3, 4, 5};
    
    	// 新文件数据
    	std::vector<uint8_t> newData = {1, 2, 3, 9, 5};  // 更改第四个字节的值
    
    	// 差异数据存储
    	std::vector<uint8_t> diffData;
    
    	// 生成差异数据
    	int result = create_diff(oldData.data(), oldData.size(), newData.data(), newData.size(), diffData);
    
    	if (result == 0) {
    		// 差异数据生成成功,你可以保存它或传输给其他设备
    		std::cout << "差异数据生成成功!" << std::endl;
    	} else {
    		std::cout << "差异数据生成失败。" << std::endl;
    		return 1;
    	}
    
    	// 还原新文件
    	std::vector<uint8_t> restoredData;
    	restoredData.resize(oldData.size() + diffData.size());
    
    	result = apply_diff(oldData.data(), oldData.size(), diffData.data(), diffData.size(), restoredData.data(), restoredData.size());
    
    	if (result == 0) {
    		// 新文件已还原,你可以保存它
    		std::cout << "新文件还原成功!" << std::endl;
    	} else {
    		std::cout << "新文件还原失败。" << std::endl;
    		return 1;
    	}
    
    	return 0;
    }
    
  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

random_2011

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值