文章目录
什么是SMB协议
SMB 是 Server Message Block 的缩写。它是一种网络文件共享协议,允许用户与远程计算机和服务器通信,使他们能够在网络上访问文件、目录、打印机和端口等资源。SMB 在 TCP/IP 协议栈的应用层(第 7 层)上操作,并使用 TCP/IP 的 445 端口。目前我们常见的共享文件方式基本都是支持SMB协议。
以下是 SMB 常用场景:
文件共享
:SMB 的主要用途之一是文件共享。SMB 允许多个用户访问和共享存储在远程服务器上的文件,就像它们在自己的本地设备上一样。这使得在网络中的协作和共享资源变得简单。打印机共享
:SMB 可用于共享网络上的打印机。用户可以将打印请求发送到 SMB 服务器,服务器处理请求并返回响应。资源访问
:SMB 使用户能够访问远程计算机和服务器上的资源,如目录、打印机和端口。这使用户能够执行打印、访问共享文件夹和使用网络服务等任务。远程访问
:SMB 允许用户远程访问网络上的文件和资源。这对于需要从不同位置访问共享文件并在项目中协作的远程工作者或团队成员特别有用[来源6]。网络驱动器映射
:SMB 使用户可以在其设备上映射网络驱动器,使其能够像访问本地存储的文件和文件夹一样访问远程服务器上的文件和文件夹。这为网络中的共享资源提供了便利的访问方式。
值得注意的是,SMB 协议有不同的版本,包括 SMB1、SMB2、SMB3 和 SMB3.02。每个版本都引入了新功能、改进和安全增强。建议使用与您的网络环境兼容的最新 SMB 协议版本,以获得更好的性能和安全性。
SMB与CIFS区别
SMB(Server Message Block,服务器消息块)和CIFS(Common Internet File System,通用互联网文件系统)都是网络文件共享协议,它们允许计算机之间通过网络共享文件、打印机等资源。然而,它们之间存在一些差异。
SMB 是一种文件共享协议,由 IBM 发明,自 20 世纪 80 年代中期以来一直存在。它的设计目的是让计算机在局域网(LAN)上读写远程主机上的文件。SMB 是一种通信协议,不是特定的软件应用程序。SMB 协议在 Windows 系统中广泛使用,也被其他操作系统支持。
CIFS 是 SMB 的一个方言,也就是说,CIFS 是微软创建的 SMB 协议的一个特定实现。CIFS 的设计目标与 SMB 相似,但具有微软特色。由于 CIFS 是 SMB 的一种形式,因此在讨论和应用中,它们可以互换使用。
虽然它们都是顶级协议,但在实现和性能调优方面仍然存在差异,因此它们有不同的名称。协议实现,如 CIFS 和 SMB,通常会以不同的方式处理文件锁定、局域网/广域网上的性能和文件的批量修改等问题。
总之,SMB 是一种网络文件共享协议,而 CIFS 是 SMB 协议的一个特定实现。在实际应用中,它们之间的差异主要在于实现和性能调优方面。现在,我们只需要使用 SMB 这个术语,因为现代存储系统在底层通常不再使用 CIFS,而是使用 SMB 2 或 SMB 3。
为什么要使用SMB
除了以上说到的常用场景,我们在项目中主要还是用来对接上下游存储文件使用到的共享网盘,进行文件传输与存储,一般会搭配ADFS(Active Directory Federation Services)进行身份验证和授权。
以下是我们真实项目的一些应用案例:
因为在我们公司申请一个NAS共享文件夹一天就可以搞定,而搭建一台SFTP服务器,要走的流程很多,并且NAS共享文件夹还可以直接映射到我们Window系统当作本地硬盘那样使用,用户不需要额外开发一个文件上传下载接口,只需要暴露SMB协议给对接的应用即可,所以很多个人用户是使用NAS共享文件夹来存储文件和跟团队成员在局域网内协作处理文件。像Macbook也有共享文件功能,可以通过SMB协议跟局域网内的服务进行文件共享。
如何对接SMB服务
目前主流的SMB版本都是SmbV2、SmbV3,但是还有一些老系统会使用SmbV1。在Java目前的第三方工具包,SmbV1和SmbV2及以上的对接方式是不兼容的,所以需要分开处理。
支持SmbV1:
- Jcifs
支持SmbV2及以上:
- jcifs-codelibs
- jcifs-ng
- smbj
本文主要会使用Jcifs和smbj这两个框架来分别对接SmbV1和SmbV2服务,本文末尾提供了Github链接,大家可以直接下载示例代码查看。
如何用Java实现Smb文件传输
我们项目中,对共享文件夹要实现的操作主要就是 增、删、改、查,对应的文件操作就是文件上传、文件下载、文件删除、文件改名、文件查询。
SmbV1的实现
第三方工具包版本
<dependency>
<groupId>jcifs</groupId>
<artifactId>jcifs</artifactId>
<version>1.3.17</version>
</dependency>
基于SmbV1的文件上传
public boolean upload(FileUploadDTO fileUploadDTO) throws IOException {
String remoteFolder = fileUploadDTO.getRemoteFolder();
String uploadFile = fileUploadDTO.getLocalFilePath();
String remoteHost = fileUploadDTO.getRemoteHost();
String account = fileUploadDTO.getAccount();
String psw = fileUploadDTO.getPassword();
String domain = fileUploadDTO.getDomain();
File source = new File(fileUploadDTO.getLocalFilePath());
NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(domain, account, psw);
String smbUrl = "smb://" + remoteHost + "/" + fileUploadDTO.getShareName() + "/" + remoteFolder + source.getName();
SmbFile smbFile = new SmbFile(smbUrl, auth);
try (FileInputStream fis = new FileInputStream(source);
SmbFileOutputStream smbfos = new SmbFileOutputStream(smbFile)) {
final byte[] b = new byte[16 * 1024];
int read;
while ((read = fis.read(b, 0, b.length)) > 0) {
smbfos.write(b, 0, read);
}
log.info("=======>File {} has been upload to {} successfully", uploadFile, fileUploadDTO.getRemoteFolder() + source.getName());
}
return true;
}
基于SmbV1的文件下载
public boolean download(FileDownloadDTO fileDownloadDTO) throws IOException {
List<String> matchFileList = new ArrayList<String>();
String subFolder = fileDownloadDTO.getRemoteFolder();
String remoteHost = fileDownloadDTO.getRemoteHost();
String account = fileDownloadDTO.getAccount();
String psw = fileDownloadDTO.getPassword();
String domain = fileDownloadDTO.getDomain();
NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(domain, account, psw);
String smbUrl = "smb://" + remoteHost + "/" + fileDownloadDTO.getShareName() + "/" + subFolder;
SmbFile smbFolder = new SmbFile(smbUrl, auth);
SmbFile[] files = smbFolder.listFiles(new SmbFilenameFilter() {
@Override
public boolean accept(SmbFile dir, String name) throws SmbException {
return !name.endsWith(fileDownloadDTO.getFileExtension());
}
});
for (SmbFile file : files) {
String fileName = file.getName();
log.info("=======>File Name:{}", fileName);
log.info("=======>File lastModifiedTime:{}", file.getLastModified());
log.info("=======>Is file existed?:{}", file.exists());
//Check file pattern
if (!matchPattern(matchFileList, file, fileDownloadDTO.getFilePattern())) continue;
String localFilePath = fileDownloadDTO.getLocalFolder() + fileName;
String newFileName = fileName + fileDownloadDTO.getFileExtension();
// Download file from share drive
try (SmbFileInputStream sfis = new SmbFileInputStream(file);
FileOutputStream fos = new FileOutputStream(localFilePath)) {
byte[] b = new byte[16 * 1024];
int read;
while ((read = sfis.read(b, 0, b.length)) > 0) {
fos.write(b, 0, read);
}
log.info("=======>File {} has been downloaded to {} successfully", fileName, localFilePath);
}
// Rename file after download successfully
SmbFile newFile = new SmbFile(file.getParent() + newFileName, auth);
file.renameTo(newFile);
log.info("=======>File {} has been renamed to {} successfully", fileName, newFileName);
}
if (matchFileList.isEmpty()) {
throw new NotMatchFilesException("No any match files found, please check your file pattern!", null);
}
return true;
}
基于SmbV1的文件重命名
public boolean rename(FileRenameDTO fileRenameDTO) throws IOException {
List<