/// <summary> /// Attempts to complete the connection establish with a minimum of locking to avoid possible deadlocking /// </summary> /// <param name="remoteConnectionInfo"><see cref="ConnectionInfo"/> corresponding with remoteEndPoint</param> /// <param name="possibleClashWithExistingConnection">True if a connection already exists with provided remoteEndPoint</param> /// <param name="existingConnection">A reference to an existing connection if it exists</param> /// <returns>True if connection is successfully setup, otherwise false</returns> private bool ConnectionSetupHandlerFinal(ConnectionInfo remoteConnectionInfo, ref bool possibleClashWithExistingConnection, ref Connection existingConnection) { lock (NetworkComms.globalDictAndDelegateLocker) { List <Connection> connectionByEndPoint = NetworkComms.GetExistingConnection(ConnectionInfo.RemoteEndPoint, ConnectionInfo.LocalEndPoint, ConnectionInfo.ConnectionType, ConnectionInfo.ApplicationLayerProtocol); //If we no longer have the original endPoint reference (set in the constructor) then the connection must have been closed already if (connectionByEndPoint.Count == 0) { connectionSetupException = true; connectionSetupExceptionStr = "Connection setup received after connection closure with " + ConnectionInfo; } else { //COMMENT: As of version 3.0.0 we have allowed loop back connections where the identifier is the same //We need to check for a possible GUID clash //Probability of a clash is approx 0.1% if 1E19 connections are maintained simultaneously (This many connections has not be tested ;)) //but hey, we live in a crazy world! //if (remoteConnectionInfo.NetworkIdentifier == NetworkComms.NetworkIdentifier) //{ // connectionSetupException = true; // connectionSetupExceptionStr = "Remote peer has same network identifier to local, " + remoteConnectionInfo.NetworkIdentifier + ". A real duplication is vanishingly improbable so this exception has probably been thrown because the local and remote application are the same."; //} //else if (connectionByEndPoint[0] != this) { possibleClashWithExistingConnection = true; existingConnection = connectionByEndPoint[0]; } else if (connectionByEndPoint[0].ConnectionInfo.NetworkIdentifier != ShortGuid.Empty && connectionByEndPoint[0].ConnectionInfo.NetworkIdentifier != remoteConnectionInfo.NetworkIdentifier) { //We are in the same connection, so don't need to throw and exception but the remote network identifier //has changed. //This can happen for connection types where the local connection (this) may not have been closed //when the remote peer closed. We need to trigger the connection close delegates with the old info, update //the connection info and then call the establish delegates #region Reset Connection without closing //Call the connection close delegates try { //Almost there //Last thing is to call any connection specific shutdown delegates if (ConnectionSpecificShutdownDelegate != null) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Triggered connection specific shutdown delegates with " + ConnectionInfo); } ConnectionSpecificShutdownDelegate(this); } } catch (Exception ex) { LogTools.LogException(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 (NetworkComms.globalConnectionShutdownDelegates != null) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Debug("Triggered global shutdown delegates with " + ConnectionInfo); } NetworkComms.globalConnectionShutdownDelegates(this); } } catch (Exception ex) { LogTools.LogException(ex, "GlobalConnectionShutdownDelegateError", "Error while executing global connection shutdown delegates for " + ConnectionInfo + ". Ensure any shutdown exceptions are caught in your own code."); } EndPoint newRemoteEndPoint; if (this.ConnectionInfo.RemoteEndPoint.GetType() == typeof(IPEndPoint) && remoteConnectionInfo.LocalEndPoint.GetType() == typeof(IPEndPoint)) { newRemoteEndPoint = new IPEndPoint(this.ConnectionInfo.RemoteIPEndPoint.Address, remoteConnectionInfo.LocalIPEndPoint.Port); } else { throw new NotImplementedException("ConnectionSetupHandlerFinal not implemented for EndPoints of type " + this.ConnectionInfo.RemoteEndPoint.GetType()); } NetworkComms.UpdateConnectionReferenceByEndPoint(this, newRemoteEndPoint, this.ConnectionInfo.LocalEndPoint); ConnectionInfo.UpdateInfoAfterRemoteHandshake(remoteConnectionInfo, newRemoteEndPoint); //Trigger the establish delegates TriggerConnectionEstablishDelegates(); #endregion return(true); } else { //Update the connection info //We never change the this.ConnectionInfo.RemoteEndPoint.Address as there might be NAT involved //We may update the port however EndPoint newRemoteEndPoint; if (this is IPConnection) { newRemoteEndPoint = new IPEndPoint(this.ConnectionInfo.RemoteIPEndPoint.Address, remoteConnectionInfo.LocalIPEndPoint.Port); } #if NET35 || NET4 else if (this is BluetoothConnection) { newRemoteEndPoint = ConnectionInfo.RemoteBTEndPoint; } #endif else { throw new NotImplementedException("ConnectionSetupHandlerFinal not implemented for EndPoints of type " + this.ConnectionInfo.RemoteEndPoint.GetType()); } NetworkComms.UpdateConnectionReferenceByEndPoint(this, newRemoteEndPoint, this.ConnectionInfo.LocalEndPoint); ConnectionInfo.UpdateInfoAfterRemoteHandshake(remoteConnectionInfo, newRemoteEndPoint); return(true); } } } return(false); }
/// <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 } }