在重构Ping32局域网监控软件的屏幕墙功能时,我们的目标是业务逻辑和界面更新分离。屏幕墙的一个特点是短时间内要和多个客户端建立连接传输数据,管理多个连接就不可避免要涉及到多线程问题,目的是要高效的传输屏幕数据。涉及到网络多线程编程肯定不会为每一个终端建立一个线程来收发数据,而是使用线程池。
考虑到应用场合,我们发起连接的时候要有一个连接超时机制,本来在我们的C++代码里,已经有这样封装好的函数,C#虽然也有Connect超时机制的代码,但是代码毕竟不是我写的,所以还是需要审查一遍。不看不知道,一看就发现了问题,大体TcpClient的连接超时代码如下:
public TcpClient Connect() { // kick off the thread that tries to connect connected = false; exception = null; Thread thread = new Thread(new ThreadStart(BeginConnect)); thread.IsBackground = true; // 作为后台线程处理 // 不会占用机器太长的时间 thread.Start(); // 等待如下的时间 thread.Join(_timeout_milliseconds); if (connected == true) { // 如果成功就返回TcpClient对象 thread.Abort(); return connection; } if (exception != null) { // 如果失败就抛出错误 thread.Abort(); throw exception; } else { // 同样地抛出错误 thread.Abort(); string message = string.Format("TcpClient connection to {0}:{1} timed out", _hostname, _port); throw new TimeoutException(message); } }
这里建立连接的时候竟然创建一个线程,然后利用线程超时就Terminate来实现超时机制,不得不可谓愚蠢之极,毕竟网上的代码都不负责任。
这样影响稳定性、影响性能的代码肯定不能再Ping32中出现的。所以重新编写了C# TCP连接超时的代码。
namespace NSecsoft.NSec.Core.Util { class ConnectState { public readonly ManualResetEvent mrEve = new ManualResetEvent(false); public TcpClient tcpClient; } public static class ConnectEx { public static bool ConnectWithTimeout(TcpClient client, IPAddress address, int port, int timeout) { bool ret = false; try { ConnectState state = new ConnectState() { tcpClient = client }; IAsyncResult ar = client.BeginConnect(address, port, new AsyncCallback(ConnectCallback), state); state.mrEve.WaitOne(timeout); if (client.Connected) { ret = true; } } catch (Exception) { // ignored } return ret; } private static void ConnectCallback(IAsyncResult ar) { ConnectState state = ar.AsyncState as ConnectState;</blockquote> state.mrEve.Set(); state.mrEve.Dispose(); state.tcpClient.EndConnect(ar); } } }