networkComms.net2.3.1开源版本,基于gpl V3协议。因为不能公开3.x版本的源码,所以基于此版本进行学习。3.X版本进行了诸多改进和Bug修复,使用方法上两者相差不大。
namespace NetworkCommsDotNet { /// <summary> /// Connection对象 这个类是TcpConnection和 UDPConnnection连接类的父类 /// Connection由以下五个文件组成 大家注意到每个类前面都有个 partial关键字 /// ConnectionCreate.cs <1> /// ConnectionDelegatesHandlers.cs <2> /// ConnectionIncomingData.cs <3> /// ConnectionSendClose.cs <4> /// ConnectionStatic.cs <5> /// </summary> public abstract partial class Connection { static ManualResetEvent workedThreadSignal = new ManualResetEvent(false); static volatile bool shutdownWorkerThreads = false; static object staticConnectionLocker = new object(); static Thread connectionKeepAliveWorker; /// <summary> /// 一些默认的设置 /// </summary> static Connection() { ConnectionKeepAlivePollIntervalSecs = 30; MaxNumSendTimes = 100; MinNumSendsBeforeConnectionSpecificSendTimeout = 4; MinSendTimeoutMS = 2000; MinimumMSPerKBSendTimeout = 20; DefaultMSPerKBSendTimeout = 1000; NumberOfStDeviationsForWriteTimeout = 3; } /// <summary> /// 每KB数据发送超时的最小时间 默认为10. /// </summary> public static double MinimumMSPerKBSendTimeout { get; set; } /// <summary> /// 写入间隔的最大时间 默认100 /// </summary> public static int MaxNumSendTimes { get; set; } /// <summary> /// The minimum number of writes before the connection specific write timeouts will be used. Default is 3. /// </summary> public static int MinNumSendsBeforeConnectionSpecificSendTimeout { get; set; } /// <summary> /// The default milliseconds per KB write timeout before connection specific values become available. Default is 1000. See <see cref="MinNumSendsBeforeConnectionSpecificSendTimeout"/>. /// </summary> public static int DefaultMSPerKBSendTimeout { get; set; } /// <summary> /// The minimum timeout for any sized send in milliseconds. Prevents timeouts when sending less than 1KB. Default is 500. /// </summary> public static int MinSendTimeoutMS { get; set; } /// <summary> /// The interval between keep alive polls of all connections. Set to int.MaxValue to disable keep alive poll /// </summary> public static int ConnectionKeepAlivePollIntervalSecs { get; set; } /// <summary> /// The number of standard deviations from the mean to use for write timeouts. Default is 4.0. /// </summary> public static double NumberOfStDeviationsForWriteTimeout { get; set; } /// 如果 connectionKeepAliveWorker线程没有启动,则启动之 这个线程主要用来进行心跳检测 protected static void TriggerConnectionKeepAliveThread() { lock (staticConnectionLocker) { if (!shutdownWorkerThreads && (connectionKeepAliveWorker == null || connectionKeepAliveWorker.ThreadState == ThreadState.Stopped)) { connectionKeepAliveWorker = new Thread(ConnectionKeepAliveWorker); connectionKeepAliveWorker.Name = "ConnectionKeepAliveWorker"; connectionKeepAliveWorker.Start(); } } } /// 一个单独的静态工作者线程,用来保持连接是活动的 private static void ConnectionKeepAliveWorker() { if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Debug("Connection keep alive polling thread has started."); DateTime lastPollCheck = DateTime.Now; while (!shutdownWorkerThreads) { try { //We have a short sleep here so that we can exit the thread fairly quickly if we need too if (ConnectionKeepAlivePollIntervalSecs == int.MaxValue) workedThreadSignal.WaitOne(5000); else workedThreadSignal.WaitOne(100); //Check for shutdown here if (shutdownWorkerThreads) break; //Any connections which we have not seen in the last poll interval get tested using a null packet if (ConnectionKeepAlivePollIntervalSecs < int.MaxValue && (DateTime.Now - lastPollCheck).TotalSeconds > (double)ConnectionKeepAlivePollIntervalSecs) { AllConnectionsSendNullPacketKeepAlive(); lastPollCheck = DateTime.Now; } } catch (Exception ex) { NetworkComms.LogError(ex, "ConnectionKeepAlivePollError"); } } } /// <summary> /// Polls all existing connections based on ConnectionKeepAlivePollIntervalSecs value. Serverside connections are polled slightly earlier than client side to help reduce potential congestion. /// </summary> /// <param name="returnImmediately"></param> private static void AllConnectionsSendNullPacketKeepAlive(bool returnImmediately = false) { if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Starting AllConnectionsSendNullPacketKeepAlive"); //Loop through all connections and test the alive state List<Connection> allConnections = NetworkComms.GetExistingConnection(); int remainingConnectionCount = allConnections.Count; #if WINDOWS_PHONE QueueItemPriority nullSendPriority = QueueItemPriority.High; #else QueueItemPriority nullSendPriority = QueueItemPriority.AboveNormal; #endif ManualResetEvent allConnectionsComplete = new ManualResetEvent(false); for (int i = 0; i < allConnections.Count; i++) { //We don't send null packets to unconnected udp connections UDPConnection asUDP = allConnections[i] as UDPConnection; if (asUDP != null && asUDP.UDPOptions == UDPOptions.None) { if (Interlocked.Decrement(ref remainingConnectionCount) == 0) allConnectionsComplete.Set(); continue; } else { int innerIndex = i; NetworkComms.CommsThreadPool.EnqueueItem(nullSendPriority, new WaitCallback((obj) => { try { //If the connection is server side we poll preferentially if (allConnections[innerIndex] != null) { if (allConnections[innerIndex].ConnectionInfo.ServerSide) { //We check the last incoming traffic time //In scenarios where the client is sending us lots of data there is no need to poll if ((DateTime.Now - allConnections[innerIndex].ConnectionInfo.LastTrafficTime).TotalSeconds > ConnectionKeepAlivePollIntervalSecs) allConnections[innerIndex].SendNullPacket(); } else { //If we are client side we wait upto an additional 3 seconds to do the poll //This means the server will probably beat us if ((DateTime.Now - allConnections[innerIndex].ConnectionInfo.LastTrafficTime).TotalSeconds > ConnectionKeepAlivePollIntervalSecs + 1.0 + (NetworkComms.randomGen.NextDouble() * 2.0)) allConnections[innerIndex].SendNullPacket(); } } } catch (Exception) { } finally { if (Interlocked.Decrement(ref remainingConnectionCount) == 0) allConnectionsComplete.Set(); } }), null); } } //Max wait is 1 seconds per connection if (!returnImmediately && allConnections.Count > 0) { if (!allConnectionsComplete.WaitOne(allConnections.Count * 2500)) //This timeout should not really happen so we are going to log an error if it does NetworkComms.LogError(new TimeoutException("Timeout after " + allConnections.Count.ToString() + " seconds waiting for null packet sends to finish. " + remainingConnectionCount.ToString() + " connection waits remain. This error indicates very high send load or a possible send deadlock."), "NullPacketKeepAliveTimeoutError"); } } /// <summary> /// Shutdown any static connection components /// </summary> /// <param name="threadShutdownTimeoutMS"></param> internal static void ShutdownBase(int threadShutdownTimeoutMS = 1000) { try { shutdownWorkerThreads = true; if (connectionKeepAliveWorker != null && !connectionKeepAliveWorker.Join(threadShutdownTimeoutMS)) connectionKeepAliveWorker.Abort(); } catch (Exception ex) { NetworkComms.LogError(ex, "CommsShutdownError"); } finally { shutdownWorkerThreads = false; workedThreadSignal.Reset(); } } } }