构建两个基础类型(为了保留目录下所有文件和目录,方便递归)
namespace LoadProject
{
enum PathType {
Unkown = -1,
File = 0,
Dir = 1
}
struct FileStruct
{
public PathType FileType { set; get; }
public string FilePath { get; set; }
public string FileName { get; set; }
}
}
基础类(FtpHelper)提供Ftp基本的操作
namespace LoadProject
{
class FtpHelper
{
///
/// 下载目录下所有的文件
///
/// ftp目录地址
/// 目录名称
/// 下载至本地目录地址对象
/// 是否有用户名
/// 用户名
/// 密码
public static void DownFtpDir(string ftpPath, string dirName, DirectoryInfo parentDir, bool withUser = false, string userName = null, string passWord = null)
{
try
{
var lsFileStruct = FtpHelper.GetFiles(ftpPath, withUser, userName, passWord);
var aparentDir = new DirectoryInfo(parentDir.FullName + @"\" + dirName);
if (!aparentDir.Exists)
aparentDir = parentDir.CreateSubdirectory(dirName);
foreach (var fileStruct in lsFileStruct)
{
if (fileStruct.FileType == PathType.File)
{
var filePath = aparentDir.FullName + @"\" + fileStruct.FileName;
DownFile(fileStruct.FilePath, filePath, withUser, userName, passWord);
}
else if (fileStruct.FileType == PathType.Dir)
DownFtpDir(fileStruct.FilePath, fileStruct.FileName, aparentDir, withUser, userName, passWord);
}
}
catch (Exception)
{
throw;
}
}
///
/// 获取当前目录下所有的文件和目录信息
///
///
///
///
///
///
public static List GetFiles(string ftpPath, bool withUser = false, string userName = null, string passWord = null)
{
try
{
FtpWebRequest ftpWebRequest = CreateFtpRequest(ftpPath,withUser,userName,passWord);
WebResponse response = ftpWebRequest.GetResponse();
var responseStream = response.GetResponseStream();
List strs = new List();
//由于偶然性读取网络流出错,借助MemoryStream处理
var memoryStream = new MemoryStream();
int RequestCount = 1;
while(true)
{
try
{
responseStream.CopyTo(memoryStream);
break;
}
catch(Exception) //由于请求一次出错(经测试如果请求过快或者目录下内容较小,会读取流出错),避免偶发性请求出错,可以请求多次(这里设置为5次)
{
RequestCount++;
if (RequestCount > 5)
throw;
responseStream = CreateFtpRequest(ftpPath, withUser, userName, passWord).GetResponse().GetResponseStream();
memoryStream.Dispose();
memoryStream = new MemoryStream();
continue;
}
}
memoryStream.Position = 0; //需要设置起点为0
using (var aReader = new StreamReader(memoryStream))
{
var line = aReader.ReadLine();
while(line != null)
{
if (line.Contains(""))
{
string msg = line.Substring(line.LastIndexOf("") + 5).Trim();
strs.Add(new FileStruct() { FileType = PathType.Dir, FilePath = ftpPath + "/" + msg, FileName = msg });
}
else
{
string msg = line.Substring(39);
strs.Add(new FileStruct() { FileType = PathType.File, FilePath = ftpPath + "/" + msg, FileName = msg });
}
line = aReader.ReadLine();
}
}
memoryStream.Close();
return strs;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("获取目录数据出错,原因:" + ex.Message + "\n目录:" + ftpPath);
throw new Exception("远程主机无响应,下载目录信息出错");
}
}
///
/// 从ftp下载文件至本地路径
///
///
///
/// 是否使用用户名
///
///
public static void DownFile(string ftpPath, string filePath, bool withUser = false, string userName = null, string passWord = null)
{
try
{
FtpWebRequest ftpWebRequest = CreateDownFtpRequest(ftpPath, withUser, userName, passWord);
WebResponse response = ftpWebRequest.GetResponse();
var respondStream = response.GetResponseStream();
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
byte[] buffer = new byte[1024];
int bytesRead = 0;
int requstCount = 1; //文件下载失败,重新请求次数
while (true)
{
try
{
bytesRead = respondStream.Read(buffer, 0, buffer.Length);
}
catch (Exception) //与请求目录同理
{
if (requstCount > 5)
throw;
requstCount++;
respondStream = FtpHelper.CreateDownFtpRequest(ftpPath, withUser, userName, passWord).GetResponse().GetResponseStream();
bytesRead = 0;
fileStream.Position = 0;
continue;
}
if (bytesRead == 0)
break;
fileStream.Write(buffer, 0, bytesRead);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.Write("从ftp服务器下载文件出错,文件名:" + ftpPath + "原因:" + ex.ToString());
throw new Exception("下载文件至本地出错");
}
}
///
/// 构建获取目录请求
///
///
///
///
///
///
public static FtpWebRequest CreateFtpRequest(string ftpPath, bool withUser = false, string userName = null, string passWord = null)
{
FtpWebRequest ftpWebRequest;
if (!Uri.TryCreate(ftpPath, UriKind.Absolute, out Uri ftpUri))
{
throw new Exception(ftpPath + "无效");
}
ftpWebRequest = (FtpWebRequest)FtpWebRequest.CreateDefault(ftpUri);
ftpWebRequest.Timeout = 60000;
ftpWebRequest.ReadWriteTimeout = 5000;
ftpWebRequest.EnableSsl = false;
ftpWebRequest.UseBinary = true;
ftpWebRequest.KeepAlive = false;
if (withUser)
ftpWebRequest.Credentials = new NetworkCredential(userName, passWord);
ftpWebRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
return ftpWebRequest;
}
///
/// 构建下载文件请求
///
///
///
///
///
///
public static FtpWebRequest CreateDownFtpRequest(string ftpPath, bool withUser = false, string userName = null, string passWord = null)
{
FtpWebRequest ftpWebRequest;
if (!Uri.TryCreate(ftpPath, UriKind.Absolute, out Uri ftpUri))
{
throw new Exception(ftpPath + "无效");
}
ftpWebRequest = (FtpWebRequest)FtpWebRequest.CreateDefault(ftpUri);
ftpWebRequest.EnableSsl = false;
System.Diagnostics.Debug.WriteLine(ftpPath);
ftpWebRequest.Timeout = 30000;
ftpWebRequest.ReadWriteTimeout = 10000;
ftpWebRequest.UseBinary = true;
ftpWebRequest.KeepAlive = false;
if (withUser)
ftpWebRequest.Credentials = new NetworkCredential(userName, passWord);
ftpWebRequest.Method = WebRequestMethods.Ftp.DownloadFile;
return ftpWebRequest;
}
}
}
基本的思路如下:
获取当前目录下的文件和目录信息,处理当前目录下文件和目录。如果是目录,则递归同样出路当前目录信息;如果是文件,则下载文件至目录保存。
值得一说的是,在实现的过程中,当请求很多的时候,在读取响应流会在偶然出现“ 请求一方没有响应“的错误。通过查询知道原因大概是当流为空的时候,读取流的对象会自动把流断开,参考https://stackoverflow.com/questions/48829032/streamreader-readline-throwing-disposed-exception-rather-than-returning-null。 因此,避免偶然读取流断开就报错的异常,多请求几次,确定是服务器问题才报错。处理方法就是有注释(try...catch{}
)那段。
类库使用的话,直接FtpHelper.DownFtpDir(参数)
, 即能调用下载目录下所有文件。
注意点:如果目录下存在相同的文件,则会覆盖。如果不用覆盖,可以自行处理。
本文提取至完整下载功能(包含ftp目录树加载,下载进度显示,下载速度显示)一部分。需要完整代码可以私聊
qq1754630968