標籤:
在網路通訊程式中,心跳檢測是必不可少的,我們來看一下networkcomms中是如何?的
以networkcomms2.3.1為例:
在伺服器端,會有一個線程專門用來發送心跳訊息
代碼如下:
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"); } }
protected override void SendNullPacket() { try { //Only once the connection has been established do we send null packets if (ConnectionInfo.ConnectionState == ConnectionState.Established) { //Multiple threads may try to send packets at the same time so we need this lock to prevent a thread cross talk lock (sendLocker) { if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Sending null packet to " + ConnectionInfo); //Send a single 0 byte double maxSendTimePerKB = double.MaxValue; if (!NetworkComms.DisableConnectionSendTimeouts) { if (SendTimesMSPerKBCache.Count > MinNumSendsBeforeConnectionSpecificSendTimeout) maxSendTimePerKB = Math.Max(MinimumMSPerKBSendTimeout, SendTimesMSPerKBCache.CalculateMean() + NumberOfStDeviationsForWriteTimeout * SendTimesMSPerKBCache.CalculateStdDeviation()); else maxSendTimePerKB = DefaultMSPerKBSendTimeout; }#if WINDOWS_PHONE var stream = socket.OutputStream.AsStreamForWrite(); StreamWriteWithTimeout.Write(new byte[] { 0 }, 1, stream, 1, maxSendTimePerKB, MinSendTimeoutMS); stream.Flush();#else StreamWriteWithTimeout.Write(new byte[] { 0 }, 1, tcpClientNetworkStream, 1, maxSendTimePerKB, MinSendTimeoutMS);#endif //Update the traffic time after we have written to netStream ConnectionInfo.UpdateLastTrafficTime(); } } //If the connection is shutdown we should call close if (ConnectionInfo.ConnectionState == ConnectionState.Shutdown) CloseConnection(false, -8); } catch (Exception) { CloseConnection(true, 19); } }SendNullPacket()
在上面的方法中,我們可以看到在網路通訊的伺服器端和用戶端中,由於伺服器端設定的發送心跳訊息的時間小於用戶端發送心跳訊息,所以發送心跳訊息的工作主要由伺服器端來完成。
如果伺服器端超出常規時間沒有發送心跳訊息,用戶端才會開始發送。
http://www.cnblogs.com/networkcomms
c#網路通訊架構networkcomms核心解析之四 心跳檢測