最近频繁操作FTP,但是也频繁遇到一个问题,那就是检测的超时问题,虽然有设定超时时间,但是由于其他原因,会造成这个超时时间根本无法随心定义,主要原因如MSDN描述如下:
Timeout 是进行后续同步请求时使用 GetResponse 方法等待响应以及 GetRequestStream 方法等待流所允许的毫秒数。 Timeout 适用于整个请求和响应,不单独对 GetRequestStream 与 GetResponse 方法调用响应。 如果资源在超时期限内未返回,请求将引发 WebException,并将 Status 属性设置为 WebExceptionStatus.Timeout。
Timeout 属性必须在 GetRequestStream 或 GetResponse 方法被调用之前设置。 在调用 GetRequestStream 或 GetResponse 方法之后更改 Timeout 属性不起任何作用
Timeout 属性对使用 BeginGetResponse 或 BeginGetRequestStream 方法生成的异步请求无效。
警告 在异步请求的情况下,客户端应用程序实现其自己的超时机制。 请参考 BeginGetResponse 方法中的示例。
若要指定在读写操作超时之前等待的时间量,请使用 ReadWriteTimeout 属性。
域名系统 (DNS) 查询可能需要 15 秒返回或超时。 如果您的请求包含要求解析的主机名,并且您将 Timeout 设置为小于 15 秒的值,则在 15 秒或更长时间之后才会引发 WebException 以指示您的请求超时。
原有的访问FTP的代码如下:
这种方案在项目中的体检非常不好,因为凡是超过5秒以上的请求(内网中,5秒数字为估算),基本成功访问的可能性很低了,但是FTP自己的超时机制还是会等待,有时甚至等待20-30秒,结果返回访问失败,这样对于用户来说很是头疼,就连我在测试时也是非常纠结,只能在超过5秒后选择直接关闭DEBUG,但是问题终究需要解决,查阅资料后,发现如下的方式:
ManualResetEvent 类
发送反馈
通知一个或多个正在等待的线程已发生事件。无法继承此类。
命名空间: System.Threading
程序集: mscorlib(在 mscorlib.dll 中)
ManualResetEvent 允许线程通过发信号互相通信。 通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。
当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。 此线程可被视为控制 ManualResetEvent。 调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。 当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。 并释放所有等待线程。
一旦它被终止, ManualResetEvent 将保持终止状态,直到它被手动重置。 即对 WaitOne 的调用将立即返回。
以下代码参考自:http://www.51testing.com/html/71/n-836371.html
FTP连通性检查方法:
Timeout 是进行后续同步请求时使用 GetResponse 方法等待响应以及 GetRequestStream 方法等待流所允许的毫秒数。 Timeout 适用于整个请求和响应,不单独对 GetRequestStream 与 GetResponse 方法调用响应。 如果资源在超时期限内未返回,请求将引发 WebException,并将 Status 属性设置为 WebExceptionStatus.Timeout。
Timeout 属性必须在 GetRequestStream 或 GetResponse 方法被调用之前设置。 在调用 GetRequestStream 或 GetResponse 方法之后更改 Timeout 属性不起任何作用
Timeout 属性对使用 BeginGetResponse 或 BeginGetRequestStream 方法生成的异步请求无效。
警告 在异步请求的情况下,客户端应用程序实现其自己的超时机制。 请参考 BeginGetResponse 方法中的示例。
若要指定在读写操作超时之前等待的时间量,请使用 ReadWriteTimeout 属性。
域名系统 (DNS) 查询可能需要 15 秒返回或超时。 如果您的请求包含要求解析的主机名,并且您将 Timeout 设置为小于 15 秒的值,则在 15 秒或更长时间之后才会引发 WebException 以指示您的请求超时。
原有的访问FTP的代码如下:
//创建FtpWebRequest对象
FtpWebRequest ftprequest = (FtpWebRequest)WebRequest.Create("ftp://" + DomainName);
ftprequest.Timeout = 5000;//设定5秒超时
ftprequest.ReadWriteTimeout = 5000;
//域名系统 (DNS) 查询可能需要 15 秒返回或超时。 如果您的请求包含需要解析的主机
//名,并将 Timeout 设置为少于 15 秒的值,则在引发 WebException 以指示您的请求
//超时之前,可能需要 15 秒或更多的时间。
这种方案在项目中的体检非常不好,因为凡是超过5秒以上的请求(内网中,5秒数字为估算),基本成功访问的可能性很低了,但是FTP自己的超时机制还是会等待,有时甚至等待20-30秒,结果返回访问失败,这样对于用户来说很是头疼,就连我在测试时也是非常纠结,只能在超过5秒后选择直接关闭DEBUG,但是问题终究需要解决,查阅资料后,发现如下的方式:
ManualResetEvent 类
发送反馈
通知一个或多个正在等待的线程已发生事件。无法继承此类。
命名空间: System.Threading
程序集: mscorlib(在 mscorlib.dll 中)
ManualResetEvent 允许线程通过发信号互相通信。 通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。
当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。 此线程可被视为控制 ManualResetEvent。 调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。 当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。 并释放所有等待线程。
一旦它被终止, ManualResetEvent 将保持终止状态,直到它被手动重置。 即对 WaitOne 的调用将立即返回。
以下代码参考自:http://www.51testing.com/html/71/n-836371.html
参考别人的代码,鉴于我在项目中需要使用的功能,修改封装如下:
/// <summary>
/// 函数运行中超时控制类
/// </summary>
public class FuncTimeout
{
/// <summary>
/// 信号量
/// </summary>
private ManualResetEvent manu = new ManualResetEvent(false);
/// <summary>
/// 是否接受到信号
/// </summary>
private bool isGetSignal;
/// <summary>
/// 设置超时时间
/// </summary>
private int timeout;
public delegate bool EventNeedRun(ITimeOutPara paras);
/// <summary>
/// 要调用的方法的一个委托
/// </summary>
private EventNeedRun FunctionNeedRun;
/// <summary>
/// 构造函数,传入超时的时间以及运行的方法
/// </summary>
/// <param name="_action"></param>
/// <param name="_timeout"></param>
public FuncTimeout(EventNeedRun _action, int _timeout)
{
FunctionNeedRun = _action;
timeout = _timeout;
}
/// <summary>
/// 回调函数
/// </summary>
/// <param name="ar"></param>
public void MyAsyncCallback(IAsyncResult ar)
{
//isGetSignal为false,表示异步方法其实已经超出设置的时间,此时不再需要执行回调方法。
if (isGetSignal == false)
{
Console.WriteLine("放弃执行回调函数");
Thread.CurrentThread.Abort();
}
else
{
Console.WriteLine("调用回调函数");
}
}
/// <summary>
/// 调用函数
/// </summary>
/// <param name="param1"></param>
public bool doAction(ITimeOutPara paras)
{
EventNeedRun WhatTodo = CombineActionAndManuset;
//通过BeginInvoke方法,在线程池上异步的执行方法。
var r = WhatTodo.BeginInvoke(paras, MyAsyncCallback, null);
//设置阻塞,如果上述的BeginInvoke方法在timeout之前运行完毕,则manu会收到信号。此时isGetSignal为true。
//如果timeout时间内,还未收到信号,即异步方法还未运行完毕,则isGetSignal为false。
isGetSignal = manu.WaitOne(timeout * 1000);
if (isGetSignal == true)
{
Console.WriteLine("函数运行完毕,收到设置信号,异步执行未超时");
return true;
}
else
{
Console.WriteLine("没有收到设置信号,异步执行超时");
return false;
}
}
/// <summary>
/// 把要传进来的方法,和 manu.Set()的方法合并到一个方法体。
/// action方法运行完毕后,设置信号量,以取消阻塞。
/// </summary>
/// <param name="num"></param>
private bool CombineActionAndManuset(ITimeOutPara paras)
{
bool pass = FunctionNeedRun(paras);
manu.Set();
return pass;
}
}
/// <summary>
/// 可以借助这个接口进行异步方法的参数传入和传出。
/// </summary>
public interface ITimeOutPara
{
/// <summary>
/// 参数集合,顺序不能乱
/// </summary>
List<object> Parameters { set; get; }
}
/// <summary>
/// 检查FTP连通性,参数
/// </summary>
public class CheckFtpPatermeters : ITimeOutPara
{
public List<object> Parameters { set; get; }
public CheckFtpPatermeters(string DomainName, string FtpUserName, string FtpUserPwd)
{
Parameters = new List<object>();
Parameters.Add(DomainName);
Parameters.Add(FtpUserName);
Parameters.Add(FtpUserPwd);
}
}
FTP连通性检查方法:
/// <summary>
/// 异步调用FTP验证
/// </summary>
/// <param name="DomainName"></param>
/// <param name="FtpUserName"></param>
/// <param name="FtpUserPwd"></param>
/// <param name="asyncEventTure"></param>
/// <param name="asyncEventFalse"></param>
public void CheckFtp(string DomainName, string FtpUserName, string FtpUserPwd
, EventHandler asyncEventTure, EventHandler asyncEventFalse, int timeout)
{
bool ResultValue = true;
CheckFtpPatermeters paras = new CheckFtpPatermeters(DomainName, FtpUserName, FtpUserPwd);
try
{
FuncTimeout time = new FuncTimeout(asyncCheckFtp, timeout);
ResultValue = time.doAction(paras);
}
catch
{
ResultValue = false;
}
if (paras.Parameters.Count ==4 && paras.Parameters[3].ToString().Equals("true"))
asyncEventTure(null, EventArgs.Empty);
else
asyncEventFalse(null, EventArgs.Empty);
}
class Program
{
static void Main(string[] args)
{
FtpHelper FtpInstance = new FtpHelper();
string FtpSer = "1.1.1.1";
string FtpUsr = "xxxx";
string FtpPass = "xxxx";
FtpInstance.CheckFtp(FtpSer, FtpUsr, FtpPass, returnTrue, returnFalse,8);
Console.ReadLine();
}
public static void returnTrue(object sender,EventArgs e)
{
Console.WriteLine("returnTrue");
}
public static void returnFalse(object sender, EventArgs e)
{
Console.WriteLine("returnFalse");
}
}