SMBJ 简单使用指南 实现在 Java/Android 程序中访问 SMB 服务器

文章目录

一、问题背景

当我们在 Java 程序 或 Android 程序中,需要使用 SMB 的客户端连接 SMB 服务器进行上传和下载文件的时候,我们可以使用优秀的开源库 hierynomus/smbjhttps://github.com/hierynomus/smbj,其是专门为 Java 实现的 SMB2/SMB3 客户端库。其具有 API 简单清晰,使用方便的特点,本文将简单介绍其使用方法,实现文件/文件夹的上传、删除 等文件操作。

二、依赖导入

首先,我们需要在自己的项目中导入 SMBJ 的相关依赖

groovy 复制代码
dependencies {
    implementation 'com.hierynomus:smbj:0.14.0'
}

同时,在 Android 编译环境中,需要解决 META-INF/versions/9/OSGI-INF/MANIFEST.MF 文件冲突的问题,否则会报如下错误:

shell 复制代码
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction
   > 2 files found with path 'META-INF/versions/9/OSGI-INF/MANIFEST.MF' from inputs:
      - org.bouncycastle:bcprov-jdk18on:1.79/bcprov-jdk18on-1.79.jar
      - org.jspecify:jspecify:1.0.0/jspecify-1.0.0.jar
     Adding a packaging block may help, please refer to
     https://developer.android.com/reference/tools/gradle-api/com/android/build/api/dsl/Packaging
     for more information

app 模块下的 build.gradle 文件的 android 包络中添加以下代码:

groovy 复制代码
android {
	packagingOptions {
        pickFirst 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'
    }
}

三、连接到 SMB

SMBJ 对于文件的操作都是基于 com.hierynomus.smbj.share.DiskShare,因此我们需要先创建出 DiskShare 对象,

  • 在创建 DiskShare 对象,需要 com.hierynomus.smbj.session.Session 对象通过 connectShare() 方法连接到 SMB 的路径,
  • Session 对象是通过 com.hierynomus.smbj.connection.Connection 对象通过 authenticate 方法进行认证得到连接,
  • 最后 Connection 对象是通过 com.hierynomus.smbj.SMBClient 对象通过 connect 方法 连接到 SMB 服务器。

因此,连接的时序图如下:

核心代码如下:

kt 复制代码
// SMB 客户端对象
var client: SMBClient? 
// 文件操作对象,对应SMB的服务器的根目录
var diskShare: DiskShare?
try {
    client = SMBClient().apply {
        // 建立连接
        val connection = connect(SMB_IP)
        // 进行认证
        val session = connection.authenticate(AuthenticationContext(SMB_USERNAME,SMB_PASSWORD.toCharArray(),null))
        // 打开服务器的的根目录
        diskShare = session.connectShare(SMB_FOLDER) as? DiskShare
    }
} catch (e: Exception) {
	// 如果有异常 需要关闭 SMB 客户端对象
    client?.close()
}

这里有三个常量需要定义:

  • SMB_IPSMB 服务器的地址
  • SMB_USERNAMESMB 服务器登录用户名
  • SMB_PASSWORDSMB 服务器登录密码
  • SMB_FOLDERSMB 服务器的根目录

如果是通过匿名连接,则可以使用 AuthenticationContext.anonymous()AuthenticationContext.guest() 进行认证。

通过以上方法可以得到 DiskShare 对象进行操作文件。

四、文件夹创建

如果我们需要在 SMB 服务器创建一个新文件,则可以使用 com.hierynomus.smbj.utils.SmbFiles.mkdirs() 方法进行创建,同时此方法会递归创建父文件夹,保证父文件夹存在,文件夹可以创建成功。同时,如果创建失败,会抛出异常,只需要捕获此异常即可知道是创建失败,因此可以使用以下方法创建文件夹

kt 复制代码
var result = false
try {
    SmbFiles().mkdirs(diskShare, path)
    result = true
} catch (e: Exception) {
    result = false
}

这里的 path 是相对于 SMB 服务器的根目录地址,API文档如下:

之后,我们可以使用 DiskShare.folderExists() 方法检查文件夹是否存在

五、删除文件/文件夹

SMBJ 提供了两个方便的 API 进行删除文件和删除文件夹:

  • DiskShare.rm() 删除文件
  • DiskShare.rmdir() 删除文件夹

我们可以使用 DiskShare.fileExists()DiskShare.folderExists() 来检测需要删除的是文件还是文件夹,调用不同的 API 进行删除:

kt 复制代码
if (diskshare.folderExists(path)) {
    diskshare.rmdir(path, true)
} else if (diskshare.fileExists(path)) {
    diskshare.rm(path)
}

这里的 path 是相对于 SMB 服务器的根目录地址,API文档如下:

六、上传文件

文件上传有多种方法,但所有的方法的第一步是需要打开远端的文件,在打开文件的时候,可以传递不同的参数以实现不同的需求:

kt 复制代码
val file = diskShare.openFile(remotePath, EnumSet.of(AccessMask.GENERIC_WRITE), null,  EnumSet.of(SMB2ShareAccess.FILE_SHARE_WRITE), SMB2CreateDisposition.FILE_CREATE, null)

此方法的前面如下:

java 复制代码
public File openFile(String path, Set<AccessMask> accessMask, Set<FileAttributes> attributes, Set<SMB2ShareAccess> shareAccesses, SMB2CreateDisposition createDisposition, Set<SMB2CreateOptions> createOptions) {}
  • path 是相对于 SMB 服务器的根目录地址
  • accessMask 是用于控制文件的访问权限,默认传递 EnumSet.of(AccessMask.GENERIC_WRITE)
  • attributes 是文件的属性值,可以传空
  • shareAccesses 是操作类型,此处是写入文件,因此传递 EnumSet.of(SMB2ShareAccess.FILE_SHARE_WRITE)
  • createDisposition 是文件创建的方法,其可以传递以下类型的值
    • FILE_SUPERSEDE:如果文件存在时,则先删除旧文件,再写入新文件。如果文件不存在,则创建新文件
    • FILE_OPEN:如果文件存在时,则打开文件。如果文件不存在,则返回操作失败
    • FILE_CREATE:如果文件不存在,则创建新文件。如果文件存在,则返回操作失败
    • FILE_OPEN_IF:如果文件存在时,则打开文件。如果文件不存在,则创建文件
    • FILE_OVERWRITE:如果文件存在时,则覆写文件。如果文件不存在,则返回操作失败
    • FILE_OVERWRITE_IF:如果文件存在时,则覆写文件。如果文件不存在,则创建文件

随后我们需要创建本地的文件对象 localFile,打开本地文件的输入流。我们可以用如下方法写入文件:

kt 复制代码
// 打开本地文件的输入流
localFile.inputStream().buffered(UPLOAD_BUFFER_SIZE).use { input ->
	diskShare.openFile(remotePath, EnumSet.of(AccessMask.GENERIC_WRITE), null,  EnumSet.of(SMB2ShareAccess.FILE_SHARE_WRITE), SMB2CreateDisposition.FILE_CREATE, null).use { file ->
		// 打开远端文件的输出流
		file.outputStream.buffered(UPLOAD_BUFFER_SIZE).use { output ->
			// 将输入流写入到输出流中
			input.copyTo(output)
		}
	}
}
kt 复制代码
// 使用 FileByteChunkProvider 
FileByteChunkProvider(localFile).use { provider ->
	diskShare.openFile(remotePath, EnumSet.of(AccessMask.GENERIC_WRITE), null,  EnumSet.of(SMB2ShareAccess.FILE_SHARE_WRITE), SMB2CreateDisposition.FILE_CREATE, null).use { file ->
		// 将 provider 写入 file
		file.write(it)
	}
}

第二种方式在遇到大文件时会失败,建议使用第一种方法

同时,SmbFiles 提供了 copy()write() 的方法,方便上传文件。

七、Proguard 混淆规则

复制代码
-keep class org.slf4j.** { *; }
-dontwarn org.slf4j.**

-dontwarn javax.el.**
-dontwarn org.ietf.jgss.**
相关推荐
砖厂小工20 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心20 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心21 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
YuMiao1 天前
gstatic连接问题导致Google Gemini / Studio页面乱码或图标缺失问题
服务器·网络协议
SimonKing1 天前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean1 天前
Jackson View Extension Spring Boot Starter
java·后端
Kapaseker1 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴1 天前
Android17 为什么重写 MessageQueue
android
Seven971 天前
剑指offer-79、最⻓不含重复字符的⼦字符串
java
皮皮林5511 天前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java