标签:
在网络通信程序中,心跳检测是必不可少的,我们来看一下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); } }
在上面的方法中,我们可以看到在网络通信的服务器端和客户端中,由于服务器端设定的发送心跳消息的时间小于客户端发送心跳消息,所以发送心跳消息的工作主要由服务器端来完成。
如果服务器端超出常规时间没有发送心跳消息,客户端才会开始发送。
http://www.cnblogs.com/networkcomms
c#网络通信框架networkcomms内核解析之四 心跳检测
标签:
原文地址:http://www.cnblogs.com/networkcomms/p/4292092.html