由于研究Winform项目的版本自动检测更新,需要用到Ftp下载更新包文件。
特写出这个小Demo.
我的Ftp是本地用IIS搭建的FTP服务器,下载过一个1.8G的视频文件,亲测有效。
注意点:
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);
}
///
/// 主线程下载
///
///
///
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);
}
///
/// 异步下载
///
///
///
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);
}
///
/// 传入委托的方法,用于设置完成状态
///
///
///
private void DownLoadDataComplete(object sender, FtpClient.UpDownLoadCompletedArgs e)
{
Action action = ()=>
{
pgb.Value = 100;
lblProgressValue.Text = "下载完毕";
};
this.Invoke(action);
}
///
/// 传入委托的方法,用于设置下载进度状态
///
///
///
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
{
///
/// 文件结构信息
///
public struct FtpFileStruct
{
///
///
///
public string Flags;
///
/// 拥有者
///
public string Owner;
///
/// 文件组
///
public string Group;
///
/// 是否目录
///
public bool IsDirectory;
///
/// 创建时间
///
public DateTime CreateTime;
///
/// 文件名
///
public string Name;
///
/// 文件长度
///
public double Length;
}
///
/// 文件列表类型
///
public enum FtpFileListStyle
{
UnixStyle, //UNIX类型
WindowsStyle, //WINDOWS
Unknown //未知
}
///
/// 传输模式:二进制类型、ASCII类型
///
public enum FtpTransferType {
Binary, ASCII };
///
/// Ftp上传下载
///
public class FtpClient : IDisposable //Ftp上传下载
{
#region Private Fields
///
/// 用户名
///
private string _UserName;
///
/// 密码
///
private string _Password;
///
/// 初始创建Ftp类时候制定的根目录
///
private string _RootDirectory;
///
/// 工作目录
///
private string _WorkDirectory;
///
/// 服务器返回的应答信息(包含应答码)
///
private string ReplyMessage {
get; set; }
///
/// 服务器返回的应答码
///
private int ReplyCode {
get; set; }
///
/// 进行控制连接的socket
///
private Socket _SocketControl;
///
/// 传输模式
///
private FtpTransferType _TransferType = Framework.FtpTransferType.ASCII;
///
/// 接收和发送数据的缓冲区
///
private Byte[] _Buffer = new Byte[BUFFER_SIZE];
///
/// 当前尝试连接ID,用于报错
///
IPAddress _IPAddress;
int tryConnectTimes = 0;
#endregion
#region Property
///
/// FTP服务器IP地址
///
public string Host {
get; set; }
///
/// FTP服务器端口
///
public int Port {
get; set; }
///
/// 目录
///
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);
}
}
}
///
/// 在不知道当前路径的情况下提取并设置对应字段
///
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("\""));
}
///
/// 异步事件的间隔时间,毫秒
///
public int AsyncTriggerInterval {
get; set; } = 200;
///
/// 缓冲大小
///
public static int BUFFER_SIZE {
get; set; } = 1024 * 8;
///
/// 编码方式
///
public Encoding Encoder {
get; set; } = Encoding.Default;
///
/// 代理
///
public WebProxy WebProxy {
get; }
///
/// 是否登录
///
public bool IsConnected {
get; private set; }
///
/// 获得传输模式
///
/// 传输模式
public FtpTransferType TransferType
{
get {
return _TransferType; }
set
{
if (value == _TransferType) return; //如果TransferType已经一样则不重复发送
if (value == FtpTransferType.Binary)
SendCommand("TYPE I");//binary类型传输
else
SendCommand("TYPE A");//ASCII类型传输
if (ReplyCode != 200)
throw new IOException(ReplyMessage.Substring(4));
else
_TransferType = value;
}
}
#endregion
#region EventArgs
///
/// 上传下载进度
///
public class UpDownLoadProcessArgs : EventArgs
{
private long _TotalBytes;
private long _CurrentBytes;
public UpDownLoadProcessArgs(long currentBytes, long totalBytes)
{
_CurrentBytes = currentBytes;
_TotalBytes = totalBytes;
}
///
/// 完成进度百分比
///
public int ProgressPercentage
{
get {
return (int)(((double)_CurrentBytes / (double)_TotalBytes) * 100); }
}
///
/// 总字节数
///
public long TotalBytes
{
get {
return _TotalBytes; }
}
///
/// 当前字节数
///
public long CurrentBytes
{
get {
return _CurrentBytes; }
}
}
///
/// 上传下载完成事件
///
public class UpDownLoadCompletedArgs : EventArgs
{
private readonly long _TotalBytes;
private readonly long _CurrentBytes;
public UpDownLoadCompletedArgs(long curBytes, long totalBytes)
{
_CurrentBytes = curBytes;
_TotalBytes = totalBytes;
}
///
/// 进度百分比
///
public int ProgressPercentage
{
get {
return (int)(((double)_CurrentBytes / (double)_TotalBytes) * 100); }
}
///
/// 是否完成
///
public bool IsCompleted
{
get {
return _CurrentBytes >= _TotalBytes; }
}
///
/// 总字节数
///
public long TotalBytes
{
get {
return _TotalBytes; }
}
///
/// 当前字节数
///
public long CurrentBytes
{
get {
return _CurrentBytes; }
}
}
///
/// 返回服务器特性
///
///
public string Feature
{
get
{
SendCommand("FEAT");
return ReplyMessage;
}
}
#endregion
#region Deletegate
public 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
///
/// 异步下载进度发生改变触发的事件
///
public event FtpDownloadProgressChanged DownloadProgressChanged;
///
/// 异步下载文件完成之后触发的事件
///
public event FtpDownloadDataCompleted DownloadDataCompleted;
///
/// 异步上传进度发生改变触发的事件
///
public event FtpUploadProgressChanged UploadProgressChanged;
///
/// 异步上传文件完成之后触发的事件
///
public event FtpUploadFileCompleted UploadFileCompleted;
#endregion
#region Constructor
~FtpClient()
{
Dispose(false);
}
///
/// 缺省构造函数
///
public FtpClient()
{
Host = "";
WorkDirectory = "/";
_UserName = "";
_Password = "";
Port = 21;
IsConnected = false;
}
///
/// 构造函数
///
/// 服务器名称
/// 服务器目录
/// 用户名
/// 密码
/// 端口
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 Support
private 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
///
/// 获得一个FtpClient副本
///
///
public FtpClient Clone()
{
return new FtpClient(Host, WorkDirectory, _UserName, _Password, Port, Encoder.ToString());
}
///
/// 建立连接
///
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,IP4
case 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);
}
///
/// 关闭连接
///
public void CloseConnect()
{
if (_SocketControl != null && IsConnected)
{
SendCommand("QUIT");
}
CloseSocketConnect();
}
///
/// 得到文件信息
///
/// Ftp服务器接收到LIST命令后,返回的列表字符串。
///
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();
}
///
/// 从Unix格式中返回文件信息
///
/// 文件信息
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;
}
///
/// 从Windows格式中返回文件信息
///
/// 文件信息
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) == "" )
{
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;
}
///
/// 获得文件列表
///
/// 文件名的匹配字符串
///
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);
}
///
/// 判断文件是否存在
///
/// 文件全路径或文件名
///
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;
}
///
/// 判断目录是否存在
///
/// 文件路径名
///
public bool IsDirectoryExists(string directoryName)
{
FtpFileStruct[] ftpFileStructs= GetDirectorys();//获取子目录
foreach (var directoryStruct in ftpFileStructs)
{
if (directoryStruct.Name == directoryName) return true;
}
return false;
}
///
/// 获得文件大小
///
/// 文件名称
///
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;
}
///
/// 获得文件时间字符串
///
///
///
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;
}
///
/// 删除
///
/// 待删除文件名
public string DeleteFile(string strFileName)
{
if (!IsConnected) Connect();
SendCommand("DELE " + strFileName);
if (ReplyCode != 250)
return ReplyMessage;
return string.Empty;
}
///
/// 重命名(如果新文件名与已有文件重名,将覆盖已有文件,需服务器支持)
///
/// 旧文件名
/// 新文件名
/// 是否覆盖己有文件
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_上传和下载
///
/// 下载文件
///
/// 远程文件名
/// 本地文件完整路径
/// 如果加载字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。
public long DownLoadFile(string remoteFileName, string LocalFilePath)
{
string strFolder = new FileInfo(LocalFilePath).DirectoryName;
return DownLoadFile(remoteFileName, strFolder, Path.GetFileName(LocalFilePath));
}
///
/// 异步下载
///
/// 远程文件名
/// 本地文件完整路径
/// CancellationToken
///
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);
}
///
/// 下载文件
///
/// 远程文件名
/// 文件夹完整路径
/// 本地文件名
/// 如果加载字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。
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;
}
///
/// 异步下载
///
/// 远程文件名
/// 文件夹完整路径
/// 本地文件完整路径
/// CancellationToken
/// 如果加载字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。
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;
}
///
/// 下载到文件到字符数组
///
///
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();
}
///
/// 异步下载文件
///
/// 远程文件名
/// 本地目录
/// 本地文件名
///
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;
}
///
/// 上传一个文件在当前工作目录下
///
/// 文件名
/// 本地文件完整路径
/// 如果上传字节不等于总字节长度,则返回0表示失败;成功则返回总字节长度,表示成功。
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;
}
///
/// 异步上传
///
/// 本地文件完整路径
/// 上传为文件完整路径
/// CancellationToken
/// 返回当前字节长度
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;
}
///
/// 上传文件扩展,strFileName可以为文件名称或文件全路径名称
/// 该函数是使用安全的,上传失败不会覆盖原来的文件
///
/// 文件名称或文件全路径名称
/// 本地文件全路径名称
/// 取消标记
/// 返回当前字节长度
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;
}
///
/// 上传内存文件到服务器
///
///
///
///
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_目录操作
///
/// 得到目录列表
///
///
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();
}
///
/// 创建目录
///
/// 目录名
/// 如果有错误返回错误文本,反之返回空字符串
public string MakeDirectory(string directoryPath)
{
if (!IsConnected) Connect();
SendCommand("MKD " + directoryPath);
if (!(ReplyCode == 257 || ReplyCode == 250))
return ReplyMessage;
return string.Empty;
}
///
/// 删除目录
///
/// 目录名
/// 如果有错误返回错误文本,反之返回空字符串
public string RemoveDirectory(string strDirName)
{
if (!IsConnected) Connect();
SendCommand("RMD " + strDirName);
if (ReplyCode != 250)
return ReplyMessage;
return string.Empty;
}
///
/// 改变目录
///
/// 新的工作目录名
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));
}
///
/// 改变工作路径为父文件夹
///
public void ChangeWorkDirectoryToParent()
{
if (!IsConnected) Connect();
SendCommand("CDUP");
if (ReplyCode != 250)
throw new IOException(ReplyMessage.Substring(4));
SetCurrentWorkDirectory(); //设置比骄傲村当前工作路径的对应字段
}
///
/// 建立进行数据连接的socket
///
/// 数据连接socket
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;
}
///
/// 关闭socket连接(用于登录以前)
///
private void CloseSocketConnect()
{
if (_SocketControl != null)
{
_SocketControl.Close();
_SocketControl = null;
}
IsConnected = false;
}
///
/// 读取Socket返回的所有字符串,将一行应答字符串记录在strReply和strMsg,应答码记录在iReplyCode
///
/// 包含应答码的字符串行
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],因为只有最后一行字符串应答码与信息之间有空格
else
ReplyMessage = 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));
}
///
/// 发送命令并获取应答码和最后一行应答字符串
///
/// 命令
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();
}
else
throw new IOException("控制连接己关闭");
}
catch (Exception e)
{
throw e;
}
}
///
/// 判断文件列表的方式Window方式还是Unix方式
///
/// 文件信息列表
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;
}
///
/// 按照一定的规则进行字符串截取
///
/// 截取的字符串
/// 查找的字符
/// 查找的位置
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;
}
///
/// 跟据名称得到代码
///
///
///
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码
///
/// 获得MD5码
///
/// 源字符串
/// MD5码
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
}
}