admin 管理员组

文章数量: 887021

Demo

由于研究Winform项目的版本自动检测更新,需要用到Ftp下载更新包文件。
特写出这个小Demo.
我的Ftp是本地用IIS搭建的FTP服务器,下载过一个1.8G的视频文件,亲测有效。
注意点:

  1. Ftp类编写需要参考FTP的帮助文档
  2. 同步更新下载进度状态、下载完成状态的方法:将更新事件的方法绑定到Ftp封装类公布的事件中。
  3. 多线程控制窗体控件的时候不能直接对控件进行赋值等操作,需要将操控UI的代码写在一个委托当中,然后用This.Invoke(操控控件委托)。

运行效果如图:

内容比较多,我会上传Demo附件

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Framework;namespace DemoFtp
{public partial class Form1 : Form{public Form1(){InitializeComponent();txtLocalPath.Text = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);}/// <summary>/// 主线程下载/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnDownLoad_Click(object sender, EventArgs e){FtpClient ftpClient = new FtpClient("192.168.0.104","/","FtpUser","Chendong144216,");string remoteFilePath = txtRemotePath.Text;string fileName = Path.GetFileName(remoteFilePath);string localFilePath = txtLocalPath.Text + "\\" + fileName;ftpClient.DownLoadFile(remoteFilePath, localFilePath);}/// <summary>/// 异步下载/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnDownloadAsync_Click(object sender, EventArgs e){FtpClient ftpClient = new FtpClient("192.168.0.104", "/", "FtpUser", "Chendong144216,");string remoteFilePath = txtRemotePath.Text;string fileName = Path.GetFileName(remoteFilePath);string localFilePath = txtLocalPath.Text + "\\" + fileName;ftpClient.DownloadProgressChanged += DownloadProgressChanged;ftpClient.DownloadDataCompleted += DownLoadDataComplete;Task<long> task = ftpClient.DownLoadFileAsync(remoteFilePath, txtLocalPath.Text, fileName);}/// <summary>/// 传入委托的方法,用于设置完成状态/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void DownLoadDataComplete(object sender, FtpClient.UpDownLoadCompletedArgs e){Action action = ()=> {pgb.Value = 100;lblProgressValue.Text = "下载完毕";};this.Invoke(action);}/// <summary>/// 传入委托的方法,用于设置下载进度状态/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void DownloadProgressChanged(object sender, FtpClient.UpDownLoadProcessArgs e){Action<int> action = v =>{pgb.Value = v;lblProgressValue.Text = $"下载进度 {v}%";};this.Invoke(action, e.ProgressPercentage);}}
}

Ftp封装类

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;namespace Framework
{/// <summary>/// 文件结构信息/// </summary>public struct FtpFileStruct{/// <summary>/// /// </summary>public string Flags;/// <summary>/// 拥有者/// </summary>public string Owner;/// <summary>/// 文件组/// </summary>public string Group;/// <summary>/// 是否目录/// </summary>public bool IsDirectory;/// <summary>/// 创建时间/// </summary>public DateTime CreateTime;/// <summary>/// 文件名/// </summary>public string Name;/// <summary>/// 文件长度/// </summary>public double Length;}/// <summary>/// 文件列表类型/// </summary>public enum FtpFileListStyle{UnixStyle,      //UNIX类型WindowsStyle,   //WINDOWSUnknown         //未知}/// <summary>/// 传输模式:二进制类型、ASCII类型/// </summary>public enum FtpTransferType { Binary, ASCII };/// <summary>/// Ftp上传下载/// </summary>public class FtpClient : IDisposable         //Ftp上传下载{#region Private Fields/// <summary>/// 用户名/// </summary>private string _UserName;/// <summary>/// 密码/// </summary>private string _Password;/// <summary>/// 初始创建Ftp类时候制定的根目录/// </summary>private string _RootDirectory;/// <summary>/// 工作目录/// </summary>private string _WorkDirectory;/// <summary>/// 服务器返回的应答信息(包含应答码)/// </summary>private string ReplyMessage { get; set; }/// <summary>/// 服务器返回的应答码/// </summary>private int ReplyCode { get; set; }/// <summary>/// 进行控制连接的socket/// </summary>private Socket _SocketControl;/// <summary>/// 传输模式/// </summary>private FtpTransferType _TransferType = Framework.FtpTransferType.ASCII;/// <summary>/// 接收和发送数据的缓冲区/// </summary>private Byte[] _Buffer = new Byte[BUFFER_SIZE];/// <summary>/// 当前尝试连接ID,用于报错/// </summary>IPAddress _IPAddress;int tryConnectTimes = 0;#endregion#region Property/// <summary>/// FTP服务器IP地址/// </summary>public string Host { get; set; }/// <summary>/// FTP服务器端口/// </summary>public int Port { get; set; }/// <summary>/// 目录/// </summary>public string WorkDirectory{get{if (!string.IsNullOrEmpty(_WorkDirectory)) return _WorkDirectory;if (!IsConnected) Connect();SetCurrentWorkDirectory();return _WorkDirectory;}set{if (string.IsNullOrEmpty(_RootDirectory))//第一次设置Directory的时候,定RootDirectory_RootDirectory = value;if (_WorkDirectory != value){_WorkDirectory = value;if (IsConnected)//如果正在连接,则更换当前目录ChangeWorkDirectory(_WorkDirectory);}}}/// <summary>/// 在不知道当前路径的情况下提取并设置对应字段/// </summary>private void SetCurrentWorkDirectory(){SendCommand("PWD ");if (!(ReplyCode == 257)) throw new IOException(ReplyMessage.Substring(4));//return strPath;string dir = ReplyMessage.Substring(5);_WorkDirectory = dir.Substring(0, dir.IndexOf("\""));}/// <summary>/// 异步事件的间隔时间,毫秒/// </summary>public int AsyncTriggerInterval { get; set; } = 200;/// <summary>/// 缓冲大小/// </summary>public static int BUFFER_SIZE { get; set; } = 1024 * 8;/// <summary>/// 编码方式/// </summary>public Encoding Encoder { get; set; } = Encoding.Default;/// <summary>/// 代理/// </summary>public WebProxy WebProxy { get; }/// <summary>/// 是否登录/// </summary>public bool IsConnected { get; private set; }/// <summary>/// 获得传输模式/// </summary>/// <returns>传输模式</returns>public FtpTransferType TransferType{get { return _TransferType; }set {if (value == _TransferType) return; //如果TransferType已经一样则不重复发送if (value == FtpTransferType.Binary)SendCommand("TYPE I");//binary类型传输elseSendCommand("TYPE A");//ASCII类型传输if (ReplyCode != 200)throw new IOException(ReplyMessage.Substring(4));else_TransferType = value;}}#endregion#region EventArgs/// <summary>/// 上传下载进度/// </summary>public class UpDownLoadProcessArgs : EventArgs{private long _TotalBytes;private long _CurrentBytes;public UpDownLoadProcessArgs(long currentBytes, long totalBytes){_CurrentBytes = currentBytes;_TotalBytes = totalBytes;}/// <summary>/// 完成进度百分比/// </summary>public int ProgressPercentage{get { return (int)(((double)_CurrentBytes / (double)_TotalBytes) * 100); }}/// <summary>/// 总字节数/// </summary>public long TotalBytes{get { return _TotalBytes; }}/// <summary>/// 当前字节数/// </summary>public long CurrentBytes{get { return _CurrentBytes; }}}/// <summary>/// 上传下载完成事件/// </summary>public class UpDownLoadCompletedArgs : EventArgs{private readonly long _TotalBytes;private readonly long _CurrentBytes;public UpDownLoadCompletedArgs(long curBytes, long totalBytes){_CurrentBytes = curBytes;_TotalBytes = totalBytes;}/// <summary>/// 进度百分比/// </summary>public int ProgressPercentage{get { return (int)(((double)_CurrentBytes / (double)_TotalBytes) * 100); }}/// <summary>/// 是否完成/// </summary>public bool IsCompleted{get { return _CurrentBytes >= _TotalBytes; }}/// <summary>/// 总字节数/// </summary>public long TotalBytes{get { return _TotalBytes; }}/// <summary>/// 当前字节数/// </summary>public long CurrentBytes{get { return _CurrentBytes; }}}/// <summary>/// 返回服务器特性/// </summary>/// <returns></returns>public string Feature{get{SendCommand("FEAT");return ReplyMessage;}}#endregion#region Deletegatepublic delegate void FtpDownloadProgressChanged(object sender, UpDownLoadProcessArgs e);public delegate void FtpDownloadDataCompleted(object sender, UpDownLoadCompletedArgs e);public delegate void FtpUploadProgressChanged(object sender, UpDownLoadProcessArgs e);public delegate void FtpUploadFileCompleted(object sender, UpDownLoadCompletedArgs e);#endregion#region Event/// <summary>/// 异步下载进度发生改变触发的事件/// </summary>public event FtpDownloadProgressChanged DownloadProgressChanged;/// <summary>/// 异步下载文件完成之后触发的事件/// </summary>public event FtpDownloadDataCompleted DownloadDataCompleted;/// <summary>/// 异步上传进度发生改变触发的事件/// </summary>public event FtpUploadProgressChanged UploadProgressChanged;/// <summary>/// 异步上传文件完成之后触发的事件/// </summary>public event FtpUploadFileCompleted UploadFileCompleted;#endregion#region Constructor~FtpClient(){Dispose(false);}/// <summary>/// 缺省构造函数/// </summary>public FtpClient(){Host = "";WorkDirectory = "/";_UserName = "";_Password = "";Port = 21;IsConnected = false;}/// <summary>/// 构造函数/// </summary>/// <param name="remoteHost">服务器名称</param>/// <param name="remotePath">服务器目录</param>/// <param name="remoteUser">用户名</param>/// <param name="remotePass">密码</param>/// <param name="remotePort">端口</param>public FtpClient(string remoteHost, string remotePath, string remoteUser, string remotePass, int remotePort = 21, string code = "DEFAULT"){Host = remoteHost;_UserName = remoteUser;_Password = remotePass;Port = remotePort;this.Encoder = GetEncoder(code);Connect();WorkDirectory = remotePath;     //在设置初始化文件夹的时候会自动Connect()}public FtpClient(Uri FtpUri, string strUserName, string strPassword, int Port, string code = "UNICODE", WebProxy objProxy = null){this.Host = FtpUri.Host;WorkDirectory = FtpUri.AbsolutePath;if (!WorkDirectory.EndsWith("/"))WorkDirectory += "/";_UserName = strUserName;_Password = strPassword;WebProxy = objProxy;this.Port = Port;Encoder = GetEncoder(code);Connect();}#endregion#region IDisposable Supportprivate bool _DisposedValue = false; // 要检测冗余调用protected virtual void Dispose(bool disposing){if (_DisposedValue) return;if (disposing){// TODO: 释放托管状态(托管对象)。}//CloseConnect();// TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。// TODO: 将大型字段设置为 null。_DisposedValue = true;}// 添加此代码以正确实现可处置模式。public void Dispose(){// 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。Dispose(true);// TODO: 如果在以上内容中替代了终结器,则取消注释以下行。GC.SuppressFinalize(this);}#endregion#region Function/// <summary>/// 获得一个FtpClient副本/// </summary>/// <returns></returns>public FtpClient Clone(){return new FtpClient(Host, WorkDirectory, _UserName, _Password, Port, Encoder.ToString());}/// <summary>/// 建立连接 /// </summary>public void Connect(){//绑定主机IPHostEntry iPHostEntry = Dns.GetHostEntry(Host);_IPAddress = iPHostEntry.AddressList[iPHostEntry.AddressList.Length-1];IPEndPoint ep = new IPEndPoint(_IPAddress, Port);switch (ep.AddressFamily){//同时支持IP6,IP4case AddressFamily.InterNetwork:_SocketControl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);break;case AddressFamily.InterNetworkV6:_SocketControl = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);break;default:_SocketControl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);break;}// 链接try{_SocketControl.Connect(ep);}catch (Exception){IsConnected = false;return;}FetchReply();        // 获取应答码if (ReplyCode != 220) //在连接到一台FTP服务器的端口21并接收到一个由代码220打头的行,表示服务器已准备好你向它发USER和PASS命令,以登录进此FTP服务器之后,紧跟着发送USER命令。{IsConnected = false;return;}SendCommand("USER " + _UserName);if (!(ReplyCode == 331 || ReplyCode == 230)){CloseSocketConnect();//关闭连接throw new IOException(ReplyMessage.Substring(4));}if (ReplyCode != 230){SendCommand("PASS " + _Password);if (!(ReplyCode == 230 || ReplyCode == 202)){CloseSocketConnect();//关闭连接throw new IOException(ReplyMessage.Substring(4));}}IsConnected = true;   // 切换到目录ChangeWorkDirectory(WorkDirectory);}/// <summary>/// 关闭连接/// </summary>public void CloseConnect(){if (_SocketControl != null && IsConnected){SendCommand("QUIT");}CloseSocketConnect();}/// <summary>/// 得到文件信息/// </summary>/// <param name="dataString">Ftp服务器接收到LIST命令后,返回的列表字符串。</param>/// <returns></returns>private FtpFileStruct[] GetFileStructArray(string dataString){List<FtpFileStruct> FileList = new List<FtpFileStruct>();string[] dataRecords = dataString.Split('\n');FtpFileListStyle fileStyle = GuessFileListStyle(dataRecords);foreach (string dataRecord in dataRecords){if (fileStyle != FtpFileListStyle.Unknown && dataRecord != ""){FtpFileStruct newFileStruct = new FtpFileStruct{Name = ".."};switch (fileStyle){case FtpFileListStyle.UnixStyle:newFileStruct = ParseFileStructFromUnixStyle(dataRecord);break;case FtpFileListStyle.WindowsStyle:newFileStruct = ParseFileStructFromWindowsStyle(dataRecord);break;}if (!(newFileStruct.Name == "." || newFileStruct.Name == ".."))FileList.Add(newFileStruct);}}return FileList.ToArray();}/// <summary>/// 从Unix格式中返回文件信息/// </summary>/// <param name="dataRecord">文件信息</param>private FtpFileStruct ParseFileStructFromUnixStyle(string dataRecord){FtpFileStruct newFileStruct = new FtpFileStruct();string processstr = dataRecord.Trim();newFileStruct.Flags = processstr.Substring(0, 10);newFileStruct.IsDirectory = (newFileStruct.Flags[0] == 'd');processstr = (processstr.Substring(11)).Trim();_cutSubstringFromStringWithTrim(ref processstr, ' ', 0);   //跳过一部分newFileStruct.Owner = _cutSubstringFromStringWithTrim(ref processstr, ' ', 0);newFileStruct.Group = _cutSubstringFromStringWithTrim(ref processstr, ' ', 0);_cutSubstringFromStringWithTrim(ref processstr, ' ', 0);   //跳过一部分string yearOrTime = processstr.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[2];if (yearOrTime.IndexOf(":") >= 0)  //time{processstr = processstr.Replace(yearOrTime, DateTime.Now.Year.ToString());}newFileStruct.CreateTime = DateTime.Parse(_cutSubstringFromStringWithTrim(ref processstr, ' ', 8));newFileStruct.Name = processstr;   //最后就是名称return newFileStruct;}/// <summary>/// 从Windows格式中返回文件信息/// </summary>/// <param name="dataRecord">文件信息</param>private FtpFileStruct ParseFileStructFromWindowsStyle(string dataRecord){FtpFileStruct newFileStruct = new FtpFileStruct();string str = dataRecord.Trim();string dateString = str.Substring(0, 8);str = (str.Substring(8, str.Length - 8)).Trim();string timeString = str.Substring(0, 7);str = (str.Substring(7, str.Length - 7)).Trim();DateTimeFormatInfo myDTFI = new CultureInfo("en-US", false).DateTimeFormat;myDTFI.ShortTimePattern = "t";newFileStruct.CreateTime = DateTime.Parse(dateString + " " + timeString, myDTFI);if (str.Substring(0, 5) == "<DIR>"){newFileStruct.IsDirectory = true;str = (str.Substring(5, str.Length - 5)).Trim();}else{newFileStruct.Length = Convert.ToDouble(str.Substring(0, str.IndexOf(" ")));str = str.Substring(str.IndexOf(" ") + 1);newFileStruct.IsDirectory = false;}newFileStruct.Name = str;return newFileStruct;}/// <summary>/// 获得文件列表/// </summary>/// <param name="FileFilterString">文件名的匹配字符串</param>/// <returns></returns>public FtpFileStruct[] Dir(string FileFilterString){if (!IsConnected) Connect();// 建立链接Socket dataSocket = CreateDataSocket();SendCommand(WebRequestMethods.Ftp.ListDirectoryDetails + " " + FileFilterString);   //传送命令if (!(ReplyCode == 150 || ReplyCode == 125 || ReplyCode == 226 )) //分析应答代码NLST{return GetFileStructArray("");}   //获得结果string strData = string.Empty;while (true){int iBytes = dataSocket.Receive(_Buffer, _Buffer.Length, 0);strData += Encoder.GetString(_Buffer, 0, iBytes);if (iBytes == 0) break;}dataSocket.Close();//数据socket关闭时也会有返回码if (ReplyCode != 226){FetchReply();if (ReplyCode != 226)return GetFileStructArray("");}return GetFileStructArray(strData);}/// <summary>/// 判断文件是否存在/// </summary>/// <param name="filePath">文件全路径或文件名</param>/// <returns></returns>public bool IsFileExists(string filePath){if (string.IsNullOrEmpty(filePath)) return false;string fileName = Path.GetFileName(filePath);FtpFileStruct[] fileStructArray = Dir(filePath);foreach (FtpFileStruct fileStruct in fileStructArray){if (fileStruct.Name == fileName && !fileStruct.IsDirectory)return true;}return false;}/// <summary>/// 判断目录是否存在/// </summary>/// <param name="directoryName">文件路径名</param>/// <returns></returns>public bool IsDirectoryExists(string directoryName){FtpFileStruct[]  ftpFileStructs= GetDirectorys();//获取子目录foreach (var directoryStruct in ftpFileStructs){if (directoryStruct.Name == directoryName) return true;}return false;}/// <summary>/// 获得文件大小/// </summary>/// <param name="strFileName">文件名称</param>/// <returns></returns>public long GetFileSize(string strFileName){FtpFileStruct[] file = Dir(strFileName);if (file.Length > 0)foreach (FtpFileStruct a in file){if (a.Length > 0) return (int)a.Length;}return 0;}/// <summary>/// 获得文件时间字符串/// </summary>/// <param name="strFileName"></param>/// <returns></returns>public string GetFileCreateTime(string strFileName){FtpFileStruct[] file = Dir(strFileName);if (file.Length > 0)foreach (FtpFileStruct a in file){return a.CreateTime.ToString("yyyy-MM-dd HH:mm:ss");}return null;}/// <summary>/// 删除/// </summary>/// <param name="strFileName">待删除文件名</param>public string DeleteFile(string strFileName){if (!IsConnected) Connect();SendCommand("DELE " + strFileName);if (ReplyCode != 250)return ReplyMessage;return string.Empty;}/// <summary>/// 重命名(如果新文件名与已有文件重名,将覆盖已有文件,需服务器支持)/// </summary>/// <param name="strOldFileName">旧文件名</param>/// <param name="strNewFileName">新文件名</param>/// <param name="overrideFile">是否覆盖己有文件</param>public string Rename(string strOldFileName, string strNewFileName, bool overrideFile = true){if (!IsConnected) Connect();if (overrideFile == true)if (IsFileExists(strNewFileName)) DeleteFile(strNewFileName);SendCommand("RNFR " + strOldFileName);if (ReplyCode != 350) return ReplyMessage;SendCommand("RNTO " + strNewFileName);// 如果新文件名与原有文件重名,将覆盖原有文件if (ReplyCode != 250) return ReplyMessage;return string.Empty;}#endregion#region Function_上传和下载/// <summary>/// 下载文件/// </summary>/// <param name="remoteFileName">远程文件名</param>/// <param name="LocalFilePath">本地文件完整路径</param>/// <returns>如果加载字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。</returns>public long DownLoadFile(string remoteFileName, string LocalFilePath){string strFolder = new FileInfo(LocalFilePath).DirectoryName;return DownLoadFile(remoteFileName, strFolder, Path.GetFileName(LocalFilePath));}/// <summary>/// 异步下载/// </summary>/// <param name="remoteFileName">远程文件名</param>/// <param name="localFilePath">本地文件完整路径</param>/// <param name="cancel">CancellationToken</param>/// <returns></returns>public long DownLoadFile(string remoteFileName, string localFilePath, System.Threading.CancellationToken cancel){string strFolder = new FileInfo(localFilePath).DirectoryName;return DownLoadFile(remoteFileName, strFolder, Path.GetFileName(localFilePath), cancel);}/// <summary>/// 下载文件/// </summary>/// <param name="remoteFileName">远程文件名</param>/// <param name="folderPath">文件夹完整路径</param>/// <param name="localFileName">本地文件名</param>/// <returns>如果加载字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。</returns>public long DownLoadFile(string remoteFileName, string folderPath, string localFileName){if (!IsConnected) Connect();TransferType =FtpTransferType.Binary;if (localFileName.Equals("")) localFileName = remoteFileName;long fileLength = GetFileSize(remoteFileName);Socket dataSocket = CreateDataSocket();long currentPrcess = 0;SendCommand("RETR " + remoteFileName);if (!(ReplyCode == 150 || ReplyCode == 125 || ReplyCode == 226 || ReplyCode == 250)){throw new IOException(ReplyMessage.Substring(4));}FileStream outputFileStream = new FileStream(folderPath + "\\" + localFileName, FileMode.Create);System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();long elapsedMillisecondsRemark = 0;stopWatch.Start();while (true){int intBytes = dataSocket.Receive(_Buffer, _Buffer.Length, 0);currentPrcess += intBytes;outputFileStream.Write(_Buffer, 0, intBytes);if ((stopWatch.ElapsedMilliseconds - elapsedMillisecondsRemark) >= AsyncTriggerInterval){DownloadProgressChanged?.Invoke(this, new UpDownLoadProcessArgs(currentPrcess, fileLength));elapsedMillisecondsRemark = stopWatch.ElapsedMilliseconds;}if (intBytes <= 0){DownloadProgressChanged?.Invoke(this, new UpDownLoadProcessArgs(currentPrcess, fileLength));break;}}stopWatch.Stop();outputFileStream.Close();dataSocket.Close();DownloadDataCompleted?.Invoke(this, new UpDownLoadCompletedArgs(currentPrcess, fileLength));if (!(ReplyCode == 226 || ReplyCode == 250)){FetchReply();if (!(ReplyCode == 226 || ReplyCode == 250))throw new IOException(ReplyMessage.Substring(4));}return currentPrcess != fileLength ? 0 : currentPrcess;     }/// <summary>/// 异步下载/// </summary>/// <param name="remoteFileName">远程文件名</param>/// <param name="folderPath">文件夹完整路径</param>/// <param name="localFileName">本地文件完整路径</param>/// <param name="cancel">CancellationToken</param>/// <returns>如果加载字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。</returns>public long DownLoadFile(string remoteFileName, string folderPath, string localFileName, System.Threading.CancellationToken cancel){if (!IsConnected) Connect();TransferType = FtpTransferType.Binary;if (localFileName.Equals("")) localFileName = remoteFileName;long fileLength = GetFileSize(remoteFileName);Socket dataSocket = CreateDataSocket();long currentPrcess = 0;SendCommand("RETR " + remoteFileName);if (!(ReplyCode == 150 || ReplyCode == 125 || ReplyCode == 226 || ReplyCode == 250))throw new IOException(ReplyMessage.Substring(4));FileStream outputFileStream = new FileStream(folderPath + "\\" + localFileName, FileMode.Create);System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();long elapsedMillisecondsRemark = 0;//定义刷新间隔stopWatch.Start();int intBytes = 0;while (!cancel.IsCancellationRequested){intBytes = dataSocket.Receive(_Buffer, _Buffer.Length, 0);currentPrcess += intBytes;outputFileStream.Write(_Buffer, 0, intBytes);if ((stopWatch.ElapsedMilliseconds - elapsedMillisecondsRemark) >= AsyncTriggerInterval){DownloadProgressChanged?.Invoke(this, new UpDownLoadProcessArgs(currentPrcess, fileLength));elapsedMillisecondsRemark = stopWatch.ElapsedMilliseconds;}if (intBytes <= 0){DownloadProgressChanged?.Invoke(this, new UpDownLoadProcessArgs(currentPrcess, fileLength));break;}}stopWatch.Stop();outputFileStream.Close();dataSocket.Close() ;DownloadDataCompleted?.Invoke(this, new UpDownLoadCompletedArgs(currentPrcess, fileLength));if (!cancel.IsCancellationRequested){//如果没有取消if (!(ReplyCode == 226 || ReplyCode == 250)){FetchReply();if (!(ReplyCode == 226 || ReplyCode == 250))throw new IOException(ReplyMessage.Substring(4));}}return currentPrcess != fileLength ? 0 : currentPrcess;  }/// <summary>/// 下载到文件到字符数组/// </summary>/// <param name="remoteFileName"></param>public byte[] DownLoadBytes(string remoteFileName){if (!IsConnected) Connect();TransferType = Framework.FtpTransferType.Binary;long fileLength = GetFileSize(remoteFileName);Socket dataSocket = CreateDataSocket();long currentPrcess = 0;SendCommand("RETR " + remoteFileName);if (!(ReplyCode == 150 || ReplyCode == 125 || ReplyCode == 226 || ReplyCode == 250))throw new IOException(ReplyMessage.Substring(4));byte[] fileBytesArray = new byte[fileLength];MemoryStream outputMemoryStream = new MemoryStream(fileBytesArray, true);System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();long elapsedMillisecondsRemark = 0;stopWatch.Start();int intBytes;while (true){intBytes = dataSocket.Receive(_Buffer, _Buffer.Length, 0);currentPrcess += intBytes;outputMemoryStream.Write(_Buffer, 0, intBytes);if ((stopWatch.ElapsedMilliseconds - elapsedMillisecondsRemark) >= AsyncTriggerInterval){DownloadProgressChanged?.Invoke(this, new UpDownLoadProcessArgs(currentPrcess, fileLength));elapsedMillisecondsRemark = stopWatch.ElapsedMilliseconds;}if (intBytes <= 0){DownloadProgressChanged?.Invoke(this, new UpDownLoadProcessArgs(currentPrcess, fileLength));break;}}stopWatch.Stop();outputMemoryStream.Close();DownloadDataCompleted?.Invoke(this, new UpDownLoadCompletedArgs(currentPrcess, fileLength));dataSocket.Close();if (!(ReplyCode == 226 || ReplyCode == 250)){FetchReply();if (!(ReplyCode == 226 || ReplyCode == 250)){throw new IOException(ReplyMessage.Substring(4));}}return outputMemoryStream.ToArray();}/// <summary>/// 异步下载文件/// </summary>/// <param name="remoteFilePath">远程文件名</param>/// <param name="localFolder">本地目录</param>/// <param name="localFilePath">本地文件名</param>/// <returns></returns>public Task<long> DownLoadFileAsync(string remoteFilePath, string localFolder, string localFilePath){Task<long> DownloadTask = new Task<long>(() =>{FtpClient tempftp = new FtpClient(Host, WorkDirectory, _UserName, _Password, Port, Encoder.ToString());tempftp.DownloadDataCompleted += DownloadDataCompleted; tempftp.DownloadProgressChanged += DownloadProgressChanged;long size = tempftp.DownLoadFile(remoteFilePath, localFolder, localFilePath);tempftp.Dispose();return size;});DownloadTask.Start();return DownloadTask;}public Task<long> DownLoadFileAsync(string strRemoteFileName, string strFolder, string strLocalFileName, System.Threading.CancellationToken cancel){Task<long> DownloadTask = new Task<long>(() =>{FtpClient tempftp = new FtpClient(Host, WorkDirectory, _UserName, _Password, Port, Encoder.ToString());tempftp.DownloadDataCompleted += DownloadDataCompleted;tempftp.DownloadProgressChanged += DownloadProgressChanged;long size = tempftp.DownLoadFile(strRemoteFileName, strFolder, strLocalFileName, cancel);tempftp.Dispose();return size;}, cancel);DownloadTask.Start();return DownloadTask;}/// <summary>/// 上传一个文件在当前工作目录下/// </summary>/// <param name="uploadFileName">文件名</param>/// <param name="locaFilePath">本地文件完整路径</param>/// <returns>如果上传字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。</returns>public long UploadFile(string uploadFileName, string locaFilePath){if (!IsConnected) Connect();Socket dataSocket = CreateDataSocket();SendCommand("STOR " + Path.GetFileName(uploadFileName));if (!(ReplyCode == 125 || ReplyCode == 150))throw new IOException(ReplyMessage.Substring(4));FileStream inputFileStream = new FileStream(locaFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);long fileLength = inputFileStream.Length;long currentPrcess = 0;System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();long elapsedMillisecondsRemark = 0;stopWatch.Start();int intBytes;while ((intBytes = inputFileStream.Read(_Buffer, 0, _Buffer.Length)) > 0){currentPrcess += intBytes;if ((stopWatch.ElapsedMilliseconds - elapsedMillisecondsRemark) >= AsyncTriggerInterval){UploadProgressChanged?.Invoke(dataSocket, new UpDownLoadProcessArgs(currentPrcess, fileLength));elapsedMillisecondsRemark = stopWatch.ElapsedMilliseconds;}dataSocket.Send(_Buffer, intBytes, 0);}inputFileStream.Close();dataSocket.Close();stopWatch.Stop();UploadFileCompleted?.Invoke(this, new UpDownLoadCompletedArgs(currentPrcess, fileLength));if (!(ReplyCode == 226 || ReplyCode == 250)){FetchReply();if (!(ReplyCode == 226 || ReplyCode == 250))throw new IOException(ReplyMessage.Substring(4));}return currentPrcess == fileLength ? currentPrcess : 0;}/// <summary>/// 异步上传/// </summary>/// <param name="uploadFileName">本地文件完整路径</param>/// <param name="localFilePath">上传为文件完整路径</param>/// <param name="cancel">CancellationToken</param>/// <returns>返回当前字节长度</returns>public long UploadFile(string uploadFileName, string localFilePath, System.Threading.CancellationToken cancel){if (!IsConnected) Connect();Socket dataSocket = CreateDataSocket();SendCommand("STOR " + Path.GetFileName(uploadFileName));if (!(ReplyCode == 125 || ReplyCode == 150))throw new IOException(ReplyMessage.Substring(4));FileStream inputFileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read);long fileLength = inputFileStream.Length;long currentPrcess = 0;System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();long elapsedMillisecondsRemark = 0;stopWatch.Start();int intBytes;while ((intBytes = inputFileStream.Read(_Buffer, 0, _Buffer.Length)) > 0){currentPrcess += intBytes;if ((stopWatch.ElapsedMilliseconds - elapsedMillisecondsRemark) >= AsyncTriggerInterval){UploadProgressChanged?.Invoke(dataSocket, new UpDownLoadProcessArgs(currentPrcess, fileLength));elapsedMillisecondsRemark = stopWatch.ElapsedMilliseconds;}dataSocket.Send(_Buffer, intBytes, 0);if (cancel.IsCancellationRequested) { break; }//取消了进度,不支持继传}inputFileStream.Close();stopWatch.Stop();dataSocket.Close();UploadFileCompleted?.Invoke(this, new UpDownLoadCompletedArgs(currentPrcess, fileLength));if (!cancel.IsCancellationRequested){//如果没有取消if (!(ReplyCode == 226 || ReplyCode == 250)){FetchReply();if (!(ReplyCode == 226 || ReplyCode == 250))throw new IOException(ReplyMessage.Substring(4));}}return currentPrcess;}/// <summary>/// 上传文件扩展,strFileName可以为文件名称或文件全路径名称/// 该函数是使用安全的,上传失败不会覆盖原来的文件/// </summary>/// <param name="uploadFilePath">文件名称或文件全路径名称</param>/// <param name="locaFileName">本地文件全路径名称</param>/// <param name="cancel">取消标记</param>/// <returns>返回当前字节长度</returns>public long UploadFileExt(string uploadFilePath, string locaFileName, System.Threading.CancellationToken cancel){string folderPath = uploadFilePath.Replace(Path.GetFileName(uploadFilePath), "");if (!string.IsNullOrEmpty(folderPath))ChangeWorkDirectory(folderPath);string tempFileName = SuperCode.GetMD5(Path.GetFileName(uploadFilePath));var uploadedFileLength = UploadFile(tempFileName, locaFileName, cancel);if (!cancel.IsCancellationRequested){var error = Rename(tempFileName, uploadFilePath);if (!string.IsNullOrEmpty(error))throw new Exception(error);}DeleteFile(tempFileName);ChangeWorkDirectory(WorkDirectory);return uploadedFileLength;}/// <summary>/// 上传内存文件到服务器/// </summary>/// <param name="fileName"></param>/// <param name="Bytes"></param>/// <returns></returns>public long UploadBytes(string fileName, byte[] Bytes){if (!IsConnected) Connect();Socket dataSocket = CreateDataSocket();SendCommand("STOR " + Path.GetFileName(fileName));if (!(ReplyCode == 125 || ReplyCode == 150))throw new IOException(ReplyMessage.Substring(4));MemoryStream inputMemoryStream = new MemoryStream(Bytes);long fileLength = Bytes.Length;long currentProcess = 0;System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();long elapsedMillisecondsRemark = 0;stopWatch.Start();int intBytes;while ((intBytes = inputMemoryStream.Read(_Buffer, 0, _Buffer.Length)) > 0){currentProcess += intBytes;if ((stopWatch.ElapsedMilliseconds - elapsedMillisecondsRemark) >= AsyncTriggerInterval){UploadProgressChanged?.Invoke(dataSocket, new UpDownLoadProcessArgs(currentProcess, fileLength));elapsedMillisecondsRemark = stopWatch.ElapsedMilliseconds;}dataSocket.Send(_Buffer, intBytes, 0);}inputMemoryStream.Close();dataSocket.Close();UploadFileCompleted?.Invoke(dataSocket, new UpDownLoadCompletedArgs(currentProcess, fileLength));stopWatch.Stop();if (!(ReplyCode == 226 || ReplyCode == 250)){FetchReply();if (!(ReplyCode == 226 || ReplyCode == 250))throw new IOException(ReplyMessage.Substring(4));}return currentProcess;}#endregion#region Function_目录操作/// <summary>/// 得到目录列表/// </summary>/// <returns></returns>public FtpFileStruct[] GetDirectorys(){FtpFileStruct[] fileArray = Dir("");List<FtpFileStruct> fileStructList = new List<FtpFileStruct>();foreach (FtpFileStruct file in fileArray){if (file.IsDirectory == true)fileStructList.Add(file);}return fileStructList.ToArray();}/// <summary>/// 创建目录/// </summary>/// <param name="directoryPath">目录名</param>/// <returns>如果有错误返回错误文本,反之返回空字符串</returns>public string MakeDirectory(string directoryPath){if (!IsConnected) Connect();SendCommand("MKD " + directoryPath);if (!(ReplyCode == 257 || ReplyCode == 250))return ReplyMessage;return string.Empty;}/// <summary>/// 删除目录/// </summary>/// <param name="strDirName">目录名</param>/// <returns>如果有错误返回错误文本,反之返回空字符串</returns>public string RemoveDirectory(string strDirName){if (!IsConnected) Connect();SendCommand("RMD " + strDirName);if (ReplyCode != 250)return ReplyMessage;return string.Empty;}/// <summary>/// 改变目录/// </summary>/// <param name="newDirectory">新的工作目录名</param>private void ChangeWorkDirectory(string newDirectory){if ( String.IsNullOrEmpty(newDirectory)|| newDirectory.Equals(".") )return;if (!IsConnected) Connect();SendCommand("CWD " + newDirectory);if (ReplyCode != 250)throw new IOException(ReplyMessage.Substring(4));}/// <summary>/// 改变工作路径为父文件夹/// </summary>public void ChangeWorkDirectoryToParent(){if (!IsConnected) Connect();SendCommand("CDUP");if (ReplyCode != 250)throw new IOException(ReplyMessage.Substring(4));SetCurrentWorkDirectory();       //设置比骄傲村当前工作路径的对应字段}/// <summary>/// 建立进行数据连接的socket/// </summary>/// <returns>数据连接socket</returns>private Socket CreateDataSocket(){SendCommand("PASV");if (ReplyCode != 227)throw new IOException(ReplyMessage.Substring(4));int index1 = ReplyMessage.IndexOf('(');int index2 = ReplyMessage.IndexOf(')');var ipData = ReplyMessage.Substring(index1 + 1, index2 - index1 - 1).Split(',');int[] parts = new int[6];for (int i = 0; i < ipData.Length; i++){try{parts[i] = Int32.Parse(ipData[i]);}catch (Exception){throw new IOException("不能识别的 PASV 应答码: " + ReplyMessage);}}string ipAddress = parts[0] + "." + parts[1] + "." + parts[2] + "." + parts[3];int port = (parts[4] << 8) + parts[5];Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint ep = new IPEndPoint(IPAddress.Parse(ipAddress), port);try{s.Connect(ep);}catch (Exception){throw new IOException("不能建立数据连接");}return s;}/// <summary>/// 关闭socket连接(用于登录以前)/// </summary>private void CloseSocketConnect(){if (_SocketControl != null){_SocketControl.Close();_SocketControl = null;}IsConnected = false;}/// <summary>/// 读取Socket返回的所有字符串,将一行应答字符串记录在strReply和strMsg,应答码记录在iReplyCode/// </summary>/// <returns>包含应答码的字符串行</returns>private void FetchReply(){ReplyMessage = "";while (true){int bytesLength = _SocketControl.Receive(_Buffer, _Buffer.Length, 0);ReplyMessage += Encoder.GetString(_Buffer, 0, bytesLength);if (bytesLength < _Buffer.Length) break;}char[] seperator = { '\n' };string[] messageArray = ReplyMessage.Split(seperator);if (messageArray.Length > 2)ReplyMessage = messageArray[messageArray.Length - 2];//seperator[0]是10,换行符是由13和10组成的,分隔后10后面虽没有字符串,//但也会分配为空字符串给后面(也是最后一个)字符串数组,//所以最后一个messageArray是没用的空字符串//但为什么不直接取mess[0],因为只有最后一行字符串应答码与信息之间有空格elseReplyMessage = messageArray[0];if (string.IsNullOrEmpty(ReplyMessage)||!ReplyMessage.Substring(3, 1).Equals(" "))//返回字符串正确的是以应答码(如220开头, 后面接一空格, 再接问候字符串){tryConnectTimes++;if (tryConnectTimes >= 100){IsConnected = false;throw new Exception($"Ftp多次尝试获取返回数据失败!\r\n请见检查DNS解析是否失败!当前尝试连接IP地址为{_IPAddress.ToString()}");}FetchReply();}ReplyCode = Int32.Parse(ReplyMessage.Substring(0, 3));}/// <summary>/// 发送命令并获取应答码和最后一行应答字符串/// </summary>/// <param name="strCommand">命令</param>private void SendCommand(String strCommand){try{Byte[] cmdBytes = Encoder.GetBytes((strCommand + "\r\n").ToCharArray());if (_SocketControl.Connected == true){_SocketControl.Send(cmdBytes, cmdBytes.Length, 0);FetchReply();}elsethrow new IOException("控制连接己关闭");}catch (Exception e){throw e;}}/// <summary>/// 判断文件列表的方式Window方式还是Unix方式/// </summary>/// <param name="recordArray">文件信息列表</param>private FtpFileListStyle GuessFileListStyle(string[] recordArray){foreach (string s in recordArray){if (s.Length > 10&& Regex.IsMatch(s.Substring(0, 10), "(-|d)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)")){return FtpFileListStyle.UnixStyle;}else if (s.Length > 8&& Regex.IsMatch(s.Substring(0, 8), "[0-9][0-9]-[0-9][0-9]-[0-9][0-9]")){return FtpFileListStyle.WindowsStyle;}}return FtpFileListStyle.Unknown;}/// <summary>/// 按照一定的规则进行字符串截取/// </summary>/// <param name="s">截取的字符串</param>/// <param name="c">查找的字符</param>/// <param name="startIndex">查找的位置</param>private string _cutSubstringFromStringWithTrim(ref string s, char c, int startIndex){int pos1 = s.IndexOf(c, startIndex);string retString = s.Substring(0, pos1);s = (s.Substring(pos1)).Trim();return retString;}/// <summary>/// 跟据名称得到代码/// </summary>/// <param name="code"></param>/// <returns></returns>private Encoding GetEncoder(string code){switch (code){case "UTF7":return Encoding.UTF7;case "UTF8":return Encoding.UTF8;case "UTF32":return Encoding.UTF32;case "ASCII":return Encoding.ASCII;case "UNICODE":return Encoding.Unicode;case "BIGENDIANUNICODE":return Encoding.BigEndianUnicode;case "DEFAULT":return Encoding.Default;default:return Encoding.Default;}}#endregion#region GetMD5                          获得MD5码/// <summary>/// 获得MD5码/// </summary>/// <param name="source">源字符串</param>/// <returns>MD5码</returns>public static string GetMD5(string source, int code = 2){byte[] sor = Encoding.UTF8.GetBytes(source);string mode = "x2";MD5 md5 = MD5.Create();byte[] result = md5.ComputeHash(sor);StringBuilder strbul = new StringBuilder();if (code > 2) mode = "x" + code.ToString();for (int i = 0; i < result.Length; i++){strbul.Append(result[i].ToString(mode));//加密结果"x2"结果为32位,"x3"结果为48位,"x4"结果为64位}return strbul.ToString();}#endregion}
}

本文标签: Demo