ftpWebRequest 在大量上传时挂起

本文关键字:挂起 ftpWebRequest | 更新日期: 2024-11-05 21:51:22

下面的代码在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(); }
}

ftpWebRequest 在大量上传时挂起

终于找到了问题所在!

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

所以发生的事情是我的最大连接数被击中了。(显然还有一个 .NET max,因为我在 IIS 中的最大连接数已经设置得很高。

将此行添加到 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;
}