1. 程式人生 > >C#Ftp類之FtpWebRoesponse意外報停不響應 [System.Net.Sockets]

C#Ftp類之FtpWebRoesponse意外報停不響應 [System.Net.Sockets]

學無止境,此路甚長。

最近做專案遇到一個問題,自己是做後臺的,但涉及到網路的機會有些少,在這方面也是剛剛起步,在這裡記錄一下自己的成長,以供日後回望。

問題描述:FtpWebRoesponse接收伺服器反饋的時候,一直不相應,其實是因為FtpWebRoesponse拿不到訊息,一直苦苦等待,直到超時(TimeOut)之後才恢復正常。

觸發原因:在呼叫自定義的方法時,多次例項化了自定義的FtpServer類,導致程式中存在多個NetworkCredentia網路憑證,伺服器卻只會驗證通過第一個。

解決方法:採用單例模式

為了滿足同步伺服器的需求,暫時使用微軟封裝的的System.Net.Sockets名稱空間下的的Ftp類,編寫一個程式模組去請求伺服器(測試的時候用的是視窗自帶IIS管理器釋出的的Ftp服務)在模組中有兩個函式是這麼寫的(程式碼什麼的可以簡略的看):

      

   public classFtpServer

{

       private NetworkCredential networkCredential;

        public string FtpUriString { get; set; }//IP,UserName,PassWord都是屬性,可以自己賦值,做測試的時候用的是Ftp的網址,使用者,密碼

        

/// </summary>

        /// 請求

        /// </summary>

        /// <param name="uri"></param>

        /// <param name="requestMethod"></param>

        ///

<returns></returns>

        public FtpWebRequest CreateFtpWebRequest(string uri, string requestMethod)

        {

            FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(uri);

            networkCredential = new NetworkCredential(this.UserName, this.PassWord);

            request.Credentials = networkCredential;

            request.KeepAlive = true;

            request.UseBinary = true;

            request.Method = requestMethod;

            return request;

        }

        /// <summary>

        /// 反饋

        /// </summary>

        /// <param name="request"></param>

        /// <returns></returns>

        public FtpWebResponse GetFtpWebResponse(FtpWebRequest request)

        {

            FtpWebResponse response = null;

            try

            {

                response = (FtpWebResponse)request.GetResponse();

                return response;

            }

            catch (WebException)

            {

                return null;

            }

            finally

            { }

        }

 

        /// <summary>

        /// 嘗試登陸

        /// </summary>

        /// <param name="loginInfo"></param>

        /// <returns></returns>

        public bool FtpServerConnet()

        {

            this.FtpUriString = "ftp://" + this.FtpServerIP;

            FtpWebRequest request = CreateFtpWebRequest(FtpUriString, WebRequestMethods.Ftp.ListDirectoryDetails);

            FtpWebResponse response = GetFtpWebResponse(request);

            if (response == null) return false;

            return true;

        }

/// <summary>

        /// 獲得伺服器目標路徑詳細列表

        /// </summary>

        /// <param name="ftpDirectoryPath"></param>

        /// <returns></returns>

        public string[] GetFtpListDirectoryDetails(string ftpDirectoryPath)

        {

            string uri = GetUriString(ftpDirectoryPath);

            StringBuilder strBuilder = new StringBuilder();

            try

            {

                FtpWebRequest request = CreateFtpWebRequest(uri, WebRequestMethods.Ftp.ListDirectoryDetails);

                if (request == null) return null;

                FtpWebResponse response = GetFtpWebResponse(request);

                Stream stream = response.GetResponseStream();

                StreamReader reader = new StreamReader(stream, Encoding.UTF8);

                string line = reader.ReadLine();

                while (line != null)

                {

                    strBuilder.Append(line);

                    strBuilder.Append("@");

                    line = reader.ReadLine();

                }

                reader.Close();

                response.Close();

                string path = strBuilder.ToString();

                path.Remove(path.LastIndexOf("@"), 1);

                return path.Split('@');

            }

            catch

            {

                return null;

            }

        }

}

      在呼叫的時候是這樣呼叫的:

在A模組中:

FtpServer ftpClient = new FtpServer();

if (ftpClient .FtpServerConnet())

{

         ftpClient .GetFtpListDirectoryDetails(ftpPath);

}

在B模組中:

FtpServer ftpClient = new FtpServer();

if (ftpClient .FtpServerConnet())

{

         ftpClient .GetFtpListDirectoryDetails(ftpPath);

}

問題來了

假如在A模組執行過後,B模組執行以上程式碼會卡在GetFtpWebResponse方法裡(文字加粗的地方),一直不響應,直到TimeOut(請求超時)

     public FtpWebResponse GetFtpWebResponse(FtpWebRequest request)

        {

            FtpWebResponse response = null;

            try

            {

                response = (FtpWebResponse)request.GetResponse();

                return response;

            }

            catch (WebException)

            {

                return null;

            }

為什麼?

因為A和B中都例項化了FtpServer類,在這個類裡面有個私有變數

private NetworkCredential networkCredential;

這個東西就是網路憑證,裡面儲存的是你登入Ftp服務必須的驗證資訊。

在例項化FtpServer時,會將這個宣告一個該變數,例項化一次就擁有一個,這導致的問題是,同一個憑證在程式中出現多次,都以自己例項化的憑證訪問Ftp伺服器,但他們都是一樣的,那Ftp伺服器相信誰呢?第一個以此憑證登陸的人,即第一個請求Ftp伺服器的模組,之後的都是無法驗證。這就導致了在FtpWebResponse在接收伺服器迴應時,一直得不到反饋,得不到反饋就繼續等待,除錯的時候就像程式卡死,但是你還能做其他操作,其實不是卡死(這是單執行緒的弊端),一般在伺服器互動中,下載上傳都採用多執行緒的方式,以便繼續做其他的事情。

解決方法

在知道問題後我第一個想到的就是C#設計模式中的單例模式(事實證明,學了終會有用到的時候)

1、定義私有變數

2、建構函式私有化

3、獲得唯一單例的方法

        private static FtpServer _FtpServer = new FtpServer();

       private FtpServer()

        {

        }

        public static FtpServer GetInstance()

        {

            return _FtpServer;

        }

至此問題便解決了。

眾裡尋他千百度,暮然回首,就在燈火闌珊處。