使用的包
-
<groupId>org.apache.commons</groupId> <artifactId>commons-vfs2</artifactId> <version>2.2</version>
-
<groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version>
SFTP服務器
- Windows10自帶的OpenSSH服務器
- FreeSSH軟件搭建的SFTP服務器
問題描述 - Windows10自帶的OpenSSH服務器
-
使用SFTP上傳文件到Windows系統上的SFTP服務器的過程中,先使用臨時文件名,上傳成功之后使用moveTo方法將文件名改為最終文件名
-
使用moveTo方法改名的過程中,如果是使用Windows10自帶的OpenSSH服務器,會直接拋出一個異常,異常信息為無法獲取當前用戶的組ID
-
查看相應的棧信息時發現是執行moveTo方法時,先通過獲取文件用戶組以及用戶來判斷是否擁有寫權限,使用了ChannelExec發送id -G或者id -u命令。
-
通過上述步驟得知,通過ChannelExec和id -G命令無法得到文件所屬的用戶組,所以拋出了找不到用戶組的異常
解決辦法 - Windows10自帶的OpenSSH服務器
-
通過在網上查找資料,發現commons-vfs2在后續的版本(2.5.0)中對此進行了優化,如果是不支持ChannelExec的系統,就默認返回一個值
-
判斷文件系統是否支持Exec,不支持就返回默認值,支持才回去獲取文件所屬用戶組
-
通過上面圖片可以看出,execDisabled字段是在構造SftpFileSystem時提前進行一次獲取用戶ID的操作,如果出現異常,表明文件系統不支持Exec的操作,execDisabled字段就設為true,在后續就不會再去獲取文件所屬用戶組以及用戶
問題描述 - FreeSSH軟件搭建的SFTP服務器
-
使用moveTo方法改名的過程中,如果是使用FreeSSH軟件搭建的SSH服務器,會在moveTo方法中卡住,一直不會退出。
-
通過Debug發現最終依然是在獲取用戶組的方法中出現的問題。一次Debug下去發現是通過ChannelExec發送了id -G的命令之后,在使用PipedInputStream讀取返回的數據時死鎖。
-
PipedInputStream是Java中的管道流,和PipedOutputStream配套使用,兩邊都能對同一個緩沖區進行操作,PipedOutputStream往里面進行寫入數據,PipedInputStream從其中讀取數據。
-
in字段表示從連接的管道輸出流接收到的下一個數據字節將存儲在循環緩沖區中的位置索引。 in<0表示緩沖區為空, in==out表示緩沖區已滿。writeSide字段是表示PipedOutputStream所在線程,closedByWriter代表Writer是否已經關閉。
-
從上述字段來分析,在while循環中發生了死鎖,代表in是一直小於0,且並沒有被PipedOutputStream關閉,而如果writeSide如果存在,只需要循環兩次就會退出,並不會造成死鎖。
-
通過writeSide賦值操作發現是在PipedInputStream流中的receive方法中得到當前線程,而receive方法是在PipedOutputStream流的wirite方法中調用。如果說writeSide字段為null,表示PipedOutputStream流並沒有調用write方法往緩沖區中寫入數據
總結
通過上述分析發現,往FreeSSH軟件搭建的SFTP服務器上傳文件,且使用moveTo方法改名死鎖的原因很有可能是通過Exec獲取文件所屬的用戶組,但是SFTP服務器並沒有返回任何數據,導致當前PipedInputStream流一直等待PipedOutputStream流返回數據並且關閉
解決辦法 - FreeSSH軟件搭建的SFTP服務器
- 不使用FreeSSH軟件搭建的SFTP服務器