手把手教你在Java后端使用bsdiff实现增量更新

Posted XeonYu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你在Java后端使用bsdiff实现增量更新相关的知识,希望对你有一定的参考价值。

之前写过一篇博客是:手把手教你在Android中使用bsdiff实现文件增量更新
由于android Studio自带NDK的环境,所以实现JNI是比较简单的。
但是在博客中也说到了,文件差分的功能肯定是要在服务端进行的。而服务端运行的环境可能是在window,mac或者linux等。所以,我们需要把bsdiff的源码编译成对应环境需要的 native 库文件,以便于给Java调用。
由于大多数项目基本都是在linux上运行的,这里我就只演示下如何把bsdiff源码编译成so文件,然后在java代码中调用。

废话不多说,开始干!


Clion搭建编译环境

这里我使用的Jetbrains全家桶中的Clion. Jetbrains的IDE不用多说了,懂得都懂。

首先是需要准备一个Linux的系统,Windows的话本地调试推荐直接用WSL安装ubuntu。安装方式也比较简单,按照官方文档走即可。WSL安装文档:

如果是MAC的话,本地调试推荐使用docker去安装linux。在使用Docker作为Clion的编译环境时,一定要安装那种已经配置过cmake和gcc等编译环境的镜像,否则Clion识别不出来,我之前是先安装的ubuntu,然后再安装cmake,gdb等环境,结果Clion死活找不到,一直提示未找到安装包,在这个地方卡了快一天,给我整的都怀疑人生了。后来还是按照官方文档的方式才成功的,官方文档:Using Docker with CLion

jetbrains在github上也提供了几个写好的Dockerfile,自己根据需要选择即可。clion-remote

另外一种是比较推荐的方式,去阿里云购买一个云主机,直接远端编译,然后把写好的代码发布到远端进行测试,windows和mac都能用,比较方便。

安装完linux环境准备好之后需要安装cmake,gdb等,这里就不多BB了。安装后去Clion配置下工具链,根据自己需要配置,如下。

然后是配置cmake

至此环境就配置好了


在Linux上将C编译成so

在之前Android使用bsdiff的博客中,我们已经对bsdiff做了处理了,也能在Android上打出so。
那在java web项目中也是一样的步骤,准备java声明的native代码,更改cpp代码,配置cmake即可。

需要注意的是Android项目中的NDK可以直接引入jni.h,在Clion中我们要把需要手动把jdk中的jni.h和jni_md.h 拷贝到项目里,
jni.h在jdk中的includ目录里,jni_md.h在win32的目录里。

代码的结构如下

然后就可以去编译so文件了。点击build->Rebuild Project即可。

等待项目构建完成,如下

可以看到完成了,然后去上面红框给定的目录看一下文件。
可以看到,linux上是有源码的,相当于Clion把你本地的代码上传到linux中,然后通过linux的环境来进行编译了。

切到指定的最终目录看下

上面红框的so文件就是我们需要的了。

至此,linux上编译出so文件就搞定了。


Java Web Jni实现

然后就简单了,就是正常的JNi实现。先贴一下项目目录。


注意要加一下java代码访问resources目录中so文件的配置
下面是一些关键代码,这里只是demo代码,以实现功能为主,在生产环境下要注意下代码健壮性。

package com.yzq.bsdiffserver.utils;

import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;


/**
 * @description: 用来load so文件,先将项目的so拷贝到linux中,然后加载
 * @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
 * @date   : 2021/12/18
 * @time   : 14:33
 */
public class LibLoader 
    public static void loadLib(String libName, String resourcePath) 

        System.out.println("libName = " + libName);
        System.out.println("resourcePath = " + resourcePath);

        /*获取当前项目所在的linux路径 示例:/home/admin/webapp */
        final String projectPath = System.getProperty("user.dir");

        System.out.println("projectPath = " + projectPath);

        /*创建一个目录 用来放so*/
        String nativeLibPath = projectPath + File.separator + "tmp" + File.separator + "bsdiffdemo" + File.separator + "lib" + File.separator;

        File nativeLibFolder = new File(nativeLibPath);
        if (!nativeLibFolder.exists()) 
            nativeLibFolder.mkdirs();
        
        /*用来存放临时文件的目录*/
        String filePath = projectPath + File.separator + "tmp" + File.separator + "bsdiffdemo" + File.separator + "file" + File.separator;
        File fileFolder = new File(filePath);
        if (!fileFolder.exists()) 
            fileFolder.mkdirs();
        

        File libFile = new File(nativeLibFolder, libName);
        System.out.println("libFile.getAbsolutePath() = " + libFile.getAbsolutePath());
        if (libFile.exists()) 
            System.out.println("libFile 文件存在 libFile.getAbsolutePath() = " + libFile.getAbsolutePath());
            System.load(libFile.getAbsolutePath());
         else 
            try 

                final InputStream inputStream = new ClassPathResource(resourcePath).getInputStream();
//                InputStream in = LibLoader.class.getResourceAsStream(resourcePath);
                final FileOutputStream fileOutputStream = new FileOutputStream(libFile);

                /*把文件存到临时目录里*/
                final int copy = FileCopyUtils.copy(inputStream, fileOutputStream);

                System.out.println("copy result= " + copy);
                inputStream.close();
                fileOutputStream.close();
                System.out.println("libFile.getAbsolutePath() = " + libFile.getAbsolutePath());
                System.load(libFile.getPath());


             catch (Exception e) 
                e.printStackTrace();
                throw new RuntimeException("Failed to load required lib", e);
            
        
    

工具类代码,跟Android上类似。

package com.yzq.bsdiffserver.utils;

import java.io.File;


/**
 * @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
 * @description: BsDiffUtil
 * @date : 2021/12/18
 * @time : 14:33
 */
public class BsDiffUtil 
    
    static 
        String systemType = System.getProperty("os.name");
        System.out.println("systemType = " + systemType);
        try 
            /*获取当前项目所在的linux路径 示例:/home/admin/webapp */
//            final String projectPath = System.getProperty("user.dir");
            String libPath = "lib" + File.separator + "libxeon_bsdiff.so";
            String libName = "libxeon_bsdiff.so";

            /*mac上使用dylib*/
//            String libPath = "lib" + File.separator + "libxeon_bsdiff.dylib";
//            String libName = "libxeon_bsdiff.dylib";
            LibLoader.loadLib(libName, libPath);
            System.out.println("load so success");

         catch (Exception e) 
            System.out.println("e = " + e.getMessage());
            e.printStackTrace();
        

    


    /**
     * 生成补丁文件
     *
     * @param newFilePath
     * @param oldFilePath
     * @param patchFilePath
     * @return
     */
    public static native int fileDiff(String newFilePath, String oldFilePath, String patchFilePath);

    /**
     * 合并文件
     *
     * @param oldFilePath
     * @param patchFilePath
     * @param combineFilePath
     * @return
     */
    public static native int filePatch(String oldFilePath, String patchFilePath, String combineFilePath);


测试方法

package com.yzq.bsdiffserver.service;


import com.yzq.bsdiffserver.utils.BsDiffUtil;

import java.io.File;


/**
 * @description: 测试类
 * @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
 * @date   : 2021/12/18
 * @time   : 14:55
 */
public class BsDiffService 

    public static void testBsDiff() 
        try 

            /*测试load so*/
//            BsDiffUtil.test();

            final String projectPath = System.getProperty("user.dir");
            String filePath = projectPath + File.separator + "tmp" + File.separator + "bsdiffdemo" + File.separator + "file" + File.separator;
            /*旧文件路径*/

            String oldFilePath = filePath + "old.txt";
            /*新文件*/
            String newFilePath = filePath + "new.txt";
            /*补丁文件*/
            String patchFilePath = filePath + "patch.txt";
            /*合并后的文件*/
            String combineFilePath = filePath + "combine.txt";

            System.out.println("oldFilePath = " + oldFilePath);
            System.out.println("newFilePath = " + newFilePath);
            System.out.println("patchFilePath = " + patchFilePath);
            System.out.println("combineFilePath = " + combineFilePath);

            /*生成补丁文件*/
            final int diffResult = BsDiffUtil.fileDiff(newFilePath, oldFilePath, patchFilePath);
            System.out.println("diffResult = " + diffResult);

            /*合并补丁文件*/
            final int patchResult = BsDiffUtil.filePatch(oldFilePath, patchFilePath, combineFilePath);
            System.out.println("patchResult = " + patchResult);


         catch (Exception e) 
            e.printStackTrace();
        


    




java代码准备好之后直接部署到远端服务器测试即可。

这里就直接放出测试的结果了


存放so文件的目录

测试合并差分文件的目录

可以看到,合并后的文件和新文件大小是一致的,内容我看了也是完全一致的。

到这里,从Android到后端的基于bsdiff增量更新功能技术方面就打通了,具体应用的话根据你们的需求来定。

大冬天的码字不易,手都冻僵了,觉得有帮助的点个赞支持一下…


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

以上是关于手把手教你在Java后端使用bsdiff实现增量更新的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你在Java后端使用bsdiff实现增量更新

手把手教你在Java后端使用bsdiff实现增量更新

手把手教你在Java后端使用bsdiff实现增量更新

手把手教你在Java后端使用bsdiff实现增量更新

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)