/// <summary> /// Shutdown everything TCP related /// </summary> internal static void Shutdown(int threadShutdownTimeoutMS = 1000) { #if WINDOWS_PHONE try { CloseAndRemoveAllLocalConnectionListeners(); } catch (Exception ex) { NetworkComms.LogError(ex, "TCPCommsShutdownError"); } #else try { shutdownIncomingConnectionWorkerThread = true; CloseAndRemoveAllLocalConnectionListeners(); //If the worker thread does not shutdown in the required time we kill it if (newIncomingConnectionWorker != null && !newIncomingConnectionWorker.Join(threadShutdownTimeoutMS)) { newIncomingConnectionWorker.Abort(); } } catch (Exception ex) { NetworkComms.LogError(ex, "TCPCommsShutdownError"); } finally { shutdownIncomingConnectionWorkerThread = false; } #endif }
/// <summary> /// Prevent any additional threads from starting and return once all existing workers have completed. /// </summary> /// <param name="threadShutdownTimeoutMS"></param> public void EndShutdown(int threadShutdownTimeoutMS = 1000) { List <Thread> allWorkerThreads = new List <Thread>(); lock (SyncRoot) { foreach (var thread in threadDict) { workerInfoDict[thread.Key].ThreadSignal.Set(); allWorkerThreads.Add(thread.Value); } } //Wait for all threads to finish foreach (Thread thread in allWorkerThreads) { try { if (!thread.Join(threadShutdownTimeoutMS)) { thread.Abort(); } } catch (Exception ex) { NetworkComms.LogError(ex, "ManagedThreadPoolShutdownError"); } } lock (SyncRoot) { jobQueue.Clear(); shutdown = false; } }
/// <summary> /// Shutdown everything UDP related /// </summary> internal static void Shutdown() { //Close any established udp listeners try { CloseAndRemoveAllLocalConnectionListeners(); } catch (Exception ex) { NetworkComms.LogError(ex, "UDPCommsShutdownError"); } //reset the rouge senders to null so that it is recreated if we restart anything udpRogueSenders = new Dictionary <AddressFamily, UDPConnection>(); }
/// <summary> /// A single static worker thread which keeps connections alive /// </summary> 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> /// 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(); } }
private static void newListenerInstance_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { try { var newConnectionInfo = new ConnectionInfo(true, ConnectionType.TCP, new IPEndPoint(IPAddress.Parse(args.Socket.Information.RemoteAddress.DisplayName.ToString()), int.Parse(args.Socket.Information.RemotePort))); TCPConnection.GetConnection(newConnectionInfo, NetworkComms.DefaultSendReceiveOptions, args.Socket, true); } catch (ConfirmationTimeoutException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (CommunicationException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (ConnectionSetupException) { //If we are the server end and we did not pick the incoming connection up then tooo bad! } catch (SocketException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (Exception ex) { //For some odd reason SocketExceptions don't always get caught above, so another check if (ex.GetBaseException().GetType() != typeof(SocketException)) { //Can we catch the socketException by looking at the string error text? if (ex.ToString().StartsWith("System.Net.Sockets.SocketException")) { NetworkComms.LogError(ex, "ConnectionSetupError_SE"); } else { NetworkComms.LogError(ex, "ConnectionSetupError"); } } } }
/// <summary> /// Incoming data listen sync method /// </summary> private void IncomingUDPPacketWorker() { try { while (true) { if (ConnectionInfo.ConnectionState == ConnectionState.Shutdown) { break; } IPEndPoint endPoint = new IPEndPoint(IPAddress.None, 0); byte[] receivedBytes = udpClientThreadSafe.Receive(ref endPoint); if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Received " + receivedBytes.Length.ToString() + " bytes via UDP from " + endPoint.Address + ":" + endPoint.Port.ToString() + "."); } if (isIsolatedUDPConnection) { //This connection was created for a specific remoteEndPoint so we can handle the data internally //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (packetBuilder.Locker) { packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (packetBuilder.TotalBytesCached > 0) { IncomingPacketHandleHandOff(packetBuilder); } } } else { //Look for an existing connection, if one does not exist we will create it //This ensures that all further processing knows about the correct endPoint UDPConnection connection = GetConnection(new ConnectionInfo(true, ConnectionType.UDP, endPoint, udpClientThreadSafe.LocalEndPoint), ConnectionDefaultSendReceiveOptions, UDPOptions, false, this); //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (connection.packetBuilder.Locker) { //We pass the data off to the specific connection connection.packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (connection.packetBuilder.TotalBytesCached > 0) { connection.IncomingPacketHandleHandOff(connection.packetBuilder); } if (connection.packetBuilder.TotalPartialPacketCount > 0) { connection.packetBuilder.ClearNTopBytes(connection.packetBuilder.TotalBytesCached); //We cant close the connection here because it may be one of the shared udp listeners. For now we will just log. NetworkComms.LogError(new Exception("Packet builder had remaining packets after a call to IncomingPacketHandleHandOff. Until sequenced packets are implemented this indicates a possible error."), "UDPConnectionError"); } } } } } //On any error here we close the connection catch (NullReferenceException) { CloseConnection(true, 20); } catch (ArgumentNullException) { CloseConnection(true, 37); } catch (IOException) { CloseConnection(true, 21); } catch (ObjectDisposedException) { CloseConnection(true, 22); } catch (SocketException) { //Recieve may throw a SocketException ErrorCode=10054 after attempting to send a datagram to an unreachable target. //We will try to get around this by ignoring the ICMP packet causing these problems on client creation CloseConnection(true, 23); } catch (InvalidOperationException) { CloseConnection(true, 24); } catch (Exception ex) { NetworkComms.LogError(ex, "Error_UDPConnectionIncomingPacketHandler"); CloseConnection(true, 41); } //Clear the listen thread object because the thread is about to end incomingDataListenThread = null; if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Incoming data listen thread ending for " + ConnectionInfo); } }
void socket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args) { try { var stream = args.GetDataStream().AsStreamForRead(); var dataLength = args.GetDataReader().UnconsumedBufferLength; byte[] receivedBytes = new byte[dataLength]; using (MemoryStream mem = new MemoryStream(receivedBytes)) stream.CopyTo(mem); stream = null; if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Received " + receivedBytes.Length + " bytes via UDP from " + args.RemoteAddress + ":" + args.RemotePort + "."); } if (isIsolatedUDPConnection) { //This connection was created for a specific remoteEndPoint so we can handle the data internally //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (packetBuilder.Locker) { packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (packetBuilder.TotalBytesCached > 0) { IncomingPacketHandleHandOff(packetBuilder); } } } else { var remoteEndPoint = new IPEndPoint(IPAddress.Parse(args.RemoteAddress.DisplayName.ToString()), int.Parse(args.RemotePort)); var localEndPoint = new IPEndPoint(IPAddress.Parse(sender.Information.LocalAddress.DisplayName.ToString()), int.Parse(sender.Information.LocalPort)); //Look for an existing connection, if one does not exist we will create it //This ensures that all further processing knows about the correct endPoint UDPConnection connection = GetConnection(new ConnectionInfo(true, ConnectionType.UDP, remoteEndPoint, localEndPoint), ConnectionDefaultSendReceiveOptions, UDPOptions, false, this); //We pass the data off to the specific connection //Lock on the packetbuilder locker as we may recieve udp packets in parallel from this host lock (connection.packetBuilder.Locker) { connection.packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (connection.packetBuilder.TotalBytesCached > 0) { connection.IncomingPacketHandleHandOff(connection.packetBuilder); } if (connection.packetBuilder.TotalPartialPacketCount > 0) { connection.packetBuilder.ClearNTopBytes(connection.packetBuilder.TotalBytesCached); //We cant close the connection here because it may be one of the shared udp listeners. For now we will just log. NetworkComms.LogError(new Exception("Packet builder had remaining packets after a call to IncomingPacketHandleHandOff. Until sequenced packets are implemented this indicates a possible error."), "UDPConnectionError"); } } } } //On any error here we close the connection catch (NullReferenceException) { CloseConnection(true, 25); } catch (ArgumentNullException) { CloseConnection(true, 38); } catch (IOException) { CloseConnection(true, 26); } catch (ObjectDisposedException) { CloseConnection(true, 27); } catch (SocketException) { //Recieve may throw a SocketException ErrorCode=10054 after attempting to send a datagram to an unreachable target. //We will try to get around this by ignoring the ICMP packet causing these problems on client creation CloseConnection(true, 28); } catch (InvalidOperationException) { CloseConnection(true, 29); } catch (Exception ex) { NetworkComms.LogError(ex, "Error_UDPConnectionIncomingPacketHandler"); CloseConnection(true, 30); } }
/// <summary> /// Trigger connection specific packet delegates with the provided parameters. Returns true if connection specific handlers were executed. /// </summary> /// <param name="packetHeader">The packetHeader for which all delegates should be triggered with</param> /// <param name="incomingObjectBytes">The serialised and or compressed bytes to be used</param> /// <param name="options">The incoming sendReceiveOptions to use overriding defaults</param> /// <returns>Returns true if connection specific handlers were executed.</returns> public bool TriggerSpecificPacketHandlers(PacketHeader packetHeader, MemoryStream incomingObjectBytes, SendReceiveOptions options) { try { if (packetHeader == null) { throw new ArgumentNullException("packetHeader", "Provided PacketHeader cannot not be null."); } if (incomingObjectBytes == null) { throw new ArgumentNullException("incomingObjectBytes", "Provided MemoryStream cannot not be null for packetType " + packetHeader.PacketType); } if (options == null) { throw new ArgumentNullException("options", "Provided SendReceiveOptions cannot not be null for packetType " + packetHeader.PacketType); } //We take a copy of the handlers list incase it is modified outside of the lock List <IPacketTypeHandlerDelegateWrapper> handlersCopy = null; lock (delegateLocker) if (incomingPacketHandlers.ContainsKey(packetHeader.PacketType)) { handlersCopy = new List <IPacketTypeHandlerDelegateWrapper>(incomingPacketHandlers[packetHeader.PacketType]); } if (handlersCopy == null) { //If we have received an unknown packet type we ignore them on this connection specific level and just finish here return(false); } else { //Idiot check if (handlersCopy.Count == 0) { throw new PacketHandlerException("An entry exists in the packetHandlers list but it contains no elements. This should not be possible."); } //Deserialise the object only once object returnObject = handlersCopy[0].DeSerialize(incomingObjectBytes, options); //Pass the data onto the handler and move on. if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... passing completed data packet to selected connection specific handlers."); } //Pass the object to all necessary delgates //We need to use a copy because we may modify the original delegate list during processing foreach (IPacketTypeHandlerDelegateWrapper wrapper in handlersCopy) { try { wrapper.Process(packetHeader, this, returnObject); } catch (Exception ex) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Fatal("An unhandled exception was caught while processing a packet handler for a packet type '" + packetHeader.PacketType + "'. Make sure to catch errors in packet handlers. See error log file for more information."); } NetworkComms.LogError(ex, "PacketHandlerErrorSpecific_" + packetHeader.PacketType); } } } } catch (Exception ex) { //If anything goes wrong here all we can really do is log the exception if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Fatal("An exception occured in TriggerPacketHandler() for a packet type '" + packetHeader.PacketType + "'. See error log file for more information."); } NetworkComms.LogError(ex, "PacketHandlerErrorSpecific_" + packetHeader.PacketType); } return(true); }
/// <summary> /// Sends a single object to the provided endPoint. NOTE: Any possible reply will be ignored unless listening for incoming udp packets. /// </summary> /// <param name="sendingPacketType">The sending packet type</param> /// <param name="objectToSend">The object to send</param> /// <param name="ipEndPoint">The destination IPEndPoint. Supports multicast endpoints.</param> /// <param name="sendReceiveOptions">The sendReceiveOptions to use for this send</param> public static void SendObject(string sendingPacketType, object objectToSend, IPEndPoint ipEndPoint, SendReceiveOptions sendReceiveOptions) { UDPConnection connectionToUse; lock (udpRogueSenderCreationLocker) { if (NetworkComms.commsShutdown) { throw new CommunicationException("Attempting to send UDP packet but NetworkCommsDotNet is in the process of shutting down."); } else if (!udpRogueSenders.ContainsKey(ipEndPoint.AddressFamily) || udpRogueSenders[ipEndPoint.AddressFamily].ConnectionInfo.ConnectionState == ConnectionState.Shutdown) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Creating UDPRougeSender."); } IPAddress any; if (ipEndPoint.AddressFamily == AddressFamily.InterNetwork) { any = IPAddress.Any; } else if (ipEndPoint.AddressFamily == AddressFamily.InterNetworkV6) { any = IPAddress.IPv6Any; } else { throw new CommunicationException("Attempting to send UDP packet over unsupported network address family: " + ipEndPoint.AddressFamily.ToString()); } udpRogueSenders[ipEndPoint.AddressFamily] = new UDPConnection(new ConnectionInfo(true, ConnectionType.UDP, new IPEndPoint(any, 0), new IPEndPoint(any, 0)), NetworkComms.DefaultSendReceiveOptions, UDPOptions.None, false); } //Get the rouge sender here connectionToUse = udpRogueSenders[ipEndPoint.AddressFamily]; } //Get connection defaults if no sendReceiveOptions were provided if (sendReceiveOptions == null) { sendReceiveOptions = connectionToUse.ConnectionDefaultSendReceiveOptions; } //If we are listening on what will be the outgoing adaptor we send with that client to ensure reply packets are collected //Determining this is annoyingly non-trivial //For now we will use the following method and look to improve upon it in future //Some very quick testing gave an average runtime of this method to be 0.12ms (averageover 1000 iterations) (perhaps not so bad after all) try { IPEndPoint localEndPoint = NetworkComms.BestLocalEndPoint(ipEndPoint); lock (udpClientListenerLocker) { IPEndPoint existingLocalEndPoint = ExistingLocalListenEndPoints(localEndPoint.Address); if (existingLocalEndPoint != null) { connectionToUse = udpConnectionListeners[existingLocalEndPoint]; } } } catch (Exception ex) { //if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Failed to determine preferred existing udpClientListener to " + ipEndPoint.Address + ":" + ipEndPoint.Port + ". Will just use the rogue udp sender instead."); NetworkComms.LogError(ex, "BestLocalEndPointError"); } using (Packet sendPacket = new Packet(sendingPacketType, objectToSend, sendReceiveOptions)) connectionToUse.SendPacketSpecific(sendPacket, ipEndPoint); }
/// <summary> /// The worker object for the thread pool /// </summary> /// <param name="state"></param> private void ThreadWorker(object state) { WorkerInfo threadInfo = (WorkerInfo)state; do { //While there are jobs in the queue process the jobs while (true) { if (threadInfo.CurrentCallBackWrapper == null) { KeyValuePair <QueueItemPriority, WaitCallBackWrapper> packetQueueItem; lock (SyncRoot) { UpdateThreadWaitSleepJoinCountCache(); int numInJobActiveThreadsCount = Math.Max(0, threadDict.Count - CurrentNumWaitSleepJoinThreadsCache - requireJobThreadsCount); if (shutdown || threadDict.Count > MaxTotalThreadsCount) //If we have too many active threads { //If shutdown was true then we may need to set thread to idle if (threadInfo.ThreadIdle && requireJobThreadsCount > 0) { requireJobThreadsCount--; } threadInfo.ClearThreadIdle(); threadDict.Remove(threadInfo.ThreadId); workerInfoDict.Remove(threadInfo.ThreadId); UpdateThreadWaitSleepJoinCountCache(); return; } else if (numInJobActiveThreadsCount > MaxActiveThreadsCount) //If we have too many active threads { //We wont close here to prevent thread creation/destruction thrashing. //We will instead act as if there is no work and wait to potentially be timed out if (!threadInfo.ThreadIdle) { threadInfo.SetThreadIdle(); requireJobThreadsCount++; } break; } else { //Try to get a job if (!jobQueue.TryTake(out packetQueueItem)) //We fail to get a new job { //If we failed to get a job we switch to idle and wait to be triggered if (!threadInfo.ThreadIdle) { threadInfo.SetThreadIdle(); requireJobThreadsCount++; } break; } else { if (threadInfo.ThreadIdle && requireJobThreadsCount > 0) { requireJobThreadsCount--; } threadInfo.UpdateCurrentCallBackWrapper(packetQueueItem.Value); threadInfo.ClearThreadIdle(); } } } } //Perform the waitcallBack try { threadInfo.SetInsideCallBack(); threadInfo.CurrentCallBackWrapper.WaitCallBack(threadInfo.CurrentCallBackWrapper.State); } catch (Exception ex) { NetworkComms.LogError(ex, "ManagedThreadPoolCallBackError", "An unhandled exception was caught while processing a callback. Make sure to catch errors in callbacks to prevent this error file being produced."); } finally { threadInfo.ClearInsideCallBack(); } threadInfo.UpdateLastActiveTime(); threadInfo.ClearCallBackWrapper(); } //As soon as the queue is empty we wait until perhaps close time if (!threadInfo.ThreadSignal.WaitOne(250)) { //While we are waiting we check to see if we need to close if (DateTime.Now - threadInfo.LastActiveTime > ThreadIdleTimeoutClose) { lock (SyncRoot) { //We have timed out but we don't go below the minimum if (threadDict.Count > MinThreadsCount) { if (threadInfo.ThreadIdle && requireJobThreadsCount > 0) { requireJobThreadsCount--; } threadInfo.ClearThreadIdle(); threadDict.Remove(threadInfo.ThreadId); workerInfoDict.Remove(threadInfo.ThreadId); UpdateThreadWaitSleepJoinCountCache(); return; } } } } //We only leave via one of our possible breaks } while (true); }
/// <summary> /// Asynchronous incoming connection data delegate /// </summary> /// <param name="ar">The call back state object</param> void IncomingTCPPacketHandler(IAsyncResult ar) { //Initialised with true so that logic still works in WP8 bool dataAvailable = true; #if !WINDOWS_PHONE //Incoming data always gets handled in a timeCritical fashion at this point Thread.CurrentThread.Priority = NetworkComms.timeCriticalThreadPriority; //int bytesRead; #endif try { #if WINDOWS_PHONE var stream = ar.AsyncState as Stream; var count = stream.EndRead(ar); totalBytesRead = count + totalBytesRead; #else NetworkStream netStream = (NetworkStream)ar.AsyncState; if (!netStream.CanRead) { throw new ObjectDisposedException("Unable to read from stream."); } totalBytesRead = netStream.EndRead(ar) + totalBytesRead; dataAvailable = netStream.DataAvailable; #endif if (totalBytesRead > 0) { ConnectionInfo.UpdateLastTrafficTime(); //If we have read a single byte which is 0 and we are not expecting other data if (totalBytesRead == 1 && dataBuffer[0] == 0 && packetBuilder.TotalBytesExpected - packetBuilder.TotalBytesCached == 0) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... null packet removed in IncomingPacketHandler() from " + ConnectionInfo + ". 1"); } } else { //if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace(" ... " + totalBytesRead.ToString() + " bytes added to packetBuilder."); //If there is more data to get then add it to the packets lists; packetBuilder.AddPartialPacket(totalBytesRead, dataBuffer); #if !WINDOWS_PHONE //If we have more data we might as well continue reading syncronously //In order to deal with data as soon as we think we have sufficient we will leave this loop while (dataAvailable && packetBuilder.TotalBytesCached < packetBuilder.TotalBytesExpected) { int bufferOffset = 0; //We need a buffer for our incoming data //First we try to reuse a previous buffer if (packetBuilder.TotalPartialPacketCount > 0 && packetBuilder.NumUnusedBytesMostRecentPartialPacket() > 0) { dataBuffer = packetBuilder.RemoveMostRecentPartialPacket(ref bufferOffset); } else { //If we have nothing to reuse we allocate a new buffer dataBuffer = new byte[NetworkComms.ReceiveBufferSizeBytes]; } totalBytesRead = netStream.Read(dataBuffer, bufferOffset, dataBuffer.Length - bufferOffset) + bufferOffset; if (totalBytesRead > 0) { ConnectionInfo.UpdateLastTrafficTime(); //If we have read a single byte which is 0 and we are not expecting other data if (totalBytesRead == 1 && dataBuffer[0] == 0 && packetBuilder.TotalBytesExpected - packetBuilder.TotalBytesCached == 0) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... null packet removed in IncomingPacketHandler() from " + ConnectionInfo + ". 2"); } //LastTrafficTime = DateTime.Now; } else { //if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace(" ... " + totalBytesRead.ToString() + " bytes added to packetBuilder for connection with " + ConnectionInfo + ". Cached " + packetBuilder.TotalBytesCached.ToString() + "B, expecting " + packetBuilder.TotalBytesExpected.ToString() + "B."); packetBuilder.AddPartialPacket(totalBytesRead, dataBuffer); dataAvailable = netStream.DataAvailable; } } else { break; } } #endif } } if (packetBuilder.TotalBytesCached > 0 && packetBuilder.TotalBytesCached >= packetBuilder.TotalBytesExpected) { //Once we think we might have enough data we call the incoming packet handle handoff //Should we have a complete packet this method will start the appriate task //This method will now clear byes from the incoming packets if we have received something complete. IncomingPacketHandleHandOff(packetBuilder); } if (totalBytesRead == 0 && (!dataAvailable || ConnectionInfo.ConnectionState == ConnectionState.Shutdown)) { CloseConnection(false, -2); } else { //We need a buffer for our incoming data //First we try to reuse a previous buffer if (packetBuilder.TotalPartialPacketCount > 0 && packetBuilder.NumUnusedBytesMostRecentPartialPacket() > 0) { dataBuffer = packetBuilder.RemoveMostRecentPartialPacket(ref totalBytesRead); } else { //If we have nothing to reuse we allocate a new buffer dataBuffer = new byte[NetworkComms.ReceiveBufferSizeBytes]; totalBytesRead = 0; } #if WINDOWS_PHONE stream.BeginRead(dataBuffer, totalBytesRead, dataBuffer.Length - totalBytesRead, IncomingTCPPacketHandler, stream); #else netStream.BeginRead(dataBuffer, totalBytesRead, dataBuffer.Length - totalBytesRead, IncomingTCPPacketHandler, netStream); #endif } } catch (IOException) { CloseConnection(true, 12); } catch (ObjectDisposedException) { CloseConnection(true, 13); } catch (SocketException) { CloseConnection(true, 14); } catch (InvalidOperationException) { CloseConnection(true, 15); } catch (Exception ex) { NetworkComms.LogError(ex, "Error_TCPConnectionIncomingPacketHandler"); CloseConnection(true, 31); } #if !WINDOWS_PHONE Thread.CurrentThread.Priority = ThreadPriority.Normal; #endif }
/// <summary> /// Synchronous incoming connection data worker /// </summary> void IncomingTCPDataSyncWorker() { bool dataAvailable = false; try { while (true) { if (ConnectionInfo.ConnectionState == ConnectionState.Shutdown) { break; } int bufferOffset = 0; //We need a buffer for our incoming data //First we try to reuse a previous buffer if (packetBuilder.TotalPartialPacketCount > 0 && packetBuilder.NumUnusedBytesMostRecentPartialPacket() > 0) { dataBuffer = packetBuilder.RemoveMostRecentPartialPacket(ref bufferOffset); } else { //If we have nothing to reuse we allocate a new buffer dataBuffer = new byte[NetworkComms.ReceiveBufferSizeBytes]; } //We block here until there is data to read //When we read data we read until method returns or we fill the buffer length totalBytesRead = tcpClientNetworkStream.Read(dataBuffer, bufferOffset, dataBuffer.Length - bufferOffset) + bufferOffset; //Check to see if there is more data ready to be read dataAvailable = tcpClientNetworkStream.DataAvailable; //If we read any data it gets handed off to the packetBuilder if (totalBytesRead > 0) { ConnectionInfo.UpdateLastTrafficTime(); //If we have read a single byte which is 0 and we are not expecting other data if (totalBytesRead == 1 && dataBuffer[0] == 0 && packetBuilder.TotalBytesExpected - packetBuilder.TotalBytesCached == 0) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... null packet removed in IncomingDataSyncWorker() from " + ConnectionInfo + "."); } } else { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... " + totalBytesRead.ToString() + " bytes added to packetBuilder for connection with " + ConnectionInfo + ". Cached " + packetBuilder.TotalBytesCached.ToString() + "B, expecting " + packetBuilder.TotalBytesExpected.ToString() + "B."); } packetBuilder.AddPartialPacket(totalBytesRead, dataBuffer); } } else if (totalBytesRead == 0 && (!dataAvailable || ConnectionInfo.ConnectionState == ConnectionState.Shutdown)) { //If we read 0 bytes and there is no data available we should be shutting down CloseConnection(false, -10); break; } //If we have read some data and we have more or equal what was expected we attempt a data handoff if (packetBuilder.TotalBytesCached > 0 && packetBuilder.TotalBytesCached >= packetBuilder.TotalBytesExpected) { IncomingPacketHandleHandOff(packetBuilder); } } } //On any error here we close the connection catch (NullReferenceException) { CloseConnection(true, 7); } catch (IOException) { CloseConnection(true, 8); } catch (ObjectDisposedException) { CloseConnection(true, 9); } catch (SocketException) { CloseConnection(true, 10); } catch (InvalidOperationException) { CloseConnection(true, 11); } catch (Exception ex) { NetworkComms.LogError(ex, "Error_TCPConnectionIncomingPacketHandler"); CloseConnection(true, 39); } //Clear the listen thread object because the thread is about to end incomingDataListenThread = null; if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Incoming data listen thread ending for " + ConnectionInfo); } }
/// <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> /// Attempts to use the data provided in packetBuilder to recreate something usefull. If we don't have enough data yet that value is set in packetBuilder. /// </summary> /// <param name="packetBuilder">The <see cref="PacketBuilder"/> containing incoming cached data</param> protected void IncomingPacketHandleHandOff(PacketBuilder packetBuilder) { try { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... checking for completed packet with " + packetBuilder.TotalBytesCached.ToString() + " bytes read."); } if (packetBuilder.TotalPartialPacketCount == 0) { throw new Exception("Executing IncomingPacketHandleHandOff when no packets exist in packetbuilder."); } //Loop until we are finished with this packetBuilder int loopCounter = 0; while (true) { //If we have ended up with a null packet at the front, probably due to some form of concatentation we can pull it off here //It is possible we have concatenation of several null packets along with real data so we loop until the firstByte is greater than 0 if (packetBuilder.FirstByte() == 0) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... null packet removed in IncomingPacketHandleHandOff() from " + ConnectionInfo + ", loop index - " + loopCounter.ToString()); } packetBuilder.ClearNTopBytes(1); //Reset the expected bytes to 0 so that the next check starts from scratch packetBuilder.TotalBytesExpected = 0; //If we have run out of data completely then we can return immediately if (packetBuilder.TotalBytesCached == 0) { return; } } else { //First determine the expected size of a header packet int packetHeaderSize = packetBuilder.FirstByte() + 1; //Do we have enough data to build a header? if (packetBuilder.TotalBytesCached < packetHeaderSize) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... ... more data required for complete packet header."); } //Set the expected number of bytes and then return packetBuilder.TotalBytesExpected = packetHeaderSize; return; } //We have enough for a header PacketHeader topPacketHeader; using (MemoryStream headerStream = packetBuilder.ReadDataSection(1, packetHeaderSize - 1)) topPacketHeader = new PacketHeader(headerStream, NetworkComms.InternalFixedSendReceiveOptions); //Idiot test if (topPacketHeader.PacketType == null) { throw new SerialisationException("packetType value in packetHeader should never be null"); } //We can now use the header to establish if we have enough payload data //First case is when we have not yet received enough data if (packetBuilder.TotalBytesCached < packetHeaderSize + topPacketHeader.PayloadPacketSize) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... ... more data required for complete packet payload. Expecting " + (packetHeaderSize + topPacketHeader.PayloadPacketSize).ToString() + " total packet bytes."); } //Set the expected number of bytes and then return packetBuilder.TotalBytesExpected = packetHeaderSize + topPacketHeader.PayloadPacketSize; return; } //Second case is we have enough data else if (packetBuilder.TotalBytesCached >= packetHeaderSize + topPacketHeader.PayloadPacketSize) { //We can either have exactly the right amount or even more than we were expecting //We may have too much data if we are sending high quantities and the packets have been concatenated //no problem!! SendReceiveOptions incomingPacketSendReceiveOptions = IncomingPacketSendReceiveOptions(topPacketHeader); if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Received packet of type '" + topPacketHeader.PacketType + "' from " + ConnectionInfo + ", containing " + packetHeaderSize.ToString() + " header bytes and " + topPacketHeader.PayloadPacketSize.ToString() + " payload bytes."); } //If this is a reserved packetType we call the method inline so that it gets dealt with immediately bool isReservedType = false; foreach (var tName in NetworkComms.reservedPacketTypeNames) { //isReservedType |= topPacketHeader.PacketType == tName; if (topPacketHeader.PacketType == tName) { isReservedType = true; break; } } //Only reserved packet types get completed inline if (isReservedType) { #if WINDOWS_PHONE var priority = QueueItemPriority.Normal; #else var priority = (QueueItemPriority)Thread.CurrentThread.Priority; #endif PriorityQueueItem item = new PriorityQueueItem(priority, this, topPacketHeader, packetBuilder.ReadDataSection(packetHeaderSize, topPacketHeader.PayloadPacketSize), incomingPacketSendReceiveOptions); if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... handling packet type '" + topPacketHeader.PacketType + "' inline. Loop index - " + loopCounter.ToString()); } NetworkComms.CompleteIncomingItemTask(item); } else { QueueItemPriority itemPriority = (incomingPacketSendReceiveOptions.Options.ContainsKey("ReceiveHandlePriority") ? (QueueItemPriority)Enum.Parse(typeof(QueueItemPriority), incomingPacketSendReceiveOptions.Options["ReceiveHandlePriority"]) : QueueItemPriority.Normal); PriorityQueueItem item = new PriorityQueueItem(itemPriority, this, topPacketHeader, packetBuilder.ReadDataSection(packetHeaderSize, topPacketHeader.PayloadPacketSize), incomingPacketSendReceiveOptions); //QueueItemPriority.Highest is the only priority that is executed inline #if !WINDOWS_PHONE if (itemPriority == QueueItemPriority.Highest) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... handling packet type '" + topPacketHeader.PacketType + "' with priority HIGHEST inline. Loop index - " + loopCounter.ToString()); } NetworkComms.CompleteIncomingItemTask(item); } else { int threadId = NetworkComms.CommsThreadPool.EnqueueItem(item.Priority, NetworkComms.CompleteIncomingItemTask, item); if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... added completed " + item.PacketHeader.PacketType + " packet to thread pool (Q:" + NetworkComms.CommsThreadPool.QueueCount.ToString() + ", T:" + NetworkComms.CommsThreadPool.CurrentNumTotalThreads.ToString() + ", I:" + NetworkComms.CommsThreadPool.CurrentNumIdleThreads.ToString() + ") with priority " + itemPriority.ToString() + (threadId > 0 ? ". Selected threadId=" + threadId.ToString() : "") + ". Loop index=" + loopCounter.ToString() + "."); } } #else int threadId = NetworkComms.CommsThreadPool.EnqueueItem(item.Priority, NetworkComms.CompleteIncomingItemTask, item); if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... added completed " + item.PacketHeader.PacketType + " packet to thread pool (Q:" + NetworkComms.CommsThreadPool.QueueCount.ToString() + ", T:" + NetworkComms.CommsThreadPool.CurrentNumTotalThreads.ToString() + ", I:" + NetworkComms.CommsThreadPool.CurrentNumIdleThreads.ToString() + ") with priority " + itemPriority.ToString() + (threadId > 0 ? ". Selected threadId=" + threadId.ToString() : "") + ". Loop index=" + loopCounter.ToString() + "."); } #endif } //We clear the bytes we have just handed off if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Removing " + (packetHeaderSize + topPacketHeader.PayloadPacketSize).ToString() + " bytes from incoming packet buffer from connection with " + ConnectionInfo + "."); } packetBuilder.ClearNTopBytes(packetHeaderSize + topPacketHeader.PayloadPacketSize); //Reset the expected bytes to 0 so that the next check starts from scratch packetBuilder.TotalBytesExpected = 0; //If we have run out of data completely then we can return immediately if (packetBuilder.TotalBytesCached == 0) { return; } } else { throw new CommunicationException("This should be impossible!"); } } loopCounter++; } } catch (Exception ex) { //Any error, throw an exception. if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Fatal("A fatal exception occured in IncomingPacketHandleHandOff(), connection with " + ConnectionInfo + " be closed. See log file for more information."); } NetworkComms.LogError(ex, "CommsError"); CloseConnection(true, 45); } }
/// <summary> /// Closes the connection and trigger any associated shutdown delegates. /// </summary> /// <param name="closeDueToError">Closing a connection due an error possibly requires a few extra steps.</param> /// <param name="logLocation">Optional debug parameter.</param> public void CloseConnection(bool closeDueToError, int logLocation = 0) { try { if (NetworkComms.LoggingEnabled) { if (closeDueToError) { NetworkComms.Logger.Debug("Closing connection with " + ConnectionInfo + " due to error from [" + logLocation.ToString() + "]."); } else { NetworkComms.Logger.Debug("Closing connection with " + ConnectionInfo + " from [" + logLocation.ToString() + "]."); } } ConnectionInfo.NoteConnectionShutdown(); //Set possible error cases if (closeDueToError) { connectionSetupException = true; connectionSetupExceptionStr = "Connection was closed during setup from [" + logLocation.ToString() + "]."; } //Ensure we are not waiting for a connection to be established if we have died due to error connectionSetupWait.Set(); //Call any connection specific close requirements CloseConnectionSpecific(closeDueToError, logLocation); try { //If we are calling close from the listen thread we are actually in the same thread //We must guarantee the listen thread stops even if that means we need to nuke it //If we did not we may not be able to shutdown properly. if (incomingDataListenThread != null && incomingDataListenThread != Thread.CurrentThread && (incomingDataListenThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin || incomingDataListenThread.ThreadState == System.Threading.ThreadState.Running)) { //If we have made it this far we give the ythread a further 50ms to finish before nuking. if (!incomingDataListenThread.Join(50)) { incomingDataListenThread.Abort(); if (NetworkComms.LoggingEnabled && ConnectionInfo != null) { NetworkComms.Logger.Warn("Incoming data listen thread with " + ConnectionInfo + " aborted."); } } } } catch (Exception) { } //Close connection my get called multiple times for a given connection depending on the reason for being closed bool firstClose = NetworkComms.RemoveConnectionReference(this); try { //Almost there //Last thing is to call any connection specific shutdown delegates if (firstClose && ConnectionSpecificShutdownDelegate != null) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Triggered connection specific shutdown delegates with " + ConnectionInfo); } ConnectionSpecificShutdownDelegate(this); } } catch (Exception ex) { NetworkComms.LogError(ex, "ConnectionSpecificShutdownDelegateError", "Error while executing connection specific shutdown delegates for " + ConnectionInfo + ". Ensure any shutdown exceptions are caught in your own code."); } try { //Last but not least we call any global connection shutdown delegates if (firstClose && NetworkComms.globalConnectionShutdownDelegates != null) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Triggered global shutdown delegates with " + ConnectionInfo); } NetworkComms.globalConnectionShutdownDelegates(this); } } catch (Exception ex) { NetworkComms.LogError(ex, "GlobalConnectionShutdownDelegateError", "Error while executing global connection shutdown delegates for " + ConnectionInfo + ". Ensure any shutdown exceptions are caught in your own code."); } } catch (Exception ex) { if (ex is ThreadAbortException) { /*Ignore the threadabort exception if we had to nuke a thread*/ } else { NetworkComms.LogError(ex, "NCError_CloseConnection", "Error closing connection with " + ConnectionInfo + ". Close called from " + logLocation.ToString() + (closeDueToError ? " due to error." : ".")); } //We try to rethrow where possible but CloseConnection could very likely be called from within networkComms so we just have to be happy with a log here } }
/// <summary> /// Picks up any new incoming connections /// </summary> private static void IncomingConnectionWorker() { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Info("TCP IncomingConnectionWorker thread started."); } try { while (!shutdownIncomingConnectionWorkerThread) { try { bool pickedUpNewConnection = false; List <TcpListener> currentTCPListeners = new List <TcpListener>(); lock (staticTCPConnectionLocker) { foreach (var pair in tcpListenerDict) { currentTCPListeners.Add(pair.Value); } } foreach (var listener in currentTCPListeners) { if (listener.Pending() && !shutdownIncomingConnectionWorkerThread) { pickedUpNewConnection = true; //Pick up the new connection TcpClient newClient = listener.AcceptTcpClient(); //Perform the establish in a task so that we can continue picking up new connections here ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => { #region Pickup The New Connection try { GetConnection(new ConnectionInfo(true, ConnectionType.TCP, (IPEndPoint)newClient.Client.RemoteEndPoint), NetworkComms.DefaultSendReceiveOptions, newClient, true); } catch (ConfirmationTimeoutException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (CommunicationException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (ConnectionSetupException) { //If we are the server end and we did not pick the incoming connection up then tooo bad! } catch (SocketException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (Exception ex) { //For some odd reason SocketExceptions don't always get caught above, so another check if (ex.GetBaseException().GetType() != typeof(SocketException)) { //Can we catch the socketException by looking at the string error text? if (ex.ToString().StartsWith("System.Net.Sockets.SocketException")) { NetworkComms.LogError(ex, "ConnectionSetupError_SE"); } else { NetworkComms.LogError(ex, "ConnectionSetupError"); } } } #endregion })); } } //We will only pause if we didnt get any new connections if (!pickedUpNewConnection && !shutdownIncomingConnectionWorkerThread) { Thread.Sleep(100); } } catch (ConfirmationTimeoutException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (CommunicationException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (ConnectionSetupException) { //If we are the server end and we did not pick the incoming connection up then tooo bad! } catch (SocketException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (ObjectDisposedException) { //If this exception gets thrown its generally just a client closing a connection almost immediately after creation } catch (Exception ex) { //For some odd reason SocketExceptions don't always get caught above, so another check if (ex.GetBaseException().GetType() != typeof(SocketException)) { //Can we catch the socketException by looking at the string error text? if (ex.ToString().StartsWith("System.Net.Sockets.SocketException")) { NetworkComms.LogError(ex, "CommsSetupError_SE"); } else { NetworkComms.LogError(ex, "CommsSetupError"); } } } } } catch (Exception ex) { NetworkComms.LogError(ex, "CriticalCommsError"); } finally { //We try to close all of the tcpListeners CloseAndRemoveAllLocalConnectionListeners(); } //newIncomingListenThread = null; if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Info("TCP IncomingConnectionWorker thread ended."); } }