Imports System.ServiceModel Imports System.Threading Imports System.Xml Imports DigitalData.Modules.Base Imports DigitalData.Modules.EDMI.API.EDMIServiceReference Imports DigitalData.Modules.Logging Public Class Channel Inherits BaseClass Private Shared ReadOnly _FactoryLock As New Object() Private Shared _SharedFactory As ChannelFactory(Of IEDMIServiceChannel) = Nothing Private Shared _SharedFactoryEndpoint As String = String.Empty Private ReadOnly ChannelFactory As ChannelFactory(Of IEDMIServiceChannel) Public Event Reconnect As EventHandler Private Shared _ActiveConnections As Integer = 0 Public Shared ReadOnly Property ActiveConnections As Integer Get Return _ActiveConnections End Get End Property Public Shared Function GetBinding(Optional AuthenticationMode As TcpClientCredentialType = TcpClientCredentialType.Windows) As NetTcpBinding Dim oMaxConnections = Math.Max(500, Environment.ProcessorCount * 50) Return New NetTcpBinding() With { .MaxReceivedMessageSize = Constants.ChannelSettings.MAX_RECEIVED_MESSAGE_SIZE, .MaxBufferSize = Constants.ChannelSettings.MAX_BUFFER_SIZE, .MaxBufferPoolSize = Constants.ChannelSettings.MAX_BUFFER_POOL_SIZE, .ListenBacklog = oMaxConnections, .MaxConnections = oMaxConnections, .TransferMode = TransferMode.Streamed, .SendTimeout = TimeSpan.FromSeconds(60), .ReceiveTimeout = TimeSpan.FromMinutes(10), .OpenTimeout = TimeSpan.FromSeconds(60), .CloseTimeout = TimeSpan.FromSeconds(30), .Security = New NetTcpSecurity() With { .Mode = SecurityMode.Transport, .Transport = New TcpTransportSecurity() With { .ClientCredentialType = AuthenticationMode } }, .ReaderQuotas = New XmlDictionaryReaderQuotas() With { .MaxArrayLength = Constants.ChannelSettings.MAX_ARRAY_LENGTH, .MaxStringContentLength = Constants.ChannelSettings.MAX_STRING_CONTENT_LENGTH } } End Function Public Sub New(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct) MyBase.New(pLogConfig) ChannelFactory = GetOrCreateSharedFactory(pLogConfig, pServerAddress) End Sub ''' ''' Gibt eine gemeinsame ChannelFactory zurück. ''' Ein Connection-Pool für alle Client-Instanzen desselben Endpoints. ''' Private Shared Function GetOrCreateSharedFactory(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct) As ChannelFactory(Of IEDMIServiceChannel) Dim oEndpoint = $"net.tcp://{pServerAddress.Host}:{pServerAddress.Port}/DigitalData/Services/Main" ' ← Volatile.Read: garantiert aktuellen Wert auch ohne Lock Dim oCurrentFactory = Volatile.Read(_SharedFactory) If oCurrentFactory IsNot Nothing AndAlso _SharedFactoryEndpoint = oEndpoint AndAlso (oCurrentFactory.State = CommunicationState.Opened OrElse oCurrentFactory.State = CommunicationState.Created) Then Return oCurrentFactory End If SyncLock _FactoryLock ' Double-check nach Lock If _SharedFactory IsNot Nothing AndAlso _SharedFactoryEndpoint = oEndpoint AndAlso (_SharedFactory.State = CommunicationState.Opened OrElse _SharedFactory.State = CommunicationState.Created) Then Return _SharedFactory End If If _SharedFactory IsNot Nothing Then If _SharedFactory.State = CommunicationState.Faulted OrElse _SharedFactory.State = CommunicationState.Closing Then Try _SharedFactory.Abort() Catch End Try End If _SharedFactory = Nothing End If Dim oLogger = pLogConfig.GetLogger() Dim oBinding = GetBinding() Dim oAddress = New EndpointAddress(oEndpoint) oLogger.Debug("Creating shared ChannelFactory | Endpoint=[{0}] | MaxConnections=[{1}] | SendTimeout=[{2}] | OpenTimeout=[{3}]", oEndpoint, oBinding.MaxConnections, oBinding.SendTimeout, oBinding.OpenTimeout) _SharedFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress) _SharedFactoryEndpoint = oEndpoint Return _SharedFactory End SyncLock End Function ''' ''' Closes a channel gracefully, falls back to Abort on error ''' Public Shared Sub CloseChannel(pChannel As IEDMIServiceChannel) If pChannel Is Nothing Then Return Try If pChannel.State = CommunicationState.Opened Then pChannel.Close() ElseIf pChannel.State = CommunicationState.Faulted OrElse pChannel.State = CommunicationState.Closing Then pChannel.Abort() End If Catch ex As Exception Try pChannel.Abort() Catch End Try End Try End Sub ''' ''' Creates a channel and adds a Faulted-Handler ''' Public Function GetChannel() As IEDMIServiceChannel Try Logger.Debug("Creating channel from shared factory.") Dim oChannel = ChannelFactory.CreateChannel() AddHandler oChannel.Opened, Sub() Dim oCount = Interlocked.Increment(_ActiveConnections) Logger.Debug("Channel opened. Active connections: [{0}]", oCount) End Sub AddHandler oChannel.Closed, Sub() Dim oCount = Interlocked.Decrement(_ActiveConnections) Logger.Debug("Channel closed. Active connections: [{0}]", oCount) End Sub AddHandler oChannel.Faulted, Sub() Dim oCount = Interlocked.Decrement(_ActiveConnections) Logger.Warn("⚠ Channel faulted. Active connections: [{0}]", oCount) RaiseEvent Reconnect(Me, Nothing) End Sub Logger.Debug("Channel created. State=[{0}]", oChannel.State) Return oChannel Catch ex As Exception Logger.Error(ex) Throw End Try End Function End Class