SMB(全称是Server Message Block)是一个协议名,它能被用于Web连接和客户端与服务器之间的信息沟通。SMB协议作为一种局域网文件共享传输协议,常被用来作为共享文件安全传输研究的平台。
Windows操作系统都包括了客户机和服务器SMB协议支持。Microsoft为 Internet提供了SMB的开源版本,即通用Internet文件系统CIFS。与现有Internet应用程序如文件传输协议FTP相比, CIFS灵活性更大。对于UNIX系统,可使用一种称为Samba的共享软件。
在 pom.xml
中引入SMB服务相关的依赖:
<!-- 引用SmbFile类的jar包 --> <dependency> <groupId>jcifs</groupId> <artifactId>jcifs</artifactId> <version>1.3.17</version> </dependency>
在Java中通过SMB访问远程共享目录的请求格式有如下三种情况:(以 test
共享文件夹下的 test.txt
文件示例)
smb://ip/sharefolder/filename
(例如: smb://192.168.1.106/test/test.txt
)
smb://username:password@ip/sharefolder/filename
(例如: smb://admin:damin@192.168.1.106/test/test.txt
)
smb:host;username:password@ip/sharefolder/filename
(例如: smb://orcl;admin:admin@192.168.1.106/test/test.txt
)
具备了以上环节的准备,就可以在项目中实现SMB相关的应用;下面是一个简单的从共享文件夹目录中将文件下载到指定文件夹的方法。
package com.example.smb; import jcifs.smb.SmbFile; import jcifs.smb.SmbFileInputStream; import org.springframework.util.FileCopyUtils; import java.io.*; /** * @author: Create By 成猿手册 * @description: Java中SMB的上传和下载 * @date: 2020/03/22 */ public class Demo { private static final String SMB_SHARE_FOLDER = "smb://admin:admin@192.168.1.106/Test/"; private static final String SHARE_FOLDER_PATH = "2020-03-21//result"; private static final String FILE_NAME = "myresult.txt"; private static final String LOCAL_DIR = "D://LocalTest"; public static void main(String[] args) { downloadSmbFile(SMB_SHARE_FOLDER, SHARE_FOLDER_PATH, FILE_NAME, LOCAL_DIR); } /** * 从SMB共享文件夹下载文件到本地 * @param smburl smb请求的url * @param shareFolder 共享文件夹中目标文件存放的完整路径 * @param fileName 要下载/上传的完整文件名 * @param localDir 要上传/下载的完整文件夹路径 */ public static void downloadSmbFile(String smburl, String shareFolder, String fileName, String localDir) { InputStream in = null; OutputStream out = null; try { SmbFile smbfile = new SmbFile(smburl + shareFolder + File.separator + fileName); File localFile = new File(localDir + File.separator + fileName); //文件上传到SMB共享文件目录与该写法类似;即使用SmbFileOutputStream(smbfile); in = new BufferedInputStream(new SmbFileInputStream(smbfile)); out = new BufferedOutputStream(new FileOutputStream(localFile)); FileCopyUtils.copy(in, out); } catch (Exception e) { e.printStackTrace(); } finally { closeStreanm(in, out); } } //关闭文件流 private static void closeStreanm(InputStream in, OutputStream out) { try { if (in != null) { in.close(); } if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } }
不难发现,对SMB共享文件的操作放在Java中其实就是转换为操作 SmbFile
这个对象,一旦成功构建(合法的 url
,正确的 canon
等必要属性)该对象,许多问题也就变得简单起来。
例如,自己工作中有一个业务需求是要检测SMB共享目录中的某个文件是否存在,代码示例如下:
/** * 检测SMB共享文件夹中的文件是否存在 * @param smburl smb请求的url * @param shareFolder 共享文件夹中目标文件存放的完整路径 * @param fileName 要检测文件的完整文件名 */ public static boolean checkSmbFile(String smburl, String shareFolder, String fileName) { boolean result = false; try { SmbFile smbfile = new SmbFile(smburl + shareFolder + File.separator + fileName); result = smbfile.exists(); } catch (Exception e) { e.printStackTrace(); } return result; }
在这里使用登录验证主要是为解决账号密码中含有特殊字符的情况(比如转义字符,链接里的特定字符),存在特殊字符的账号密码再使用上面的路径去请求SMB服务往往会报出下列异常:
Connected to the target VM, address: '127.0.0.1:54593', transport: 'socket' jcifs.smb.SmbAuthException: Logon failure: unknown user name or bad password.
这时为了构建合法的 SmbFile
对象,我们就换一种思路:先进行登录验证,再去尝试构建该对象:
package com.example.smb; import jcifs.smb.NtlmPasswordAuthentication; import jcifs.smb.SmbFile; import java.net.MalformedURLException; /** * @author: Create By 成猿手册 * @description: smb账号密码中含有特殊字符的处理方法 * @date: 2020/3/22 */ public class SmbFileHelper { public static SmbFile newSmbFile(SmbFileInfo smbFileInfo) throws MalformedURLException { NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(smbFileInfo.getIp(), smbFileInfo.getUsername(), smbFileInfo.getPassword()); String smburl = String.format("smb://%s/%s", smbFileInfo.getIp(), smbFileInfo.getFilepath()); return new SmbFile(smburl, auth); } }
SmbFileInfo
类的写法:
package com.example.smb; /** * @author: Create By 成猿手册 * @description: SmbFileInfo实体类 * @date: 2020/3/22 */ public class SmbFileInfo { private String ip; private String username; private String password; private String filepath; //这里省略了get()和set()方法 }