C# FTP上傳(支援斷點續傳)
阿新 • • 發佈:2020-12-20
當前博文只支援上傳功能,已經過測試,可以直接使用,但請注意你的使用場景,
本人FTP服務端使用 FileZilla Server.
日誌記錄請看君修改成自己的。初始時修改自己的檔案目錄。本文上傳檔案採用佇列形式。廢話就不多說了,直接上程式碼。
public class LoadFileEventArgs : CancelEventArgs { /// <summary>Ftp檔案</summary> public String Src { get; set; } /// <summary>Ftp檔案目錄</summary> public String SrcDir { get; set; } /// <summary>原始檔大小</summary> public Int64 SrcSize { get; set; } /// <summary>目標檔案</summary> public String Des { get; set; } /// <summary>目標檔案大小</summary> public Int64 DesSize { get; set; } /// <summary>斷點續傳</summary> public bool FileAppend { get; set; } /// <summary>該檔案需要壓縮</summary> //public Boolean NeedZipFile { get; set; } /// <summary>該檔案太大需要分段壓縮</summary> //public Boolean NeedSubZipFile { get; set; } }
public class FtpHelper { public String ftpHostName; public String ftpUserId; public String ftpPassword; public String fileParam; // ../Datas/Robot-{引數}/ private static readonly object sys = new object(); private static FtpHelper instance = null; private Queue<LoadFileEventArgs> pathQueue = new Queue<LoadFileEventArgs>(); private String ftpURI { get { return String.Format("ftp://{0}", ftpHostName); } } private String localDir { get { return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Datas\\"); } } private String remoteDir { get { return String.Format("Robot-{0}//", fileParam); } } public static FtpHelper Instance { get { if (instance == null){ lock (sys){ if (instance == null) instance = new FtpHelper(); } } return instance; } } private delegate bool AsyncDelegate(LoadFileEventArgs arg); private event EventHandler<LoadFileEventArgs> onUploadFileEvent; private event EventHandler<LoadFileEventArgs> onUploadFileFinishedEvent; public event EventHandler onMonitorUploadEvent; private bool IsWorking = false;//上傳工作是否在進行; private FtpWebResponse webresp = null; private FtpHelper() { onUploadFileEvent += FtpHelper_onUploadFileEvent; onUploadFileFinishedEvent += FtpHelper_onUploadFileFinishedEvent; onMonitorUploadEvent += FtpHelper_onMonitorUploadEvent; this.onMonitorUploadEvent?.Invoke(null, null); //測試時註釋 } /// <summary> /// 測試介面,正式釋出時將要註釋 /// </summary> public void FuncInit() { //UploadDirectory(); } /// <summary> /// 上傳目錄下檔案 /// </summary> private void UploadDirectory() { if (!System.IO.Directory.Exists(localDir)) return; String[] fileArray; String[] pathArray = System.IO.Directory.GetDirectories(localDir); String strDir = String.Empty; foreach (String str in pathArray) { fileArray = System.IO.Directory.GetFiles(str); foreach (String temp in fileArray) { InsertQueue(temp, str); } } //準備上傳檔案 PerUpload(); } /// <summary> /// 插入佇列 /// </summary> /// <param name="filePath"></param> /// <param name="dirPath"></param> void InsertQueue(String filePath, String dirPath = "") { if (!System.IO.File.Exists(filePath)) return; if (String.IsNullOrEmpty(dirPath)){ String[] pathArray = System.IO.Directory.GetDirectories(localDir); foreach (String str in pathArray){ if (filePath.Contains(str)){ dirPath = str; break; } } } System.IO.FileInfo fi = new System.IO.FileInfo(filePath); String strDir = String.Format("{0}{1}//{2}//{3}//{4}", remoteDir, dirPath.Split('\\').Last(), fi.CreationTime.Date.Year, fi.CreationTime.Date.Month, fi.CreationTime.Date.Day); this.pathQueue.Enqueue(new LoadFileEventArgs{ Des = filePath, SrcDir = String.Format("ftp://{0}/{1}", ftpHostName, strDir), Src = String.Format("ftp://{0}//{1}/{2}", ftpHostName, strDir, fi.Name), DesSize = fi.Length, SrcSize = 0, FileAppend = false }); } private void PerUpload() { Task.Factory.StartNew(() => { if (FTPConnonect()){ try { IsWorking = true; AsyncDelegate uploadDelegate; while (this.pathQueue != null && this.pathQueue.Count > 0){ if (!FTPConnonect()) break; System.Threading.Thread.Sleep(100); LoadFileEventArgs args = this.pathQueue.Peek(); //檢查上傳檔案所在的目錄是否存在,不存在則建立所在的目錄。 MakeDir(args.SrcDir); //檢查檔案是否已存在並判斷檔案大小 Boolean res = HandelMatchFile(args); if (!res){ //FTP伺服器上檔案已存在,刪除準備出棧的佇列。 this.pathQueue.Dequeue(); continue; } uploadDelegate = new AsyncDelegate(instance.Upload); IAsyncResult iasync = uploadDelegate.BeginInvoke(args, new AsyncCallback(FuncCallBack), null); bool result = uploadDelegate.EndInvoke(iasync); if (result){ this.onUploadFileFinishedEvent?.Invoke(null, args); } else this.pathQueue.Dequeue(); } } catch (System.Exception ex){ SaveLog.WriteLog(new LogObj() { MessageText = String.Format("檔案上傳FTP伺服器端異常:{0}", ex.ToString()), MessageType = LogType.Info }); } finally { IsWorking = false; } } }); } private bool FTPConnonect(){ DateTime currTime = DateTime.Now; int timed = 300; //約定定時時長300s=5分鐘。 while (true){ try { //超過約定時長,退出迴圈 if (DateTime.Now.Subtract(currTime).Seconds > timed) return false; using (webresp = (FtpWebResponse)SetFtpWebRequest(ftpURI, WebRequestMethods.Ftp.ListDirectory).GetResponse()){ webresp.Dispose(); webresp.Close(); return true; } } catch (Exception ex){ SaveLog.WriteLog(new LogObj() { MessageText = String.Format("FTP伺服器端連線失敗,異常:{0}", ex.ToString()), MessageType = LogType.Info }); if (webresp != null){ webresp.Dispose(); webresp.Close(); } System.Threading.Thread.Sleep(10000); } } } /// <summary> /// 檔案上傳 /// </summary> /// <param name="arg"></param> /// <returns></returns> private bool Upload(LoadFileEventArgs arg) { Stream reqStream = null; FileStream fs = null; FtpWebResponse uploadResponse = null; Uri uri = new Uri(arg.Src); FtpWebRequest reqFtp = (FtpWebRequest)WebRequest.Create(uri); reqFtp.Method = WebRequestMethods.Ftp.UploadFile; reqFtp.KeepAlive = false; reqFtp.Credentials = new NetworkCredential(ftpUserId, ftpPassword); reqFtp.UsePassive = false; reqFtp.UseBinary = true; reqFtp.ContentLength = arg.DesSize; reqFtp.Timeout = 10000; fs = System.IO.File.Open(arg.Des, FileMode.Open); byte[] buffer = new byte[2024]; int bytesRead; try { if (arg.FileAppend){ //斷點續傳 reqFtp.Method = WebRequestMethods.Ftp.AppendFile; reqFtp.ContentOffset = arg.SrcSize; //fs.Position = arg.SrcSize; fs.Seek(arg.SrcSize, 0); } using (reqStream = reqFtp.GetRequestStream()){ while (true){ bytesRead = fs.Read(buffer, 0, buffer.Length); if (bytesRead == 0) break; reqStream.Write(buffer, 0, bytesRead); } reqStream.Dispose(); reqStream.Close(); } uploadResponse = reqFtp.GetResponse() as FtpWebResponse; return true; } catch (Exception ex){ SaveLog.WriteLog(new LogObj() { MessageText = String.Format("上傳檔案到ftp伺服器出錯:{0}", ex.ToString()), MessageType = LogType.Info }); return false; } finally{ if (reqStream != null){ reqStream.Dispose(); reqStream.Close(); } if (fs != null){ fs.Dispose(); fs.Close(); } if (uploadResponse != null){ uploadResponse.Dispose(); uploadResponse.Close(); } } } /// <summary> /// 回撥函式,暫時保留 /// </summary> /// <param name="res"></param> void FuncCallBack(IAsyncResult res) { if (res.IsCompleted) { //暫時保留 } } /// <summary> /// FTP伺服器端建立資料夾。 /// </summary> /// <param name="strPath"></param> private void MakeDir(String strPath) { try{ if (RemoteDirExist(strPath)) return; using (webresp = (FtpWebResponse)SetFtpWebRequest(strPath, WebRequestMethods.Ftp.MakeDirectory).GetResponse()){ using (Stream ftpStream = webresp.GetResponseStream()){ ftpStream.Dispose(); ftpStream.Close(); webresp.Dispose(); webresp.Close(); } } } catch (Exception ex){ SaveLog.WriteLog(new LogObj() { MessageText = String.Format("FTP伺服器端建立資料夾異常:{0}", ex.ToString()), MessageType = LogType.Info }); } } /// <summary> /// 檢查FTP服務端所匹配的檔案(伺服器斷網、客戶端斷網等多種因素造成) /// </summary> private Boolean HandelMatchFile(LoadFileEventArgs args) { Boolean res = true; Tuple<int, int> tuple = CompareFileSize(args); switch (tuple.Item1) { case 1: break; case 2: //嘗試斷點續傳服務端檔案 args.FileAppend = true; args.SrcSize = tuple.Item2; res = true; break; case 3: try { //刪除本地檔案 System.IO.File.Delete(args.Des); } catch (System.Exception ex){ SaveLog.WriteLog(new LogObj() { MessageText = String.Format("###刪除本地檔案失敗,異常原因:{0}", ex.Message), MessageType = LogType.Error }); } res = false; break; default: res = false; break; } return res; } /// <summary> /// 檢查FTP服務端是否存在當前目錄 /// </summary> /// <param name="remoteDirName"></param> /// <returns></returns> private bool RemoteDirExist(String remoteDirName) { try{ using (webresp = (FtpWebResponse)SetFtpWebRequest(remoteDirName, WebRequestMethods.Ftp.ListDirectoryDetails).GetResponse()) { webresp.Dispose(); webresp.Close(); return true; } } catch (System.Exception ex){ if (webresp != null){ webresp.Dispose(); webresp.Close(); } SaveLog.WriteLog(new LogObj() { MessageText = String.Format("檢查FTP伺服器端目錄檔案造成異常:{0}", ex.ToString()), MessageType = LogType.Info }); return false; } } /// <summary> /// 獲取FTP服務端檔案基本資訊(檔名、大小),檔案不存在返回空 /// </summary> /// <param name="arg"></param> /// <returns></returns> private String GetRemoteFileDetails(String uri) { StreamReader sr = null; String content = String.Empty; try { using (webresp = (FtpWebResponse)SetFtpWebRequest(uri, WebRequestMethods.Ftp.ListDirectoryDetails).GetResponse()){ using (sr = new StreamReader(webresp.GetResponseStream(), Encoding.Default)){ content = sr.ReadLine(); sr.Dispose(); sr.Close(); webresp.Dispose(); webresp.Close(); return content; } } } catch(System.Exception ex){ if (sr != null){ sr.Dispose(); sr.Close(); } if (webresp != null){ webresp.Dispose(); webresp.Close(); } //SaveLog.WriteLog(new LogObj() { MessageText = String.Format("獲取FTP伺服器端檔案資訊時產生異常:{0}", ex.ToString()), MessageType = LogType.Info }); return content; } } /// <summary> /// 刪除FTP服務端檔案 /// </summary> /// <param name="uri"></param> private void DelRemoteFile(String uri) { StreamReader sr = null; String content = String.Empty; try { using (webresp = (FtpWebResponse)SetFtpWebRequest(uri, WebRequestMethods.Ftp.DeleteFile).GetResponse()){ using (sr = new StreamReader(webresp.GetResponseStream(), Encoding.Default)){ content = sr.ReadToEnd(); sr.Dispose(); sr.Close(); webresp.Dispose(); webresp.Close(); } } } catch { if (sr != null){ sr.Dispose(); sr.Close(); } if (webresp != null){ webresp.Dispose(); webresp.Close(); } } } /// <summary> /// 設定FTP WebRequest引數 /// </summary> /// <param name="uri"></param> /// <param name="webRequestMethods"></param> /// <param name="timeOut"></param> /// <returns></returns> FtpWebRequest SetFtpWebRequest(String uri, String webRequestMethods, int timeOut=3000) { FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create(new Uri(uri)); ftpRequest.Method = webRequestMethods; ftpRequest.UsePassive = false; ftpRequest.UseBinary = true; ftpRequest.KeepAlive = false; ftpRequest.Timeout = timeOut; ftpRequest.Credentials = new NetworkCredential(ftpUserId, ftpPassword); return ftpRequest; } /// <summary> /// 對比遠端檔案大小 /// 1. 遠端無此檔案返回 --- 1 /// 2. 遠端檔案比本地小 --- 2 /// 3. 遠端檔案與本地大小一致 --- 3 /// </summary> /// <param name="args"></param> /// <returns></returns> private Tuple<int, int> CompareFileSize(LoadFileEventArgs args) { String content = GetRemoteFileDetails(args.Src); if (String.IsNullOrEmpty(content)) return new Tuple<int, int>(1, 0); //匹配已上傳檔案的大小 Regex regex = new Regex(@"^-.+?\sftp.+?\s+(?<size>\d+)", RegexOptions.IgnoreCase); Match match = regex.Match(content); if (match.Success) { int fileSize = Convert.ToInt32(match.Groups["size"].Value); if (fileSize < args.DesSize) return new Tuple<int, int>(2, fileSize); return new Tuple<int, int>(3, fileSize); } return new Tuple<int, int>(2, 0); } /// <summary> /// 獲取FTP伺服器端檔案大小 /// </summary> /// <param name="src"></param> /// <returns></returns> long GetRemoteFileSize(String src) { long size; try { using (webresp = (FtpWebResponse)SetFtpWebRequest(src, WebRequestMethods.Ftp.GetFileSize).GetResponse()) { size = webresp.ContentLength; webresp.Dispose(); webresp.Close(); return size; } } catch (System.Exception ex) { if (webresp != null) { webresp.Dispose(); webresp.Close(); } SaveLog.WriteLog(new LogObj() { MessageText = String.Format("獲取FTP伺服器端檔案大小時產生異常:{0}", ex.ToString()), MessageType = LogType.Info }); return 0; } } /// <summary> /// 單個檔案上傳完畢,觸發此事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void FtpHelper_onUploadFileFinishedEvent(object sender, LoadFileEventArgs e){ //String content = GetRemoteFileDetails(e.Src); long size = GetRemoteFileSize(e.Src); if (size == 0) { SaveLog.WriteLog(new LogObj() { MessageText = String.Format("###當前上傳的檔案可能出現異常或者遠端無法連線上,檔案源路徑:{0}; 上傳伺服器路徑:{1}", e.Des, e.Src), MessageType = LogType.Error }); } else { if (size == e.DesSize){ SaveLog.WriteLog(new LogObj() { MessageText = String.Format("###檔案上傳成功檔案大小為{0}, 伺服器路徑:{1}", e.DesSize, e.Src), MessageType = LogType.Error }); this.pathQueue.Dequeue();//刪除準備出棧的佇列 try { //刪除本地檔案 System.IO.File.Delete(e.Des); } catch (System.Exception ex) { SaveLog.WriteLog(new LogObj() { MessageText = String.Format("###刪除本地檔案失敗,異常原因:{0}", ex.Message), MessageType = LogType.Error }); } } else { SaveLog.WriteLog(new LogObj() { MessageText = "###檔案上傳完畢,但本地與遠端檔案大小不一致,將刪除遠端檔案再重新上傳。", MessageType = LogType.Error }); DelRemoteFile(e.Src); } } } private void FtpHelper_onUploadFileEvent(object sender, LoadFileEventArgs e){ } private void FtpHelper_onMonitorUploadEvent(object sender, EventArgs e){ Task.Factory.StartNew(() => { while (true) { if (FTPConnonect()){ if (!IsWorking && this.pathQueue != null && this.pathQueue.Count > 0){ PerUpload(); System.Threading.Thread.Sleep(5000); } else if (!IsWorking && (this.pathQueue == null || this.pathQueue.Count == 0)){ UploadDirectory(); System.Threading.Thread.Sleep(5000); } else if (IsWorking){ System.Threading.Thread.Sleep(5000); } } else System.Threading.Thread.Sleep(10000); } }); } /// <summary> /// 上傳檔案到FTP服務端 /// </summary> /// <param name="src">本地檔案路徑</param> public void Put(String src) { String path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, src); InsertQueue(path, String.Empty); } }