1. 程式人生 > 其它 >C# FTP上傳(支援斷點續傳)

C# FTP上傳(支援斷點續傳)

技術標籤:C#.netc#

當前博文只支援上傳功能,已經過測試,可以直接使用,但請注意你的使用場景,

本人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);
        }
    }