使用 Java 和 Samba JCIFS 存取文件
1. 引言
跨平台文件交換能力對於電腦網路的運作至關重要。我們可以透過伺服器訊息區塊(SMB)協定及其廣泛應用的開源實作 Samba 來提供這種能力。
在本教程中,我們將學習如何從 Java 存取 Samba 資源,而無需掛載或映射網路磁碟機。
2. JCIFS 庫
通用互聯網檔案系統(CIFS)是SMB協定的一種方言。我們將使用codelibs的JCIFS實現,它支援最新的SMB3協定。
讓我們透過Maven 依賴項來添加它:
<dependency>
<groupId>org.codelibs</groupId>
<artifactId>jcifs</artifactId>
<version>3.0.1</version>
</dependency>
3. 設定 Samba 伺服器
在本教學中,我們將使用在 VirtualBox 虛擬機器中設定的 Samba 伺服器。我們選擇 Ubuntu 伺服器作為虛擬機器。
接下來,我們來設定 VirtualBox 的僅主機網路適配器。然後,我們將 DHCP 位址範圍縮小到單一 IP 位址: 192.168.56.101 。這種方法可以省去我們進行更複雜的靜態 IP 設定的步驟。
在安裝客戶機系統期間,我們新增使用者jane 。之後,我們需要安裝 Samba 並將jane新增至 Samba 使用者清單:
$ sudo smbpasswd -a jane
我們需要兩個資料夾來存放共用檔案:
$ mkdir /srv/samba/public /srv/samba/sambashare
為了顯示這些資料夾,我們將它們的權限設為777 :
$ sudo chmod 777 /srv/samba/public /srv/samba/sambashare
現在,我們來編輯/etc/samba/smb.conf檔:
$ sudo nano /etc/samba/smb.conf
然後我們再增加兩個股份部分:
[publicshare]
comment = Anonymous Samba share
path = /srv/samba/public
read only = no
guest ok = yes
guest only = yes
[sambashare]
comment = Samba on Ubuntu
path = /srv/samba/sambashare
read only = no
browsable = yes
這樣我們就創建了兩個 Samba 共享:一個匿名的publicshare和一個受密碼保護的sambashare.
4. 簡單範例
讓我們執行一段簡單的程式碼來了解存取 Samba 共享的基本原理。我們將檢查位於publicshare上的一個文件:
// Default context
CIFSContext context = SingletonContext.getInstance();
LOGGER.info("# Checking if file exists");
try (SmbFile file = new SmbFile("smb://192.168.56.101/publicshare/test.txt", context)) {
if (file.exists()) {
LOGGER.info("File " + file.getName() + " found!");
} else {
LOGGER.info("File " + file.getName() + " not found!");
}
}
我們可以注意到建立溝通所必需的要素:
-
CIFSContext維護客戶端設定、憑證和其他相關資訊。在這裡,它是SingletonContext的一個實例,提供適用於匿名帳戶的憑證。 -
SmbFile代表任何類型的 Samba 資源。在本例中,它是一個檔案。我們可以將其放入 try-with-resource 區塊中。
最後,我們對file物件使用了exists()方法。
5. 身份驗證
我們可以使用憑證建立CIFSContext物件。讓我們列出受密碼保護的共享資料夾sambashare中的元素:
NtlmPasswordAuthenticator credentials = new NtlmPasswordAuthenticator(
"WORKGROUP", // Domain name
"jane", // Username
"Test@Password" // Password
);
// Context with authentication
CIFSContext authContext = context.withCredentials(credentials);
LOGGER.info("# Logging in with user and password");
try (SmbFile res = new SmbFile("smb://192.168.56.101/sambashare/", authContext)) {
for (String element : res.list()) {
LOGGER.info("Found element " + element);
}
}
首先,我們建立了NtlmPasswordAuthenticator物件來儲存憑證。然後,我們對現有的context物件呼叫了withCredentials()方法。結果,我們得到了一個包含憑證的子authContext物件。最後, list()函數顯示了共享的所有元件。
6. 檔案和目錄的操作
JCIFS 提供了一套全面的檔案和資料夾操作函數。讓我們來看其中的一些函數。
6.1. 文件清單和檢查
使用listFiles()函數,我們可以列出檔案和資料夾。它傳回一個SmbFile對象,該對象允許使用許多驗證函數:
LOGGER.info("# List files and folders in Samba share");
try (SmbFile res = new SmbFile("smb://192.168.56.101/publicshare/", context)) {
for (SmbFile element : res.listFiles()) {
LOGGER.info("Found Samba element of name: " + element.getName());
LOGGER.info(" Element is file or folder: " + (element.isDirectory() ? "file" : "folder"));
LOGGER.info(" Length: " + element.length());
LOGGER.info(" Last modified: " + new Date(element.lastModified()));
}
}
在這個例子中,我們遍歷了公用資料夾中的所有項目。我們使用isDirectory()方法來判斷它是檔案還是目錄。接下來,我們分別使用length()和getLastModified()方法來取得其長度和修改時間。
6.2 建立和刪除文件
JCFIS庫允許建立和刪除檔案和目錄。首先,我們來操作文件。我們將建立並立即刪除New_file.txt檔案:
LOGGER.info("# Creating and deleting a file");
String fileName = "New_file.txt";
try (SmbFile file = new SmbFile("smb://192.168.56.101/publicshare/" + fileName, context)) {
LOGGER.info("About to create file " + file.getName() + "!");
file.createNewFile();
LOGGER.info("About to delete file " + file.getName() + "!");
file.delete();
}
我們為一個尚不存在的檔案建立了一個SmbFile物件。然後,我們呼叫了它的createNewFile()方法。最後,我們應用delete()函數刪除了該檔案。值得注意的是, createNewFile()方法會跳過已存在的文件,且不會發出任何通知。
6.3 建立和刪除資料夾
我們可以採用類似的方法處理資料夾:
LOGGER.info("# Creating and deleting a folder");
String newFolderName = "New_folder/";
try (SmbFile newFolder = new SmbFile("smb://192.168.56.101/publicshare/" + newFolderName, context)) {
LOGGER.info("About to create folder " + newFolder.getName() + "!");
newFolder.mkdir();
LOGGER.info("About to delete folder " + newFolder.getName() + "!");
newFolder.delete();
}
我們使用mkdir()方法建立資料夾,使用delete()方法刪除資料夾。在建立資料夾時,必須確保該資料夾不存在;否則, mkdir()會失敗。
請注意,使用delete()方法刪除資料夾時務必格外小心。此方法會遍歷並刪除整個資料夾樹及其所有檔案。此外,它還會移除檔案的唯讀權限。
此外,我們也可以使用mkdirs()方法來建立完整的目錄樹:
LOGGER.info("# Creating and deleting a subfolder with parent");
newFolderName = "New_folder/";
String subFolderName = "New_subfolder/";
try (SmbFile newSubFolder = new SmbFile("smb://192.168.56.101/publicshare/" + newFolderName + subFolderName, context)) {
LOGGER.info("About to create folder " + newSubFolder.getName() + "!");
newSubFolder.mkdirs();
}
我們建立了一個新的目錄New_subfolder ,以及一個之前不存在的父資料夾New_folder 。
6.4 複製文件
copyTo()方法可以方便地複製檔案和目錄。我們可以對單一文件或資料夾使用它。讓我們將sambashare的所有內容複製到publicshare :
LOGGER.info("# Copying files with copyTo");
try (SmbFile source = new SmbFile("smb://192.168.56.101/sambashare/", authContext); //needs authentication
SmbFile dest = new SmbFile("smb://192.168.56.101/publicshare/", context)) { //public share
source.copyTo(dest);
}
我們透過呼叫 Samba 資源source copyTo()方法並將目標資源dest傳遞給該方法來進行複製。值得注意的是,這些資源是不同的 Samba 共享。
我們也可以在不同的伺服器之間複製檔案。但是,我們不能從本機檔案系統複製文件,只能在由 Samba 管理的資源之間複製文件。
7. 處理流程
該程式庫提供了SmbFileInputStream和[SmbFileOutputStream](https://javadoc.io/doc/org.codelibs/jcifs/latest/jcifs/org/codelibs/jcifs/smb/impl/SmbFileOutputStream.html) ,它們分別重寫了標準的 Java InputStream和OutputStream抽象類別。讓我們將本機檔案複製到 Samba 共用:
LOGGER.info("# Copying files with streams");
try (InputStream is = new FileInputStream("/home/joe/test.txt"); //Local file
SmbFile dest = new SmbFile("smb://192.168.56.101/publicshare/test_copy.txt", context); //Samba resource
OutputStream os = dest.getOutputStream()) {
byte[] buffer = new byte[65536]; // using 64KB buffer
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
我們使用FileInputStream讀取本地檔案。然後,我們使用內部緩衝區複製內容。當處理本地文件時,此方法是對copyTo()方法的補充。
8. 結論
本文介紹如何使用 JCIFS 庫存取 Samba 資源。為了進行測試,我們搭建了一個簡單的 Samba 伺服器。然後,我們分析了一個共享資源,並簡要地了解了 Samba 身份驗證。
接下來,我們將重點放在文件操作。首先,我們列出了文件和資料夾,並檢查了它們的屬性。然後,我們對檔案和目錄執行了建立、複製和刪除操作。最後,我們使用 Java I/O 流的 JCIFS 實作來讀寫 Samba 檔案。
和往常一樣,範例程式碼可在 GitHub 上找到。