学无止境,此路甚长。
最近做项目遇到一个问题,自己是做后台的,但涉及到网络的机会有些少,在这方面也是刚刚起步,在这里记录一下自己的成长,以供日后回望。
问题描述: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;
}
至此问题便解决了。
众里寻他千百度,暮然回首,就在灯火阑珊处。