1

以下代码在 99% 的情况下都能正常工作。

但是,当我使用它复制包含大量文件的目录(一些单个文件也很大)时,它会挂起(没有抛出异常)并且任何进一步的 FTP 请求都会挂起,直到我回收代码的 IIS 7.5 应用程序池正在运行。(这用于基于 Web 的文件浏览器。)

它不会每次都挂在同一个文件上,实际上让我可以完全复制目录一次,但是如果我再试一次,它只会在复制一些文件和子目录后挂起。

我的问题是,任何人都可以看到代码的明显问题吗?是否存在未正确关闭的连接对象或其他什么?

顺便说一句,我尝试过(在 FtpCopyFile 方法中)刷新和关闭“uploadStream”对象以及实例化 FtpWebResponse 对象并随后关闭它。这些变化都没有任何区别。

如果代码没有明显的地方,有人可以推荐一种方法来追踪问题吗?由于没有抛出异常并且我在服务器日志中找不到任何内容(至少是我知道要查看的日志),所以我很茫然。

任何帮助将不胜感激!

饲料

public string FtpCopy(string fromUrl, string toUrl, bool isDirectory)
{
    string copyResult = "";

    // COPY ENTIRE DIRECTORY
    if (isDirectory) {
        // MAKE SURE TOP DIRECTORY IS CREATED
        if (!FtpDirectoryExists(toUrl)) { copyResult += FtpMakeDirectory(toUrl); }

        // ITERATE TROUGH ALL FILES AND FOLDERS AND COPY TO LIVE LOCATION
        Dictionary<string,Dictionary<string,string>> newItems = FtpRecursiveFileList(fromUrl);
        foreach (KeyValuePair<string,Dictionary<string,string>> item in newItems) {
            string currentFromUrl = item.Key;
            string currentToUrl = currentFromUrl.Replace(fromUrl, toUrl);
            if(item.Value["isdirectory"] == "true") { copyResult += FtpMakeDirectory(currentToUrl); }
            else { copyResult += FtpCopyFile(currentFromUrl, currentToUrl); }
        }

    // COPY SINGLE FILE
    } else { copyResult = FtpCopyFile(fromUrl, toUrl); }

    string returnString = "";
    if (copyResult == "") { returnString = "Success"; }
    else { returnString = "Error: " + copyResult; }

    return returnString;
}

private string FtpMakeDirectory(string url) {
    string returnString = "";

    // PARSE URL
    url = url.TrimEnd('/') + "/";
    string[] urlPath = Jbu.Util.UrlToStringArray(url, FTP_PATH_PREFIX);
    string currentPath = FTP_PATH_PREFIX + urlPath[0];

    // LOOP THROUGH EACH DIRECTORY LEVEL OF PATH
    for (int i = 1; i < (urlPath.Length - 1); i++) {
        currentPath = currentPath + "/" + urlPath[i];
        string[] currentFiles = FtpListDirectoryArray(currentPath);
        bool found = false;
        if (currentFiles != null) {
            // LOOK IN CURRENT DIRECTORY FOR DIRECTORY THAT HAS SAME NAME AS NEXT LOOP'S DIRECTORY
            for (int j = 0; j < currentFiles.Length; j++) {
               if (currentFiles[j] == urlPath[i + 1]) { found = true; }
            }
        }
        // IF NAME NOT FOUND, CREATE DIRECTORY
        if(!found) { returnString += FtpResponseAsString(CreateFtpRequest(currentPath + "/" + urlPath[i + 1], "makedirectory")); }
    }

    return returnString;
}

private string FtpCopyFile(string fromUrl, string toUrl)
{
    string returnString = "";
    try {
        // GET FILE TO BE COPIED
        FtpWebRequest ftpDownloadRequest = CreateFtpRequest(fromUrl, "downloadfile");
        System.Net.FtpWebResponse downloadResponse = (System.Net.FtpWebResponse)ftpDownloadRequest.GetResponse();
        Stream ftpDownloadStream = downloadResponse.GetResponseStream();
        byte[] fileByteArray = Jbu.Util.StreamToByteArray(ftpDownloadStream);
        ftpDownloadStream.Close();

        // CREATE DIRECTORY, IF NEEDED
        string containingDirectory = toUrl.Substring(0,toUrl.LastIndexOf('/'));
        if (!FtpDirectoryExists(containingDirectory)) { returnString += FtpMakeDirectory(containingDirectory); }

        // UPLOAD FILE TO NEW LOCATION
        FtpWebRequest ftpUploadRequest = CreateFtpRequest(toUrl, "uploadfile");
        ftpUploadRequest.ContentLength = fileByteArray.Length;
        using (Stream uploadStream = ftpUploadRequest.GetRequestStream()) { uploadStream.Write(fileByteArray, 0, fileByteArray.Length); }

    } catch (Exception ex) { returnString += "Error: " + ex.ToString(); }

    return returnString;
}

private FtpWebRequest CreateFtpRequest(string url, string method)
{
    // CREATE REQUEST OBJECT
    ServicePointManager.ServerCertificateValidationCallback = (Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) => (certificate.Subject.Contains("CN=" + Jbu.Constant.FTP_CERT_DOMAIN));
    FtpWebRequest ftpRequest = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));
    ftpRequest.EnableSsl = true;
    ftpRequest.Credentials = new NetworkCredential(Jbu.Constant.FTP_USER, Jbu.Constant.FTP_PASSWORD);
    ftpRequest.UseBinary = true;
    ftpRequest.KeepAlive = false;

    // SET METHOD
    switch(method) {
        case "listdirectory": ftpRequest.Method = WebRequestMethods.Ftp.ListDirectory; break;
        case "listdirectorydetails": ftpRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails; break;
        case "makedirectory": ftpRequest.Method = WebRequestMethods.Ftp.MakeDirectory; break;
        case "removedirectory": ftpRequest.Method = WebRequestMethods.Ftp.RemoveDirectory; break;
        case "downloadfile": ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile; break;
        case "uploadfile": ftpRequest.Method = WebRequestMethods.Ftp.UploadFile; break;
        case "deletefile": ftpRequest.Method = WebRequestMethods.Ftp.DeleteFile; break;
        case "getdatetimestamp": ftpRequest.Method = WebRequestMethods.Ftp.GetDateTimestamp; break;
        default: break;
    }

    return ftpRequest;
}

private bool FtpDirectoryExists(string url)
{
    bool dirExists = true;
    try {
        FtpWebRequest ftpRequest = CreateFtpRequest(url + "/", "listdirectory");
        FtpWebResponse ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
    } catch { dirExists = false; }
    return dirExists;
}

private Dictionary<string,Dictionary<string,string>> FtpRecursiveFileList(string url)
{
    Dictionary<string,Dictionary<string,string>> returnList = new Dictionary<string,Dictionary<string,string>>();

    List<string> files = new List<string>();
    Queue<string> folders = new Queue<string>();
    folders.Enqueue(url);

    while (folders.Count > 0) {
        string fld = folders.Dequeue();
        Dictionary<string,Dictionary<string,string>> newItems = FtpListDirectoryDetailsArray(fld);
        foreach(KeyValuePair<string,Dictionary<string,string>> item in newItems) {
            returnList.Add(fld + "/" + item.Key, item.Value);
            if(item.Value["isdirectory"] == "true") {
                folders.Enqueue(fld + "/" + item.Key);
            }
        }
    }
    return returnList;
}

private string[] FtpListDirectoryArray(string ftpPath)
{
    FtpWebRequest ftpRequest = CreateFtpRequest(ftpPath, "listdirectory");
    List<string> items = new List<string>();

    try {
        FtpWebResponse ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();

        Stream responseStream = ftpResponse.GetResponseStream();
        using (StreamReader responseReader = new StreamReader(responseStream)) {
            string line;
            while ((line = responseReader.ReadLine()) != null) { items.Add(line); }
        }

    } catch { return null; }

    string[] itemData = new string[items.Count];
    for (int i = 0; i < items.Count; i++) { itemData[i] = items[i]; }
    return itemData;
}

private Dictionary<string,Dictionary<string,string>> FtpListDirectoryDetailsArray(string ftpPath)
{
    Dictionary<string,Dictionary<string,string>> items = new Dictionary<string,Dictionary<string,string>>();

    FtpWebRequest ftpRequest = CreateFtpRequest(ftpPath, "listdirectorydetails");

    try {
        FtpWebResponse ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
        Stream responseStream = ftpResponse.GetResponseStream();
        using (StreamReader responseReader = new StreamReader(responseStream)) {
            string line;
            while ((line = responseReader.ReadLine()) != null) {
                Dictionary<string,string> item = new Dictionary<string,string>();
                line = System.Text.RegularExpressions.Regex.Replace(line, @"\s+", " "); // REMOVE EXTRA SPACES
                string[] itemDetails = line.Split(' ');
                item.Add("datetime", itemDetails[0] + " " + itemDetails[1]);

                // FOLDERS
                if (itemDetails[2] == "<DIR>") {
                    item.Add("isdirectory", "true");
                    item.Add("size", "-1");
                    item.Add("name", itemDetails[3]);

                } else {
                    item.Add("isdirectory", "false");
                    item.Add("size", itemDetails[2]);
                    item.Add("name", itemDetails[3]);
                }

                items.Add(itemDetails[3], item);
            }
        }

    // IF DIRECTORY DOES NOT EXIST, RETURN EMPTY DICT
    } catch {};

    return items;
}

private string FtpResponseAsString(FtpWebRequest ftpRequest)
{
    try {
        FtpWebResponse ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
        Stream responseStream = ftpResponse.GetResponseStream();
        StreamReader responseReader = new StreamReader(responseStream);
        return responseReader.ReadToEnd();

    } catch (Exception ex) { return "Error: " + ftpRequest.RequestUri + "\n\n" + ex.ToString(); }
}
4

1 回答 1

1

终于找到问题了!

“FtpWebRequest.KeepAlive = 假;” 确实有效,但清除关闭的连接需要一点时间。

所以发生的事情是我的最大连接数被击中。(显然也有一个 .NET 最大值,因为我在 IIS 中的 maxconnections 已经设置得非常高。)

将此行添加到 CreateFtpConnection 方法通过给 IIS 足够的时间来关闭旧连接并为更多连接腾出空间来解决问题: ftpRequest.ServicePoint.ConnectionLimit = 1000;

您的具体里程很可能会根据您的文件大小而有所不同,但希望这会对某人有所帮助。

作为记录,这里是 CreateFtpConnection 现在的样子:

private FtpWebRequest CreateFtpRequest(string url, string method)
{
    // CREATE REQUEST OBJECT
    ServicePointManager.ServerCertificateValidationCallback = (Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) => (certificate.Subject.Contains("CN=" + Jbu.Constant.FTP_CERT_DOMAIN));
    FtpWebRequest ftpRequest = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));
    ftpRequest.EnableSsl = true;
    ftpRequest.Credentials = new NetworkCredential(Jbu.Constant.FTP_USER, Jbu.Constant.FTP_PASSWORD);
    ftpRequest.UseBinary = true;
    ftpRequest.KeepAlive = false;
    ftpRequest.ServicePoint.ConnectionLimit = 1000;

    // SET METHOD
    switch(method) {
        case "listdirectory": ftpRequest.Method = WebRequestMethods.Ftp.ListDirectory; break;
        case "listdirectorydetails": ftpRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails; break;
        case "makedirectory": ftpRequest.Method = WebRequestMethods.Ftp.MakeDirectory; break;
        case "removedirectory": ftpRequest.Method = WebRequestMethods.Ftp.RemoveDirectory; break;
        case "downloadfile": ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile; break;
        case "uploadfile": ftpRequest.Method = WebRequestMethods.Ftp.UploadFile; break;
        case "deletefile": ftpRequest.Method = WebRequestMethods.Ftp.DeleteFile; break;
        case "getdatetimestamp": ftpRequest.Method = WebRequestMethods.Ftp.GetDateTimestamp; break;
        default: break;
    }

    return ftpRequest;
}
于 2012-09-04T20:58:48.530 回答