import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
/**
* 在這個版本中但願作的是軟件的多線程下載
* 如今也作了斷點下載 ,是否斷點下載的依據爲目標文件是否已經存在 ,無論是否從斷點出繼續下載,
* 分的線程下載的文件都是從指定的位置開始下載的
*
*/
public class FtpTransFile {
private static String fileName; // 要上傳或下載的文件的名字
private static String path;// 臨時文件夾的目錄,用於存放多個線程下載的文件
static long threadBlock = 100 * 1024 * 1024L;
/**
*
* @param path
* 要上傳的本地文件路徑 如"C:/Users/repace/Desktop/zhangke1.txt";
* @param server
* ftp服務器ip地址 192.168.242.133
* @param userName
* 登陸ftp的用戶名 test
* @param password
* 登陸ftp用戶名對應的密碼 123456
*/
public static void fileUpload(String OStype, String path, String server,
String userName, String password) { // 要上傳的文件的本地路徑路徑
// 目前可完成單個文件的上傳
if (!(OStype.equalsIgnoreCase("windows") || OStype
.equalsIgnoreCase("linux"))) {
System.out.println("操做系統類型輸入錯誤,應爲windows或linux");
return;
}
FTPClient ftpClient = new FTPClient();
ftpClient.enterLocalPassiveMode(); // 這一句話必定要記得加上
FileInputStream fis = null;
try {
ftpClient.connect(server);
ftpClient.login(userName, password);
File srcFile = new File(path);// 要上傳的本地文件路徑
fis = new FileInputStream(srcFile);
String storeName = srcFile.getName();// 要存儲的文件的名字
String remoteFilename = "/mnt/data/ftp/www/" + OStype.toLowerCase() + "/"
+ storeName;
ftpClient.changeWorkingDirectory("/mnt/data/ftp/www/" + OStype.toLowerCase()
+ "/"); // 設置上傳的文件在centos上的目錄,文件上傳不成功是要查看指定目錄的權限
ftpClient.setBufferSize(1024);
ftpClient.setControlEncoding("UTF-8");
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);// 設置文件類型(二進制)
FTPFile[] files = ftpClient.listFiles(remoteFilename);// 判斷軟件中心是否包含這個文件
if (files.length == 1) {// 軟件中心包含該文件
long remoteSize = files[0].getSize();// 軟件中心的文件大小
long localSize = srcFile.length();// 打算要上傳的文件大小
if (remoteSize == localSize) { // 軟件中心有這個文件,而且和打算要上傳的文件大小同樣,則說要上傳的文件已存在
System.out.println("要上傳的文件已存在");
ftpClient.disconnect();
return;
} else if (remoteSize > localSize) {// 軟件中心的文件比要上傳的大,可能新上傳的文件被修改了,而後再次上傳的
System.out.println("軟件中心的軟件比即將上傳的要大,無須上傳或從新命名要上傳的文件名");
ftpClient.disconnect();
return;
}
// 軟件中心存的文件比要上傳的文件小,則嘗試移動文件內讀取指針,實現斷點續傳 **************
if (fis.skip(remoteSize) == remoteSize) {
ftpClient.setRestartOffset(remoteSize);
boolean i = ftpClient.storeFile(
new String(storeName.getBytes("UTF-8"),
"iso-8859-1"), fis);
if (i) {
System.out.println("文件斷點續傳成功");
ftpClient.disconnect();
return;
}
}
} else { // 軟件中心不包含要上傳的文件,或者續傳不成功,則上傳全新的文件便可
boolean i = ftpClient.storeFile(
new String(storeName.getBytes("UTF-8"), "iso-8859-1"),
fis);
System.out.println("文件上傳" + i);
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("FTP客戶端出錯!", e);
} finally {
try {
fis.close();
ftpClient.disconnect();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException("關閉FTP鏈接發生異常!", e);
}
}
}
/**
* *
*
* @param OStype
* 操做系統的類型 windows或者是linux
* @param fileName
* 指出要下載的文件名字 加後綴的
* @param storePath
* 下載以後想要在本地的存儲路徑,在window系統中支持兩種文件路徑\\ 或者/
* @param server
* ftp服務器IP地址
* @param userName
* ftp分配的登陸名 test
* @param password
* 與登陸名對應的登陸密碼 123456
* @throws FileNotFoundException
* @throws InterruptedException
*/
public static void fileDownload(String OStype, String fileNames,
String storePath, String server, String userName, String password)
throws FileNotFoundException, InterruptedException { // 參數是帶後綴的文件名字和下載以後要存儲的本地路徑
// 可完成單個文件的下載 ,
if (!(OStype.equalsIgnoreCase("windows") || OStype
.equalsIgnoreCase("linux"))) {
System.out.println("操做系統類型輸入錯誤,應爲windows或linux");
return;
}
fileName = fileNames;
File file = new File(storePath);
if (!file.exists()) {// 判斷文件夾是否存在,若是不存在則建立文件夾
file.mkdir();
}
FTPClient ftpClient = new FTPClient();
ftpClient.enterLocalPassiveMode(); // 這一句話必定要記得加上
String remoteFileName = "/mnt/data/ftp/www/" + OStype.toLowerCase() + "/"
+ fileName; // 服務器上的文件,前面是文件夾的名字,後面的是文件的名字
String localFileName = "";// 本地要存儲的文件絕對路徑 文件夾加上文件名
try {
ftpClient.connect(server);
ftpClient.login(userName, password);
ftpClient.setBufferSize(1024);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); // 設置文件類型(二進制)
FTPFile[] files = ftpClient.listFiles(remoteFileName);
if (files.length == 0) { // 判斷軟件中心是否有要下載的軟件
System.out.println("軟件中心沒有找到要下載的軟件");
ftpClient.disconnect();
return;
} else { //軟件中心包含請求下載的文件
long localSize = 0L; // 記錄本地文件的大小
if (storePath.endsWith("\\") || storePath.endsWith("/"))// 存儲路徑直接是某個盤下的根目錄或者用戶加上了最後的斜線
{
localFileName = storePath + fileName;
path = storePath
+ fileName.substring(0, fileName.indexOf("."))
+ "Temp/";
} else {
localFileName = storePath + "/" + fileName;
path = storePath + "/"
+ fileName.substring(0, fileName.indexOf("."))
+ "Temp/";
}
File localFile = new File(localFileName);
long remoteSize = files[0].getSize();// 軟件中心的文件大小
if (localFile.exists()) {// 指定下載的文件在本地文件夾內已經存在
localSize = localFile.length();// 已存在的文件大小
if (remoteSize == localSize) {
System.out.println("文件已下載過,無需再下載");
return;
} else if (remoteSize > localSize) { // 以前下載未完成,實現斷點下載
System.out.println("斷點下載。。。");
}
if (remoteSize < localSize) {// 若是本地的文件比軟件中心的文件大,則說明本地的文件可能有錯,刪除,而後從頭開始下載
localFile.delete();
System.out.println("軟件重新開始下載");
localSize = 0L;
}
} else {// 指定下載的文件在本地文件夾內不存在,從頭下載文件
localSize = 0L;
System.out.println("軟件從頭下載");
}
File tempfile = new File(path);
if (tempfile.exists()) {// 判斷文件夾是否存在,若是已經存在,則刪除該文件夾及其全部的子文件,以避免其包含的線程影響後面的下載過程
System.out.println("delete 以前的臨時文件夾");
deleteTempFile(path);
}
tempfile.mkdir();// 新建存放臨時文件夾的目錄
ExecutorService exec = Executors.newCachedThreadPool(); // 開始啓動多線程下載文件
int threadNum = (int) ((remoteSize - localSize) / threadBlock + 1);// 每100M分一個線程下載
// 計算線程總數
System.out.println("分紅的線程個數" + threadNum);
CountDownLatch latch = new CountDownLatch(threadNum);
System.out.println(fileNames + "請求還需下載的文件大小"
+ (remoteSize - localSize));
long[] startPos = new long[threadNum];
ChildThread[] childThreads = new ChildThread[threadNum];// ChildThread
// 變成ChildThread1共有4處修改
for (int i = 0; i < threadNum; i++) {
startPos[i] = localSize + i * threadBlock; // 設置每一個線程開始下載文件的起始位置
childThreads[i] = new ChildThread(OStype, fileName,
storePath, server, userName, password, startPos[i],
i, latch); // 建立線程 線程編號從0開始
exec.execute(childThreads[i]);// 開始執行線程
}
latch.await(); // 等待全部的線程都運行結束
exec.shutdown();
tempFileToTargetFile(localFileName, childThreads, threadNum);// 把臨時獲得的文件夾內的文件合併到目標文件
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("FTP客戶端出錯!", e);
} finally {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("關閉FTP鏈接發生異常!", e);
}
}
}
/**
* @author repace 把臨時文件夾內的文件都寫入目標文件內 即將各個線程所下的文件進行合併
* @param target
* 目標文件 是以前用戶發送的請求要把請求下載的文件存放的絕對路徑的目錄
* 好比要下載的是test.txt文件,想存在c:\\123\\文件夾內 則目標文件target
* 指的的就是c:\\123\\test.txt
* @param tempFile
* 臨時文件夾的目錄則是 c:\\123\\testTemp\\
* @param threadNum
* @return
* @throws IOException
*/
public static boolean tempFileToTargetFile(String target,
ChildThread[] childThreads, int threadNum) throws IOException { // 完成把臨時文件夾內的日誌都寫到目標文件中
System.out.println("KAISHI HEBING");
boolean result = true;
FileInputStream inputStream = null;
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(target, true); // 追加內容
for (int i = 0; i < threadNum; i++) { // 遍歷全部子線程建立的臨時文件,按順序把下載內容寫入目標文件中
inputStream = new FileInputStream(
childThreads[i].localTempFileName);
int len = 0;
byte[] b = new byte[1024];
int count = 0;
while ((len = inputStream.read(b)) != -1) {
outputStream.write(b, 0, len);
outputStream.flush();
count += len;
}
inputStream.close();
}
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (outputStream != null) {
outputStream.close();
}
File file = new File(target);
System.out.print(target + "下載獲得的文件大小是 " + file.length());
deleteTempFile(path);// 刪除臨時文件夾
return result;
}
public static void deleteTempFile(String Path) {//刪除臨時文件夾
File file = new File(Path);
if (file.isFile()) {// 表示該文件不是文件夾
file.delete();
} else {
// 首先獲得當前的路徑
String[] childFilePaths = file.list();
for (String childFilePath : childFilePaths) {
File childFile = new File(file.getAbsolutePath() + "/"
+ childFilePath);
String s = childFile.getAbsolutePath();
deleteTempFile(s);
}
file.delete();
}
}
}
|
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.net.ftp.FTPClient;
public class ChildThread extends Thread {
public int id;
private long startPosition;
CountDownLatch latch;
String remoteFileName; //要下載的文件在軟件中心的文件
String localTempFileName; //用於存放每一個線程下載的臨時文件的絕對路徑 (帶上臨時文件的名字和後綴)
String path;//臨時文件夾的目錄
FTPClient ftpClient = new FTPClient();
public ChildThread(String OStype,String fileName, String storePath,
String server, String userName, String password,long startPos,int id,CountDownLatch latch) {
ftpClient.enterLocalPassiveMode(); // 這一句話必定要記得加上
remoteFileName = "/mnt/data/ftp/www/"+OStype.toLowerCase() +"/"+ fileName; // 服務器上的文件,前面是文件夾的名字,後面的是文件的名字
startPosition=startPos;
this.latch=latch;
this.id=id;
try {
ftpClient.connect(server);
ftpClient.login(userName, password);
ftpClient.setBufferSize(1024);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); // 設置文件類型(二進制)
if (storePath.endsWith("\\") || storePath.endsWith("/"))//給出的路徑下新建一個臨時文件夾,裏面存儲的是各個線程下載的文件
{
localTempFileName=storePath +fileName.substring(0, fileName.indexOf("."))+"Temp/" +id+"_"+fileName;//保證臨時文件夾惟一 也應保證臨時文件的命名惟一
} else{
localTempFileName=storePath + "/" +fileName.substring(0, fileName.indexOf("."))+"Temp/" + id+"_"+fileName;
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("FTP客戶端出錯!", e);
} finally {
}
}
public void run() {
FileOutputStream outputStream = null;
try {
File threadTempFile=new File(localTempFileName);
outputStream = new FileOutputStream(localTempFileName,true);
ftpClient.setRestartOffset(startPosition+threadTempFile.length()); //設置每一個線程開始的下載位置 若是以前threadTempFile.length()不等於0,則從上次那個地方繼續下載 斷點下載
InputStream in= ftpClient.retrieveFileStream(remoteFileName);
int len = 0;
byte[] b = new byte[1024];
long count=threadTempFile.length();
while((len = in.read(b)) != -1) {
count +=len;//記錄文件中的長度加上此次準備寫的長度
if (count > FtpTransFile.threadBlock) { //加上最後一次讀到的已經比規定的線程塊大,則只取前面一部分便可
int lastLen= (int) (FtpTransFile.threadBlock-threadTempFile.length());
outputStream.write(b, 0,lastLen);//方法write(b, off, len),b[off]是寫入的第一個字節和b[off+len-1]是寫的這個操做的最後一個字節。
outputStream.flush();
break;
}
outputStream.write(b, 0, len);
outputStream.flush();
}
in.close();//關閉流
File file=new File(localTempFileName);
System.out.println("Thread file "+id+" "+file.length());
outputStream.close();
ftpClient.disconnect();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
latch.countDown();//每一個線程結束的時候,則總的線程數減1
}
}
|