Net的System.Net.Sockets.TcpClient和System.Net.Sockets.Socket都没有直接为Connect/BeginConnect提供超时控制机制。因此,当服务器未处于监听状态,或者发生网络故障时,客户端连接请求会被迫等待很长一段时间,直到抛出异常。默认的等待时间长达20~30s。.Net Socket库的SocketOptionName命名空间里提供了发送和接收的超时时间,但首先那是基于同步的方法,第二也没有超时连接时间的设置,所以嘛,自己写咯
首先做些准备工作
1 private TcpClient client; 2 private NetworkStream netStream;
把这俩声明为类变量,方便统一控制
private EventWaitHandle eventHandle;
这个变量是为控制回调方法无法解除绑定的问题而做的处理…具体看后面,谁要知道咋解挂回调方法告一声啊=.=
然后就是 Connect 方法
/// <summary> /// 连接 /// </summary> /// <param name=”ip”>要连接的IP</param> public bool Connect(string ip) { eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset); try { isProcessing = true; eventHandle.Reset(); // 开始异步连接 client = new TcpClient(AddressFamily.InterNetwork); client.BeginConnect(IPAddress.Parse(ip), 8001, RequestCallback, client); // 判断超时,若超时,进行相应处理 if (!eventHandle.WaitOne(1500, false)) { client.Client.Close(); // 彻底关闭TCP连接,下同 isProcessing = false; Console.WriteLine(ip + ” 连接超时”); // test,所有非异常输出语句均为测试用 return false; } else { // 判断client.Connected是为避免本机无连接直接返回 if (client.Connected) { Console.WriteLine(ip + ” 连接成功”); netStream = client.GetStream(); return true; } return false; } } catch (Exception ex) { Console.WriteLine(“Connect() “ + ex.Message); return false; } }
结合回调方法一起看
/// <summary> /// 连接回调方法 /// </summary> private void RequestCallback(IAsyncResult ar) { if (isProcessing) { eventHandle.Set(); try { client = (TcpClient)ar.AsyncState; } catch (Exception ex) { Console.WriteLine(“RequestCallback() “ + ex.Message); } } }
可以看出 eventHandle.ReSet() eventHandle.Set() 是这个机制的实现基础, EventWaitHandle 表示一个线程同步事件.EventWaitHandle 类允许线程通过发信号互相通信. 通常,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用 Set 方法,以释放一个或多个被阻止的线程.而手动设置MSDN是这么解释的:手动重置事件类似于入口.当事件不处于终止状态时,在该事件上等待的线程将阻止。.当事件处于终止状态时,所有等待的线程都被释放,而事件一直保持终止状态(即后面的等待不阻止),直到它的 Reset 方法被调用.如果一个线程必须完成一项活动后,其他线程才能继续,则手动重置事件很有用. 详细:http://msdn.microsoft.com/zh-cn/library/system.threading.eventwaithandle.aspx
在这个示例里,主线程调用 eventHandle.ReSet() 让其他需要等待的线程阻塞,然后 WaitOne(Int32, Boolean) 阻塞主线程等待信号,在回调函数里调用 eventHandle.Set() 使等待的线程运行,以达到判超时的目的
这里有两个需要特别注意的地方:
// 判断超时,若超时,进行相应处理 if (!eventHandle.WaitOne(1500, false)) { client.Client.Close(); // 彻底关闭TCP连接,下同 isProcessing = false; Console.WriteLine(ip + ” 连接超时”); // test,所有非异常输出语句均为测试用 return false; } else { // 判断client.Connected是为避免本机无连接直接返回 if (client.Connected) { Console.WriteLine(ip + ” 连接成功”); netStream = client.GetStream(); return true; } return false; }
client.Client.Close() 这里要调用 client 对象的 Client 属性,是因为 本身 client.Close() 并没有关闭基础TCP连接,所以要设置基础Socket.
还有就是回调函数的返回问题,其实这里挺奇怪的,调项目时有次不在办公室,在自己那恰好没网,结果一运行回调函数还是立马返回了,所以要通过判断 client.Connected 属性来确保判超时的正确.