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;
}
至此問題便解決了。
眾裡尋他千百度,暮然回首,就在燈火闌珊處。